aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--addons/resource.language.en_gb/resources/strings.po108
-rw-r--r--addons/skin.estuary/extras/backgrounds/pattern1.pngbin26325 -> 20983 bytes
-rw-r--r--addons/skin.estuary/extras/backgrounds/pattern2.pngbin37924 -> 37679 bytes
-rw-r--r--addons/skin.estuary/extras/backgrounds/pattern3.pngbin86557 -> 34567 bytes
-rw-r--r--addons/skin.estuary/extras/backgrounds/pattern4.pngbin22602 -> 19332 bytes
-rw-r--r--addons/skin.estuary/extras/backgrounds/pattern5.pngbin88539 -> 88485 bytes
-rw-r--r--addons/skin.estuary/extras/backgrounds/pattern6.pngbin79518 -> 76217 bytes
-rw-r--r--addons/skin.estuary/extras/backgrounds/pattern7.pngbin19791 -> 19599 bytes
-rw-r--r--addons/skin.estuary/media/DefaultPVRProvider.pngbin0 -> 11082 bytes
-rw-r--r--addons/skin.estuary/media/DefaultPVRProviders.pngbin0 -> 10456 bytes
-rw-r--r--addons/skin.estuary/media/flags/aspectratio/1.00.pngbin1076 -> 0 bytes
-rw-r--r--addons/skin.estuary/media/flags/aspectratio/1.19.pngbin1049 -> 0 bytes
-rw-r--r--addons/skin.estuary/media/flags/aspectratio/1.33.pngbin1050 -> 0 bytes
-rw-r--r--addons/skin.estuary/media/flags/aspectratio/1.37.pngbin1115 -> 0 bytes
-rw-r--r--addons/skin.estuary/media/flags/aspectratio/1.66.pngbin1036 -> 0 bytes
-rw-r--r--addons/skin.estuary/media/flags/aspectratio/1.78.pngbin1152 -> 0 bytes
-rw-r--r--addons/skin.estuary/media/flags/aspectratio/1.85.pngbin1228 -> 0 bytes
-rw-r--r--addons/skin.estuary/media/flags/aspectratio/2.00.pngbin1205 -> 0 bytes
-rw-r--r--addons/skin.estuary/media/flags/aspectratio/2.20.pngbin1144 -> 0 bytes
-rw-r--r--addons/skin.estuary/media/flags/aspectratio/2.35.pngbin1284 -> 0 bytes
-rw-r--r--addons/skin.estuary/media/flags/aspectratio/2.40.pngbin1252 -> 0 bytes
-rw-r--r--addons/skin.estuary/media/flags/aspectratio/2.55.pngbin1131 -> 0 bytes
-rw-r--r--addons/skin.estuary/media/flags/aspectratio/2.76.pngbin1193 -> 0 bytes
-rw-r--r--addons/skin.estuary/media/flags/audiochannel/0.pngbin807 -> 0 bytes
-rw-r--r--addons/skin.estuary/media/flags/audiochannel/1.pngbin839 -> 0 bytes
-rw-r--r--addons/skin.estuary/media/flags/audiochannel/10.pngbin864 -> 0 bytes
-rw-r--r--addons/skin.estuary/media/flags/audiochannel/2.pngbin958 -> 0 bytes
-rw-r--r--addons/skin.estuary/media/flags/audiochannel/3.pngbin803 -> 0 bytes
-rw-r--r--addons/skin.estuary/media/flags/audiochannel/4.pngbin928 -> 0 bytes
-rw-r--r--addons/skin.estuary/media/flags/audiochannel/5.pngbin684 -> 0 bytes
-rw-r--r--addons/skin.estuary/media/flags/audiochannel/6.pngbin809 -> 0 bytes
-rw-r--r--addons/skin.estuary/media/flags/audiochannel/7.pngbin856 -> 0 bytes
-rw-r--r--addons/skin.estuary/media/flags/audiochannel/8.pngbin657 -> 0 bytes
-rw-r--r--addons/skin.estuary/media/flags/audiocodec/aac.pngbin1042 -> 0 bytes
-rw-r--r--addons/skin.estuary/media/flags/audiocodec/aac_latm.pngbin1042 -> 0 bytes
-rw-r--r--addons/skin.estuary/media/flags/audiocodec/ac3.pngbin1383 -> 0 bytes
-rw-r--r--addons/skin.estuary/media/flags/audiocodec/aif.pngbin771 -> 0 bytes
-rw-r--r--addons/skin.estuary/media/flags/audiocodec/aifc.pngbin1076 -> 0 bytes
-rw-r--r--addons/skin.estuary/media/flags/audiocodec/aiff.pngbin771 -> 0 bytes
-rw-r--r--addons/skin.estuary/media/flags/audiocodec/alac.pngbin1056 -> 0 bytes
-rw-r--r--addons/skin.estuary/media/flags/audiocodec/ape.pngbin990 -> 0 bytes
-rw-r--r--addons/skin.estuary/media/flags/audiocodec/avc.pngbin1158 -> 0 bytes
-rw-r--r--addons/skin.estuary/media/flags/audiocodec/cdda.pngbin1223 -> 0 bytes
-rw-r--r--addons/skin.estuary/media/flags/audiocodec/dca.pngbin981 -> 0 bytes
-rw-r--r--addons/skin.estuary/media/flags/audiocodec/dolbydigital.pngbin1383 -> 0 bytes
-rw-r--r--addons/skin.estuary/media/flags/audiocodec/dts.pngbin981 -> 0 bytes
-rw-r--r--addons/skin.estuary/media/flags/audiocodec/dtshd_hra.pngbin1549 -> 0 bytes
-rw-r--r--addons/skin.estuary/media/flags/audiocodec/dtshd_ma.pngbin1524 -> 0 bytes
-rw-r--r--addons/skin.estuary/media/flags/audiocodec/dtsma.pngbin1524 -> 0 bytes
-rw-r--r--addons/skin.estuary/media/flags/audiocodec/eac3.pngbin1383 -> 0 bytes
-rw-r--r--addons/skin.estuary/media/flags/audiocodec/flac.pngbin1121 -> 0 bytes
-rw-r--r--addons/skin.estuary/media/flags/audiocodec/mp1.pngbin936 -> 0 bytes
-rw-r--r--addons/skin.estuary/media/flags/audiocodec/mp2.pngbin1105 -> 0 bytes
-rw-r--r--addons/skin.estuary/media/flags/audiocodec/mp3.pngbin1138 -> 0 bytes
-rw-r--r--addons/skin.estuary/media/flags/audiocodec/mp3float.pngbin1138 -> 0 bytes
-rw-r--r--addons/skin.estuary/media/flags/audiocodec/ogg.pngbin1077 -> 0 bytes
-rw-r--r--addons/skin.estuary/media/flags/audiocodec/opus.pngbin1351 -> 0 bytes
-rw-r--r--addons/skin.estuary/media/flags/audiocodec/pcm.pngbin1187 -> 0 bytes
-rw-r--r--addons/skin.estuary/media/flags/audiocodec/pcm_bluray.pngbin1187 -> 0 bytes
-rw-r--r--addons/skin.estuary/media/flags/audiocodec/pcm_s16le.pngbin1187 -> 0 bytes
-rw-r--r--addons/skin.estuary/media/flags/audiocodec/pcm_s24le.pngbin1187 -> 0 bytes
-rw-r--r--addons/skin.estuary/media/flags/audiocodec/truehd.pngbin1579 -> 0 bytes
-rw-r--r--addons/skin.estuary/media/flags/audiocodec/vorbis.pngbin1692 -> 0 bytes
-rw-r--r--addons/skin.estuary/media/flags/audiocodec/wav.pngbin1321 -> 0 bytes
-rw-r--r--addons/skin.estuary/media/flags/audiocodec/wavpack.pngbin1480 -> 0 bytes
-rw-r--r--addons/skin.estuary/media/flags/audiocodec/wma.pngbin1378 -> 0 bytes
-rw-r--r--addons/skin.estuary/media/flags/audiocodec/wmapro.pngbin1378 -> 0 bytes
-rw-r--r--addons/skin.estuary/media/flags/audiocodec/wmav2.pngbin1378 -> 0 bytes
-rw-r--r--addons/skin.estuary/media/flags/rds/rds.pngbin1141 -> 0 bytes
-rw-r--r--addons/skin.estuary/media/flags/videocodec/av1.pngbin1025 -> 0 bytes
-rw-r--r--addons/skin.estuary/media/flags/videocodec/avc1.pngbin1262 -> 0 bytes
-rw-r--r--addons/skin.estuary/media/flags/videocodec/bluray.pngbin1313 -> 0 bytes
-rw-r--r--addons/skin.estuary/media/flags/videocodec/div3.pngbin1213 -> 0 bytes
-rw-r--r--addons/skin.estuary/media/flags/videocodec/divx.pngbin1213 -> 0 bytes
-rw-r--r--addons/skin.estuary/media/flags/videocodec/dvd.pngbin1002 -> 0 bytes
-rw-r--r--addons/skin.estuary/media/flags/videocodec/dx50.pngbin1213 -> 0 bytes
-rw-r--r--addons/skin.estuary/media/flags/videocodec/flv.pngbin744 -> 0 bytes
-rw-r--r--addons/skin.estuary/media/flags/videocodec/h264.pngbin1160 -> 0 bytes
-rw-r--r--addons/skin.estuary/media/flags/videocodec/hddvd.pngbin1100 -> 0 bytes
-rw-r--r--addons/skin.estuary/media/flags/videocodec/hdmv.pngbin1313 -> 0 bytes
-rw-r--r--addons/skin.estuary/media/flags/videocodec/hev1.pngbin1261 -> 0 bytes
-rw-r--r--addons/skin.estuary/media/flags/videocodec/hevc.pngbin1261 -> 0 bytes
-rw-r--r--addons/skin.estuary/media/flags/videocodec/hvc1.pngbin1261 -> 0 bytes
-rw-r--r--addons/skin.estuary/media/flags/videocodec/mp4v.pngbin1362 -> 0 bytes
-rw-r--r--addons/skin.estuary/media/flags/videocodec/mpeg1.pngbin1294 -> 0 bytes
-rw-r--r--addons/skin.estuary/media/flags/videocodec/mpeg1video.pngbin1294 -> 0 bytes
-rw-r--r--addons/skin.estuary/media/flags/videocodec/mpeg2.pngbin1429 -> 0 bytes
-rw-r--r--addons/skin.estuary/media/flags/videocodec/mpeg2video.pngbin1429 -> 0 bytes
-rw-r--r--addons/skin.estuary/media/flags/videocodec/mpeg4.pngbin1362 -> 0 bytes
-rw-r--r--addons/skin.estuary/media/flags/videocodec/theora.pngbin1515 -> 0 bytes
-rw-r--r--addons/skin.estuary/media/flags/videocodec/tv.pngbin708 -> 0 bytes
-rw-r--r--addons/skin.estuary/media/flags/videocodec/vc-1.pngbin1045 -> 0 bytes
-rw-r--r--addons/skin.estuary/media/flags/videocodec/vc1.pngbin1045 -> 0 bytes
-rw-r--r--addons/skin.estuary/media/flags/videocodec/vhs.pngbin1097 -> 0 bytes
-rw-r--r--addons/skin.estuary/media/flags/videocodec/vp8.pngbin1193 -> 0 bytes
-rw-r--r--addons/skin.estuary/media/flags/videocodec/vp9.pngbin1143 -> 0 bytes
-rw-r--r--addons/skin.estuary/media/flags/videocodec/wmv.pngbin1394 -> 0 bytes
-rw-r--r--addons/skin.estuary/media/flags/videocodec/wmv3.pngbin1394 -> 0 bytes
-rw-r--r--addons/skin.estuary/media/flags/videocodec/wvc1.pngbin1045 -> 0 bytes
-rw-r--r--addons/skin.estuary/media/flags/videocodec/xvid.pngbin1195 -> 0 bytes
-rw-r--r--addons/skin.estuary/media/flags/videohdr/dolbyvision.pngbin2759 -> 0 bytes
-rw-r--r--addons/skin.estuary/media/flags/videohdr/hdr10.pngbin2910 -> 0 bytes
-rw-r--r--addons/skin.estuary/media/flags/videohdr/hlg.pngbin2078 -> 0 bytes
-rw-r--r--addons/skin.estuary/media/flags/videoresolution/1080.pngbin1441 -> 0 bytes
-rw-r--r--addons/skin.estuary/media/flags/videoresolution/3D.pngbin938 -> 0 bytes
-rw-r--r--addons/skin.estuary/media/flags/videoresolution/480.pngbin1594 -> 0 bytes
-rw-r--r--addons/skin.estuary/media/flags/videoresolution/4K.pngbin814 -> 0 bytes
-rw-r--r--addons/skin.estuary/media/flags/videoresolution/540.pngbin1541 -> 0 bytes
-rw-r--r--addons/skin.estuary/media/flags/videoresolution/576.pngbin1451 -> 0 bytes
-rw-r--r--addons/skin.estuary/media/flags/videoresolution/720.pngbin1317 -> 0 bytes
-rw-r--r--addons/skin.estuary/media/flags/videoresolution/8K.pngbin1277 -> 0 bytes
-rw-r--r--addons/skin.estuary/xml/DialogAddonSettings.xml38
-rw-r--r--addons/skin.estuary/xml/DialogPVRGuideSearch.xml36
-rw-r--r--addons/skin.estuary/xml/DialogPVRInfo.xml4
-rw-r--r--addons/skin.estuary/xml/DialogSeekBar.xml113
-rw-r--r--addons/skin.estuary/xml/DialogVideoManager.xml1
-rw-r--r--addons/skin.estuary/xml/GameOSD.xml1
-rw-r--r--addons/skin.estuary/xml/Home.xml5
-rw-r--r--addons/skin.estuary/xml/Includes.xml290
-rw-r--r--addons/skin.estuary/xml/Includes_Animations.xml16
-rw-r--r--addons/skin.estuary/xml/Includes_Buttons.xml30
-rw-r--r--addons/skin.estuary/xml/Includes_DialogSelect.xml9
-rw-r--r--addons/skin.estuary/xml/Includes_PVR.xml34
-rw-r--r--addons/skin.estuary/xml/MusicOSD.xml8
-rw-r--r--addons/skin.estuary/xml/MyPVRProviders.xml78
-rw-r--r--addons/skin.estuary/xml/MyPVRRecordings.xml1
-rw-r--r--addons/skin.estuary/xml/MyPVRTimers.xml1
-rw-r--r--addons/skin.estuary/xml/SettingsCategory.xml2
-rw-r--r--addons/skin.estuary/xml/Variables.xml217
-rw-r--r--addons/skin.estuary/xml/VideoOSD.xml11
-rw-r--r--cmake/modules/FindCurl.cmake4
-rw-r--r--cmake/modules/FindPython.cmake2
-rw-r--r--cmake/modules/buildtools/FindWaylandPPScanner.cmake3
-rw-r--r--cmake/scripts/common/HandleDepends.cmake2
-rw-r--r--cmake/scripts/webos/Install.cmake3
-rw-r--r--cmake/scripts/windows/ArchSetup.cmake8
-rw-r--r--cmake/scripts/windowsstore/ArchSetup.cmake10
-rw-r--r--system/keymaps/osmc/osmc_remote.xml4
-rw-r--r--system/keymaps/osmcv3/osmcv3_remote.xml584
-rw-r--r--system/peripherals.xml5
-rwxr-xr-xsystem/settings/settings.xml42
-rw-r--r--system/shaders/GLES/3.1/gles310_yuv2rgb.vert28
-rw-r--r--system/shaders/GLES/3.1/gles310_yuv2rgb_filter.frag129
-rw-r--r--tools/android/packaging/Makefile.in2
-rw-r--r--tools/android/packaging/xbmc/res/xml/searchable.xml16
-rw-r--r--tools/depends/Makefile.include.in2
-rw-r--r--tools/depends/configure.ac1
-rw-r--r--tools/depends/native/Makefile4
-rw-r--r--tools/depends/native/Mako/MAKO-VERSION4
-rw-r--r--tools/depends/native/Mako/Makefile10
-rw-r--r--tools/depends/native/MarkupSafe/01-all-GH399-removedistutils.patch48
-rw-r--r--tools/depends/native/MarkupSafe/MARKUPSAFE-VERSION4
-rw-r--r--tools/depends/native/MarkupSafe/Makefile12
-rw-r--r--tools/depends/native/TexturePacker/src/TexturePacker.cpp167
-rw-r--r--tools/depends/native/TexturePacker/src/decoder/IDecoder.h7
-rw-r--r--tools/depends/native/expat/EXPAT-VERSION4
-rw-r--r--tools/depends/native/meson/MESON-VERSION4
-rw-r--r--tools/depends/native/openssl/OPENSSL-VERSION4
-rw-r--r--tools/depends/native/python3/01-distutil-flags.patch12
-rw-r--r--tools/depends/native/python3/Makefile7
-rw-r--r--tools/depends/native/python3/PYTHON3-VERSION4
-rw-r--r--tools/depends/native/pythonmodule-setuptools/01-distutils-flag.patch15
-rw-r--r--tools/depends/native/pythonmodule-setuptools/Makefile23
-rw-r--r--tools/depends/native/pythonmodule-setuptools/PYTHONMODULE-SETUPTOOLS-VERSION4
-rw-r--r--tools/depends/target/curl/01-win-nghttp2-add-name.patch11
-rw-r--r--tools/depends/target/curl/CURL-VERSION4
-rw-r--r--tools/depends/target/expat/EXPAT-VERSION4
-rw-r--r--tools/depends/target/mesa/01-all-py312-setuptools.patch10
-rw-r--r--tools/depends/target/mesa/Makefile12
-rw-r--r--tools/depends/target/openssl/OPENSSL-VERSION4
-rw-r--r--tools/depends/target/python3/01-py312-cpython118618-1.patch369
-rw-r--r--tools/depends/target/python3/01-py312-cpython118618-2.patch71
-rw-r--r--tools/depends/target/python3/02-android-cpython114875.patch36
-rw-r--r--tools/depends/target/python3/10-linux-modules.patch11
-rw-r--r--tools/depends/target/python3/10-osx-modules.patch11
-rw-r--r--tools/depends/target/python3/Makefile78
-rw-r--r--tools/depends/target/python3/PYTHON3-VERSION4
-rw-r--r--tools/depends/target/python3/apple.patch9
-rw-r--r--tools/depends/target/python3/crosscompile.patch64
-rw-r--r--tools/depends/target/python3/darwin_embedded.patch11
-rw-r--r--tools/depends/target/python3/modules.setup304
-rw-r--r--tools/depends/target/pythonmodule-setuptools/PYTHONMODULE-SETUPTOOLS-VERSION4
-rw-r--r--tools/depends/target/samba-gplv3/08-py312-distutils.patch11
-rw-r--r--tools/depends/target/samba-gplv3/Makefile2
-rw-r--r--xbmc/ContextMenuItem.h1
-rw-r--r--xbmc/FileItem.cpp361
-rw-r--r--xbmc/FileItem.h43
-rw-r--r--xbmc/FileItemList.cpp3
-rw-r--r--xbmc/GUIInfoManager.cpp107
-rw-r--r--xbmc/ThumbLoader.cpp2
-rw-r--r--xbmc/URL.cpp5
-rw-r--r--xbmc/Util.cpp2
-rw-r--r--xbmc/addons/AddonManager.h1
-rw-r--r--xbmc/addons/IAddon.h1
-rw-r--r--xbmc/addons/Repository.cpp2
-rw-r--r--xbmc/addons/binary-addons/AddonInstanceHandler.cpp5
-rw-r--r--xbmc/addons/binary-addons/AddonInstanceHandler.h1
-rw-r--r--xbmc/addons/gui/GUIDialogAddonSettings.cpp30
-rw-r--r--xbmc/addons/kodi-dev-kit/include/kodi/AddonBase.h23
-rw-r--r--xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/PVR.h29
-rw-r--r--xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/pvr/ChannelGroups.h14
-rw-r--r--xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/pvr/Channels.h60
-rw-r--r--xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/pvr/EPG.h107
-rw-r--r--xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/pvr/General.h932
-rw-r--r--xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/pvr/Providers.h19
-rw-r--r--xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/pvr/Recordings.h135
-rw-r--r--xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/pvr/Timers.h169
-rw-r--r--xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr.h2
-rw-r--r--xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_defines.h11
-rw-r--r--xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_epg.h4
-rw-r--r--xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_general.h151
-rw-r--r--xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_recordings.h12
-rw-r--r--xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_timers.h19
-rw-r--r--xbmc/addons/kodi-dev-kit/include/kodi/versions.h4
-rw-r--r--xbmc/cdrip/CDDARipJob.h2
-rw-r--r--xbmc/cores/AudioEngine/CMakeLists.txt14
-rw-r--r--xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAE.cpp18
-rw-r--r--xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAE.h1
-rw-r--r--xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAEBuffer.cpp19
-rw-r--r--xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAEBuffer.h9
-rw-r--r--xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAEResampleFFMPEG.cpp14
-rw-r--r--xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAEResampleFFMPEG.h11
-rw-r--r--xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAESettings.cpp1
-rw-r--r--xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAESink.cpp4
-rw-r--r--xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAEStream.cpp20
-rw-r--r--xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAEStream.h8
-rw-r--r--xbmc/cores/AudioEngine/Interfaces/AEResample.h11
-rw-r--r--xbmc/cores/AudioEngine/Sinks/AESinkAUDIOTRACK.cpp18
-rw-r--r--xbmc/cores/AudioEngine/Sinks/AESinkWASAPI.cpp58
-rw-r--r--xbmc/cores/AudioEngine/Sinks/AESinkXAudio.cpp627
-rw-r--r--xbmc/cores/AudioEngine/Sinks/AESinkXAudio.h81
-rw-r--r--xbmc/cores/AudioEngine/Sinks/windows/AESinkFactoryWin.h6
-rw-r--r--xbmc/cores/AudioEngine/Sinks/windows/AESinkFactoryWin10.cpp95
-rw-r--r--xbmc/cores/AudioEngine/Sinks/windows/AESinkFactoryWin32.cpp14
-rw-r--r--xbmc/cores/AudioEngine/Sinks/windows/AESinkFactoryWinRT.cpp124
-rw-r--r--xbmc/cores/AudioEngine/Utils/AEUtil.cpp22
-rw-r--r--xbmc/cores/AudioEngine/Utils/AEUtil.h4
-rw-r--r--xbmc/cores/RetroPlayer/streams/RetroPlayerVideo.h2
-rw-r--r--xbmc/cores/VideoPlayer/Buffers/VideoBufferDRMPRIME.h2
-rw-r--r--xbmc/cores/VideoPlayer/DVDCodecs/Video/DVDVideoCodec.cpp2
-rw-r--r--xbmc/cores/VideoPlayer/DVDCodecs/Video/DVDVideoCodec.h2
-rw-r--r--xbmc/cores/VideoPlayer/DVDCodecs/Video/DVDVideoCodecDRMPRIME.cpp2
-rw-r--r--xbmc/cores/VideoPlayer/DVDInputStreams/dvdnav/ifo_types.h3
-rw-r--r--xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/RendererDRMPRIME.cpp2
-rw-r--r--xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/VideoLayerBridgeDRMPRIME.cpp11
-rw-r--r--xbmc/cores/VideoPlayer/VideoRenderers/LinuxRendererGLES.cpp34
-rw-r--r--xbmc/cores/VideoPlayer/VideoRenderers/VideoShaders/ConversionMatrix.h1
-rw-r--r--xbmc/cores/VideoPlayer/VideoRenderers/VideoShaders/YUV2RGBShaderGLES.cpp71
-rw-r--r--xbmc/cores/VideoPlayer/VideoRenderers/VideoShaders/YUV2RGBShaderGLES.h22
-rw-r--r--xbmc/cores/VideoPlayer/VideoRenderers/VideoShaders/test/TestConversionMatrix.cpp1
-rw-r--r--xbmc/cores/paplayer/VideoPlayerCodec.cpp9
-rw-r--r--xbmc/dialogs/GUIDialogColorPicker.cpp2
-rw-r--r--xbmc/dialogs/GUIDialogColorPicker.h2
-rw-r--r--xbmc/dialogs/GUIDialogContextMenu.cpp3
-rw-r--r--xbmc/dialogs/GUIDialogContextMenu.h2
-rw-r--r--xbmc/dialogs/GUIDialogMediaFilter.cpp4
-rw-r--r--xbmc/dialogs/GUIDialogMediaSource.cpp14
-rw-r--r--xbmc/events/EventLog.cpp2
-rw-r--r--xbmc/events/EventLog.h2
-rw-r--r--xbmc/favourites/GUIViewStateFavourites.h2
-rw-r--r--xbmc/favourites/GUIWindowFavourites.cpp8
-rw-r--r--xbmc/filesystem/AudioBookFileDirectory.cpp20
-rw-r--r--xbmc/filesystem/CurlFile.cpp10
-rw-r--r--xbmc/filesystem/DirectoryFactory.cpp9
-rw-r--r--xbmc/filesystem/PlaylistFileDirectory.cpp10
-rw-r--r--xbmc/filesystem/SmartPlaylistDirectory.cpp18
-rw-r--r--xbmc/filesystem/XbtFile.cpp37
-rw-r--r--xbmc/filesystem/XbtFile.h4
-rw-r--r--xbmc/filesystem/ZeroconfDirectory.cpp6
-rw-r--r--xbmc/games/addons/GameClientProperties.cpp147
-rw-r--r--xbmc/games/addons/GameClientProperties.h3
-rw-r--r--xbmc/games/controllers/dialogs/ControllerInstaller.cpp1
-rw-r--r--xbmc/guilib/FFmpegImage.h2
-rw-r--r--xbmc/guilib/GUITextLayout.h3
-rw-r--r--xbmc/guilib/GUIToggleButtonControl.cpp1
-rw-r--r--xbmc/guilib/GUIWindowManager.cpp8
-rw-r--r--xbmc/guilib/Texture.cpp80
-rw-r--r--xbmc/guilib/Texture.h28
-rw-r--r--xbmc/guilib/TextureBase.cpp74
-rw-r--r--xbmc/guilib/TextureBase.h12
-rw-r--r--xbmc/guilib/TextureBundleXBT.cpp14
-rw-r--r--xbmc/guilib/TextureGL.cpp203
-rw-r--r--xbmc/guilib/TextureGL.h29
-rw-r--r--xbmc/guilib/TextureGLES.cpp368
-rw-r--r--xbmc/guilib/TextureGLES.h22
-rw-r--r--xbmc/guilib/WindowIDs.h4
-rw-r--r--xbmc/guilib/XBTF.cpp24
-rw-r--r--xbmc/guilib/XBTF.h12
-rw-r--r--xbmc/guilib/XBTFReader.cpp17
-rw-r--r--xbmc/guilib/guiinfo/AddonsGUIInfo.cpp1
-rw-r--r--xbmc/guilib/guiinfo/GUIControlsGUIInfo.cpp22
-rw-r--r--xbmc/guilib/guiinfo/GUIInfo.h36
-rw-r--r--xbmc/guilib/guiinfo/GUIInfoLabel.h6
-rw-r--r--xbmc/guilib/guiinfo/GUIInfoLabels.h14
-rw-r--r--xbmc/guilib/guiinfo/VisualisationGUIInfo.cpp15
-rw-r--r--xbmc/guilib/listproviders/DirectoryProvider.cpp2
-rw-r--r--xbmc/input/WindowTranslator.cpp2
-rw-r--r--xbmc/input/keymaps/remote/IRTranslator.h1
-rw-r--r--xbmc/interfaces/json-rpc/AudioLibrary.cpp56
-rw-r--r--xbmc/interfaces/json-rpc/AudioLibrary.h10
-rw-r--r--xbmc/interfaces/json-rpc/JSONServiceDescription.cpp2
-rw-r--r--xbmc/interfaces/json-rpc/schema/methods.json28
-rw-r--r--xbmc/interfaces/json-rpc/schema/version.txt2
-rw-r--r--xbmc/messaging/ThreadMessage.h1
-rw-r--r--xbmc/music/MusicDatabase.cpp10
-rw-r--r--xbmc/network/cddb.cpp9
-rw-r--r--xbmc/pictures/GUIWindowSlideShow.cpp18
-rw-r--r--xbmc/pictures/GUIWindowSlideShow.h11
-rw-r--r--xbmc/pictures/PictureThumbLoader.cpp5
-rw-r--r--xbmc/platform/android/PlatformAndroid.cpp3
-rw-r--r--xbmc/platform/android/activity/XBMCApp.cpp37
-rw-r--r--xbmc/platform/android/activity/XBMCApp.h7
-rw-r--r--xbmc/platform/android/storage/AndroidStorageProvider.cpp45
-rw-r--r--xbmc/platform/android/storage/AndroidStorageProvider.h8
-rw-r--r--xbmc/platform/win10/AsyncHelpers.h4
-rw-r--r--xbmc/playlists/PlayListFactory.cpp14
-rw-r--r--xbmc/playlists/PlayListFactory.h1
-rw-r--r--xbmc/pvr/CMakeLists.txt3
-rw-r--r--xbmc/pvr/PVRConstants.h18
-rw-r--r--xbmc/pvr/PVRContextMenus.cpp108
-rw-r--r--xbmc/pvr/PVRDatabase.cpp186
-rw-r--r--xbmc/pvr/PVRDatabase.h21
-rw-r--r--xbmc/pvr/PVRDescrambleInfo.h2
-rw-r--r--xbmc/pvr/PVRManager.cpp14
-rw-r--r--xbmc/pvr/PVRPathUtils.cpp70
-rw-r--r--xbmc/pvr/PVRPathUtils.h40
-rw-r--r--xbmc/pvr/PVRPlaybackState.cpp45
-rw-r--r--xbmc/pvr/PVRPlaybackState.h12
-rw-r--r--xbmc/pvr/PVRSignalStatus.h2
-rw-r--r--xbmc/pvr/PVRStreamProperties.cpp8
-rw-r--r--xbmc/pvr/PVRStreamProperties.h6
-rw-r--r--xbmc/pvr/addons/PVRClient.cpp1239
-rw-r--r--xbmc/pvr/addons/PVRClient.h32
-rw-r--r--xbmc/pvr/addons/PVRClientCapabilities.cpp8
-rw-r--r--xbmc/pvr/addons/PVRClientUID.cpp59
-rw-r--r--xbmc/pvr/addons/PVRClientUID.h8
-rw-r--r--xbmc/pvr/addons/PVRClients.cpp57
-rw-r--r--xbmc/pvr/addons/PVRClients.h2
-rw-r--r--xbmc/pvr/channels/PVRChannel.h8
-rw-r--r--xbmc/pvr/channels/PVRChannelGroup.cpp41
-rw-r--r--xbmc/pvr/channels/PVRChannelGroup.h22
-rw-r--r--xbmc/pvr/channels/PVRChannelGroupMember.cpp10
-rw-r--r--xbmc/pvr/channels/PVRChannelGroupMember.h11
-rw-r--r--xbmc/pvr/channels/PVRChannelGroups.cpp2
-rw-r--r--xbmc/pvr/channels/PVRChannelGroupsContainer.cpp20
-rw-r--r--xbmc/pvr/channels/PVRChannelGroupsContainer.h18
-rw-r--r--xbmc/pvr/channels/PVRChannelsPath.cpp7
-rw-r--r--xbmc/pvr/channels/PVRChannelsPath.h3
-rw-r--r--xbmc/pvr/channels/PVRRadioRDSInfoTag.cpp4
-rw-r--r--xbmc/pvr/dialogs/GUIDialogPVRChannelManager.cpp2
-rw-r--r--xbmc/pvr/dialogs/GUIDialogPVRGroupManager.cpp8
-rw-r--r--xbmc/pvr/dialogs/GUIDialogPVRGuideSearch.cpp18
-rw-r--r--xbmc/pvr/dialogs/GUIDialogPVRRadioRDSInfo.cpp24
-rw-r--r--xbmc/pvr/dialogs/GUIDialogPVRTimerSettings.cpp158
-rw-r--r--xbmc/pvr/dialogs/GUIDialogPVRTimerSettings.h37
-rw-r--r--xbmc/pvr/epg/CMakeLists.txt2
-rw-r--r--xbmc/pvr/epg/Epg.cpp6
-rw-r--r--xbmc/pvr/epg/EpgChannelData.h4
-rw-r--r--xbmc/pvr/epg/EpgContainer.cpp10
-rw-r--r--xbmc/pvr/epg/EpgContainer.h4
-rw-r--r--xbmc/pvr/epg/EpgDatabase.cpp175
-rw-r--r--xbmc/pvr/epg/EpgDatabase.h2
-rw-r--r--xbmc/pvr/epg/EpgInfoTag.cpp74
-rw-r--r--xbmc/pvr/epg/EpgInfoTag.h25
-rw-r--r--xbmc/pvr/epg/EpgSearch.cpp51
-rw-r--r--xbmc/pvr/epg/EpgSearch.h48
-rw-r--r--xbmc/pvr/epg/EpgSearchData.h8
-rw-r--r--xbmc/pvr/epg/EpgSearchFilter.cpp65
-rw-r--r--xbmc/pvr/epg/EpgSearchFilter.h238
-rw-r--r--xbmc/pvr/filesystem/PVRGUIDirectory.cpp292
-rw-r--r--xbmc/pvr/filesystem/PVRGUIDirectory.h16
-rw-r--r--xbmc/pvr/guilib/PVRGUIActionsChannels.cpp5
-rw-r--r--xbmc/pvr/guilib/PVRGUIActionsEPG.cpp67
-rw-r--r--xbmc/pvr/guilib/PVRGUIActionsEPG.h14
-rw-r--r--xbmc/pvr/guilib/PVRGUIActionsPlayback.cpp24
-rw-r--r--xbmc/pvr/guilib/PVRGUIActionsRecordings.cpp99
-rw-r--r--xbmc/pvr/guilib/PVRGUIActionsRecordings.h27
-rw-r--r--xbmc/pvr/guilib/PVRGUIChannelIconUpdater.cpp3
-rw-r--r--xbmc/pvr/guilib/PVRGUIChannelNavigator.cpp3
-rw-r--r--xbmc/pvr/guilib/PVRGUIProgressHandler.cpp4
-rw-r--r--xbmc/pvr/guilib/PVRGUIProgressHandler.h2
-rw-r--r--xbmc/pvr/guilib/guiinfo/PVRGUIInfo.cpp95
-rw-r--r--xbmc/pvr/guilib/guiinfo/PVRGUITimesInfo.cpp38
-rw-r--r--xbmc/pvr/guilib/guiinfo/PVRGUITimesInfo.h2
-rw-r--r--xbmc/pvr/providers/CMakeLists.txt6
-rw-r--r--xbmc/pvr/providers/PVRProvider.cpp8
-rw-r--r--xbmc/pvr/providers/PVRProvider.h6
-rw-r--r--xbmc/pvr/providers/PVRProviders.cpp28
-rw-r--r--xbmc/pvr/providers/PVRProviders.h18
-rw-r--r--xbmc/pvr/providers/PVRProvidersPath.cpp110
-rw-r--r--xbmc/pvr/providers/PVRProvidersPath.h63
-rw-r--r--xbmc/pvr/recordings/PVRRecording.cpp100
-rw-r--r--xbmc/pvr/recordings/PVRRecording.h40
-rw-r--r--xbmc/pvr/recordings/PVRRecordings.cpp31
-rw-r--r--xbmc/pvr/recordings/PVRRecordings.h18
-rw-r--r--xbmc/pvr/settings/CMakeLists.txt17
-rw-r--r--xbmc/pvr/settings/IPVRSettingsContainer.h47
-rw-r--r--xbmc/pvr/settings/PVRCustomTimerSettings.cpp256
-rw-r--r--xbmc/pvr/settings/PVRCustomTimerSettings.h77
-rw-r--r--xbmc/pvr/settings/PVRIntSettingDefinition.cpp33
-rw-r--r--xbmc/pvr/settings/PVRIntSettingDefinition.h42
-rw-r--r--xbmc/pvr/settings/PVRIntSettingValues.cpp70
-rw-r--r--xbmc/pvr/settings/PVRIntSettingValues.h47
-rw-r--r--xbmc/pvr/settings/PVRStringSettingDefinition.cpp30
-rw-r--r--xbmc/pvr/settings/PVRStringSettingDefinition.h39
-rw-r--r--xbmc/pvr/settings/PVRStringSettingValues.cpp51
-rw-r--r--xbmc/pvr/settings/PVRStringSettingValues.h41
-rw-r--r--xbmc/pvr/settings/PVRTimerSettingDefinition.cpp103
-rw-r--r--xbmc/pvr/settings/PVRTimerSettingDefinition.h68
-rw-r--r--xbmc/pvr/timers/PVRTimerInfoTag.cpp28
-rw-r--r--xbmc/pvr/timers/PVRTimerInfoTag.h39
-rw-r--r--xbmc/pvr/timers/PVRTimerRuleMatcher.cpp4
-rw-r--r--xbmc/pvr/timers/PVRTimerType.cpp301
-rw-r--r--xbmc/pvr/timers/PVRTimerType.h873
-rw-r--r--xbmc/pvr/timers/PVRTimers.cpp3
-rw-r--r--xbmc/pvr/timers/PVRTimersPath.cpp3
-rw-r--r--xbmc/pvr/timers/PVRTimersPath.h4
-rw-r--r--xbmc/pvr/windows/CMakeLists.txt2
-rw-r--r--xbmc/pvr/windows/GUIViewStatePVR.cpp35
-rw-r--r--xbmc/pvr/windows/GUIViewStatePVR.h10
-rw-r--r--xbmc/pvr/windows/GUIWindowPVRBase.cpp13
-rw-r--r--xbmc/pvr/windows/GUIWindowPVRBase.h4
-rw-r--r--xbmc/pvr/windows/GUIWindowPVRChannels.cpp31
-rw-r--r--xbmc/pvr/windows/GUIWindowPVRChannels.h9
-rw-r--r--xbmc/pvr/windows/GUIWindowPVRGuide.cpp6
-rw-r--r--xbmc/pvr/windows/GUIWindowPVRProviders.cpp191
-rw-r--r--xbmc/pvr/windows/GUIWindowPVRProviders.h55
-rw-r--r--xbmc/pvr/windows/GUIWindowPVRRecordings.cpp36
-rw-r--r--xbmc/pvr/windows/GUIWindowPVRRecordings.h7
-rw-r--r--xbmc/pvr/windows/GUIWindowPVRSearch.cpp28
-rw-r--r--xbmc/rendering/gles/RenderSystemGLES.cpp19
-rw-r--r--xbmc/rendering/gles/RenderSystemGLES.h4
-rw-r--r--xbmc/settings/AdvancedSettings.h1
-rw-r--r--xbmc/settings/Settings.h2
-rw-r--r--xbmc/storage/DetectDVDType.cpp7
-rw-r--r--xbmc/storage/DetectDVDType.h1
-rw-r--r--xbmc/test/TestFileItem.cpp57
-rw-r--r--xbmc/utils/Archive.h1
-rw-r--r--xbmc/utils/ArtUtils.cpp215
-rw-r--r--xbmc/utils/ArtUtils.h31
-rw-r--r--xbmc/utils/CPUInfo.h2
-rw-r--r--xbmc/utils/CharArrayParser.cpp10
-rw-r--r--xbmc/utils/SaveFileStateJob.cpp8
-rw-r--r--xbmc/utils/URIUtils.cpp13
-rw-r--r--xbmc/utils/URIUtils.h1
-rw-r--r--xbmc/utils/test/TestArtUtils.cpp206
-rw-r--r--xbmc/utils/test/TestURIUtils.cpp13
-rw-r--r--xbmc/video/ContextMenus.cpp2
-rw-r--r--xbmc/video/VideoDatabase.cpp96
-rw-r--r--xbmc/video/VideoInfoScanner.cpp17
-rw-r--r--xbmc/video/VideoUtils.cpp103
-rw-r--r--xbmc/video/VideoUtils.h7
-rw-r--r--xbmc/video/dialogs/GUIDialogVideoInfo.cpp3
-rw-r--r--xbmc/video/guilib/VideoAction.h2
-rw-r--r--xbmc/video/guilib/VideoGUIUtils.cpp13
-rw-r--r--xbmc/video/guilib/VideoSelectActionProcessor.cpp38
-rw-r--r--xbmc/video/guilib/VideoSelectActionProcessor.h3
-rw-r--r--xbmc/video/jobs/VideoLibraryMarkWatchedJob.cpp6
-rw-r--r--xbmc/video/test/TestVideoUtils.cpp62
-rw-r--r--xbmc/video/windows/GUIWindowVideoBase.cpp2
-rw-r--r--xbmc/video/windows/GUIWindowVideoNav.cpp20
-rw-r--r--xbmc/view/GUIViewState.cpp6
-rw-r--r--xbmc/windowing/gbm/drm/DRMObject.cpp30
-rw-r--r--xbmc/windowing/gbm/drm/DRMObject.h3
-rw-r--r--xbmc/windowing/gbm/drm/DRMUtils.cpp169
-rw-r--r--xbmc/windowing/gbm/drm/DRMUtils.h5
-rw-r--r--xbmc/windowing/windows/WinSystemWin32.cpp2
-rw-r--r--xbmc/windows/GUIMediaWindow.cpp23
-rw-r--r--xbmc/windows/GUIWindowScreensaverDim.cpp6
469 files changed, 12189 insertions, 4175 deletions
diff --git a/addons/resource.language.en_gb/resources/strings.po b/addons/resource.language.en_gb/resources/strings.po
index d0064af1b9..46d1d6174f 100644
--- a/addons/resource.language.en_gb/resources/strings.po
+++ b/addons/resource.language.en_gb/resources/strings.po
@@ -3704,6 +3704,7 @@ msgid "Any channel"
msgstr ""
#. Label of "Start any time" radio button in PVR timer settings dialog
+#: addons/skin.estuary/xml/DialogPVRGuideSearch.xml
#: xbmc/pvr/dialogs/GUIDialogPVRTimerSettings.cpp
msgctxt "#810"
msgid "Start any time"
@@ -3750,6 +3751,7 @@ msgid "Record only new episodes"
msgstr ""
#. Label of "End any time" radio button in PVR timer settings dialog
+#: addons/skin.estuary/xml/DialogPVRGuideSearch.xml
#: xbmc/pvr/dialogs/GUIDialogPVRTimerSettings.cpp
msgctxt "#817"
msgid "End any time"
@@ -4004,7 +4006,50 @@ msgctxt "#859"
msgid "{0:s} [All channels]"
msgstr ""
-#empty strings from id 860 to 996
+#. Label for setting 'Delete after watching' and delete confirmation dialog box.
+#: system/settings/settings.xml
+#: xbmc/pvr/guilib/PVRGUIActionsRecordings..cpp
+msgctxt "#860"
+msgid "Delete after watching"
+msgstr ""
+
+#. Help text for setting 'Delete after watching'.
+#: system/settings/settings.xml
+msgctxt "#861"
+msgid "Configure whether recordings should be deleted after watching."
+msgstr ""
+
+#. Value for setting 'Delete after watching'. Keep after watching.
+#: system/settings/settings.xml
+msgctxt "#862"
+msgid "No"
+msgstr ""
+
+#. Value for setting 'Delete after watching'. Confirm delete.
+#: system/settings/settings.xml
+msgctxt "#863"
+msgid "Ask"
+msgstr ""
+
+#. Value for setting 'Delete after watching'. Delete after watching.
+#: system/settings/settings.xml
+msgctxt "#864"
+msgid "Yes"
+msgstr ""
+
+#. Text for delete after watch confirmation dialog box.
+#: xbmc/pvr/guilib/PVRGUIActionsRecordings..cpp
+msgctxt "#865"
+msgid "Do you want to delete this recording?"
+msgstr ""
+
+#. Text for auto delete after watch toast message.
+#: xbmc/pvr/guilib/PVRGUIActionsRecordings..cpp
+msgctxt "#866"
+msgid "Recording deleted: '{0:s}'"
+msgstr ""
+
+#empty strings from id 867 to 996
#: xbmc/windows/GUIMediaWindow.cpp
msgctxt "#997"
@@ -11354,23 +11399,27 @@ msgstr ""
#. Label for a select option or list item, representing an icon graphic (like a TV channel icon)
#: xbmc/pvr/dialogs/GUIDialogPVRChannelManager.cpp
+#: xbmc/pvr/guilib/PVRGUIActionsEPG.cpp
msgctxt "#19282"
msgid "Current icon"
msgstr ""
#. Label for a select option or list item, representing an icon graphic (like a TV channel icon)
#: xbmc/pvr/dialogs/GUIDialogPVRChannelManager.cpp
+#: xbmc/pvr/guilib/PVRGUIActionsEPG.cpp
msgctxt "#19283"
msgid "No icon"
msgstr ""
-#. used by several skins
+#. used by several skins and PVR
+#: xbmc/pvr/PVRContextMenus.cpp
msgctxt "#19284"
msgid "Choose icon"
msgstr ""
#. Label for a select/menu option to select an icon graphic
#: xbmc/pvr/dialogs/GUIDialogPVRChannelManager.cpp
+#: xbmc/pvr/guilib/PVRGUIActionsEPG.cpp
msgctxt "#19285"
msgid "Browse for icon"
msgstr ""
@@ -11675,7 +11724,10 @@ msgctxt "#19333"
msgid "Switched to channel for auto-closed PVR reminder for channel '{0:s}' at '{1:s}'"
msgstr ""
-#. label for PVR backend number of channel providers in system information's PVR section
+#. generic label for pvr providers used in different places
+#: addons/skin.estuary/xml/DialogPVRInfo.xml
+#: addons/skin.estuary/xml/Includes_PVR.xml
+#: xbmc/pvr/filesystem/PVRGUIDirectory.cpp
#: xbmc/pvr/guilib/PVRGUIActionsDatabase.cpp
#: xbmc/windows/GUIWindowSystemInfo.cpp
msgctxt "#19334"
@@ -11760,7 +11812,7 @@ msgctxt "#19347"
msgid "None of the active PVR clients provide client-specific settings."
msgstr ""
-#. label for 'by provider' sort method
+#. generic label for a pvr provider used in different places
#: xbmc/pvr/windows/GUIViewStatePVR.cpp
#: xbmc/utils/SortUtils.cpp
msgctxt "#19348"
@@ -11803,7 +11855,19 @@ msgctxt "#19354"
msgid "Play only this programme"
msgstr ""
-#empty strings from id 19355 to 19498
+#. Label of a context menu entry to create a copy of an EPG saved saerch
+#: xbmc/pvr/PVRContextMenus.cpp
+msgctxt "#19355"
+msgid "Duplicate"
+msgstr ""
+
+#. Initial title of a duplicated EPG saved search.
+#: xbmc/pvr/PVRContextMenus.cpp
+msgctxt "#19356"
+msgid "Copy of '{0:s}'"
+msgstr ""
+
+#empty strings from id 19357 to 19498
#. label for epg genre value
#: xbmc/pvr/epg/Epg.cpp
@@ -15675,7 +15739,6 @@ msgstr ""
#. Label to denote that there is something 'more'
#. xbmc/favourites/ContextMenus.h
#: xbmc/guilib/listproviders/DirectoryProvider.cpp
-#: xbmc/video/guilib/VideoSelectActionProcessor.cpp
#: system/settings/settings.xml
msgctxt "#22082"
msgid "More..."
@@ -17915,8 +17978,39 @@ msgctxt "#34113"
msgid "To keep certain AVRs powered we send an inaudible random noise signal. You can disable this setting if you are using headphone or analog output."
msgstr ""
+#. Indicates if Audio Engine should include lfe when downmixing
+#: system/settings/settings.xml
+msgctxt "#34114"
+msgid "Include LFE when downmixing"
+msgstr ""
+
+#. Description of setting with label #34114 "Include LFE when downmixing"
+#: system/settings/settings.xml
+msgctxt "#34115"
+msgid "If enabled, this setting will include lfe channel into the mixing when there is no dedicated LFE output channel available. This only makes sense for full range speakers."
+msgstr ""
+
+#. Description of setting with label #34114 "Include LFE when downmixing"
+#: system/settings/settings.xml
+msgctxt "#34116"
+msgid "Off"
+msgstr ""
+
+#. Description of setting with label #34114 "Include LFE when downmixing"
+#: system/settings/settings.xml
+msgctxt "#34117"
+msgid "50%"
+msgstr ""
+
+#. Description of setting with label #34114 "Include LFE when downmixing"
+#: system/settings/settings.xml
+msgctxt "#34118"
+msgid "100%"
+msgstr ""
+
+
#empty strings from id 34114 to 34119
-#34114-34119 reserved for future use
+#34119 reserved for future use
#: system/settings/settings.xml
msgctxt "#34120"
diff --git a/addons/skin.estuary/extras/backgrounds/pattern1.png b/addons/skin.estuary/extras/backgrounds/pattern1.png
index 5acfff911e..fb17dae43c 100644
--- a/addons/skin.estuary/extras/backgrounds/pattern1.png
+++ b/addons/skin.estuary/extras/backgrounds/pattern1.png
Binary files differ
diff --git a/addons/skin.estuary/extras/backgrounds/pattern2.png b/addons/skin.estuary/extras/backgrounds/pattern2.png
index c6af819c11..b6c4b61215 100644
--- a/addons/skin.estuary/extras/backgrounds/pattern2.png
+++ b/addons/skin.estuary/extras/backgrounds/pattern2.png
Binary files differ
diff --git a/addons/skin.estuary/extras/backgrounds/pattern3.png b/addons/skin.estuary/extras/backgrounds/pattern3.png
index 75588b9d95..2ac483467b 100644
--- a/addons/skin.estuary/extras/backgrounds/pattern3.png
+++ b/addons/skin.estuary/extras/backgrounds/pattern3.png
Binary files differ
diff --git a/addons/skin.estuary/extras/backgrounds/pattern4.png b/addons/skin.estuary/extras/backgrounds/pattern4.png
index 3d2737144c..8e2ba5f100 100644
--- a/addons/skin.estuary/extras/backgrounds/pattern4.png
+++ b/addons/skin.estuary/extras/backgrounds/pattern4.png
Binary files differ
diff --git a/addons/skin.estuary/extras/backgrounds/pattern5.png b/addons/skin.estuary/extras/backgrounds/pattern5.png
index 487e597904..a0552bfbe3 100644
--- a/addons/skin.estuary/extras/backgrounds/pattern5.png
+++ b/addons/skin.estuary/extras/backgrounds/pattern5.png
Binary files differ
diff --git a/addons/skin.estuary/extras/backgrounds/pattern6.png b/addons/skin.estuary/extras/backgrounds/pattern6.png
index 2ba32fab1a..425e434b59 100644
--- a/addons/skin.estuary/extras/backgrounds/pattern6.png
+++ b/addons/skin.estuary/extras/backgrounds/pattern6.png
Binary files differ
diff --git a/addons/skin.estuary/extras/backgrounds/pattern7.png b/addons/skin.estuary/extras/backgrounds/pattern7.png
index 6204b3a8f3..c55427735e 100644
--- a/addons/skin.estuary/extras/backgrounds/pattern7.png
+++ b/addons/skin.estuary/extras/backgrounds/pattern7.png
Binary files differ
diff --git a/addons/skin.estuary/media/DefaultPVRProvider.png b/addons/skin.estuary/media/DefaultPVRProvider.png
new file mode 100644
index 0000000000..b5dcd1a8cc
--- /dev/null
+++ b/addons/skin.estuary/media/DefaultPVRProvider.png
Binary files differ
diff --git a/addons/skin.estuary/media/DefaultPVRProviders.png b/addons/skin.estuary/media/DefaultPVRProviders.png
new file mode 100644
index 0000000000..b9f7008f93
--- /dev/null
+++ b/addons/skin.estuary/media/DefaultPVRProviders.png
Binary files differ
diff --git a/addons/skin.estuary/media/flags/aspectratio/1.00.png b/addons/skin.estuary/media/flags/aspectratio/1.00.png
deleted file mode 100644
index eb41c15f7f..0000000000
--- a/addons/skin.estuary/media/flags/aspectratio/1.00.png
+++ /dev/null
Binary files differ
diff --git a/addons/skin.estuary/media/flags/aspectratio/1.19.png b/addons/skin.estuary/media/flags/aspectratio/1.19.png
deleted file mode 100644
index 80289ae466..0000000000
--- a/addons/skin.estuary/media/flags/aspectratio/1.19.png
+++ /dev/null
Binary files differ
diff --git a/addons/skin.estuary/media/flags/aspectratio/1.33.png b/addons/skin.estuary/media/flags/aspectratio/1.33.png
deleted file mode 100644
index 43e079495a..0000000000
--- a/addons/skin.estuary/media/flags/aspectratio/1.33.png
+++ /dev/null
Binary files differ
diff --git a/addons/skin.estuary/media/flags/aspectratio/1.37.png b/addons/skin.estuary/media/flags/aspectratio/1.37.png
deleted file mode 100644
index e86c6f7a1d..0000000000
--- a/addons/skin.estuary/media/flags/aspectratio/1.37.png
+++ /dev/null
Binary files differ
diff --git a/addons/skin.estuary/media/flags/aspectratio/1.66.png b/addons/skin.estuary/media/flags/aspectratio/1.66.png
deleted file mode 100644
index 80ca726bcc..0000000000
--- a/addons/skin.estuary/media/flags/aspectratio/1.66.png
+++ /dev/null
Binary files differ
diff --git a/addons/skin.estuary/media/flags/aspectratio/1.78.png b/addons/skin.estuary/media/flags/aspectratio/1.78.png
deleted file mode 100644
index 01d70b832d..0000000000
--- a/addons/skin.estuary/media/flags/aspectratio/1.78.png
+++ /dev/null
Binary files differ
diff --git a/addons/skin.estuary/media/flags/aspectratio/1.85.png b/addons/skin.estuary/media/flags/aspectratio/1.85.png
deleted file mode 100644
index 479804fde6..0000000000
--- a/addons/skin.estuary/media/flags/aspectratio/1.85.png
+++ /dev/null
Binary files differ
diff --git a/addons/skin.estuary/media/flags/aspectratio/2.00.png b/addons/skin.estuary/media/flags/aspectratio/2.00.png
deleted file mode 100644
index cd8ff2569f..0000000000
--- a/addons/skin.estuary/media/flags/aspectratio/2.00.png
+++ /dev/null
Binary files differ
diff --git a/addons/skin.estuary/media/flags/aspectratio/2.20.png b/addons/skin.estuary/media/flags/aspectratio/2.20.png
deleted file mode 100644
index d0cebe276e..0000000000
--- a/addons/skin.estuary/media/flags/aspectratio/2.20.png
+++ /dev/null
Binary files differ
diff --git a/addons/skin.estuary/media/flags/aspectratio/2.35.png b/addons/skin.estuary/media/flags/aspectratio/2.35.png
deleted file mode 100644
index cacb088692..0000000000
--- a/addons/skin.estuary/media/flags/aspectratio/2.35.png
+++ /dev/null
Binary files differ
diff --git a/addons/skin.estuary/media/flags/aspectratio/2.40.png b/addons/skin.estuary/media/flags/aspectratio/2.40.png
deleted file mode 100644
index 35aff17350..0000000000
--- a/addons/skin.estuary/media/flags/aspectratio/2.40.png
+++ /dev/null
Binary files differ
diff --git a/addons/skin.estuary/media/flags/aspectratio/2.55.png b/addons/skin.estuary/media/flags/aspectratio/2.55.png
deleted file mode 100644
index a592e04fbe..0000000000
--- a/addons/skin.estuary/media/flags/aspectratio/2.55.png
+++ /dev/null
Binary files differ
diff --git a/addons/skin.estuary/media/flags/aspectratio/2.76.png b/addons/skin.estuary/media/flags/aspectratio/2.76.png
deleted file mode 100644
index 051e671b6a..0000000000
--- a/addons/skin.estuary/media/flags/aspectratio/2.76.png
+++ /dev/null
Binary files differ
diff --git a/addons/skin.estuary/media/flags/audiochannel/0.png b/addons/skin.estuary/media/flags/audiochannel/0.png
deleted file mode 100644
index a5be90821a..0000000000
--- a/addons/skin.estuary/media/flags/audiochannel/0.png
+++ /dev/null
Binary files differ
diff --git a/addons/skin.estuary/media/flags/audiochannel/1.png b/addons/skin.estuary/media/flags/audiochannel/1.png
deleted file mode 100644
index 87f541c99c..0000000000
--- a/addons/skin.estuary/media/flags/audiochannel/1.png
+++ /dev/null
Binary files differ
diff --git a/addons/skin.estuary/media/flags/audiochannel/10.png b/addons/skin.estuary/media/flags/audiochannel/10.png
deleted file mode 100644
index 49ed3ec3ab..0000000000
--- a/addons/skin.estuary/media/flags/audiochannel/10.png
+++ /dev/null
Binary files differ
diff --git a/addons/skin.estuary/media/flags/audiochannel/2.png b/addons/skin.estuary/media/flags/audiochannel/2.png
deleted file mode 100644
index c7102b6c22..0000000000
--- a/addons/skin.estuary/media/flags/audiochannel/2.png
+++ /dev/null
Binary files differ
diff --git a/addons/skin.estuary/media/flags/audiochannel/3.png b/addons/skin.estuary/media/flags/audiochannel/3.png
deleted file mode 100644
index 5f9b0cca6d..0000000000
--- a/addons/skin.estuary/media/flags/audiochannel/3.png
+++ /dev/null
Binary files differ
diff --git a/addons/skin.estuary/media/flags/audiochannel/4.png b/addons/skin.estuary/media/flags/audiochannel/4.png
deleted file mode 100644
index 67c04c0e03..0000000000
--- a/addons/skin.estuary/media/flags/audiochannel/4.png
+++ /dev/null
Binary files differ
diff --git a/addons/skin.estuary/media/flags/audiochannel/5.png b/addons/skin.estuary/media/flags/audiochannel/5.png
deleted file mode 100644
index a7f5f895ac..0000000000
--- a/addons/skin.estuary/media/flags/audiochannel/5.png
+++ /dev/null
Binary files differ
diff --git a/addons/skin.estuary/media/flags/audiochannel/6.png b/addons/skin.estuary/media/flags/audiochannel/6.png
deleted file mode 100644
index d35e28d8e4..0000000000
--- a/addons/skin.estuary/media/flags/audiochannel/6.png
+++ /dev/null
Binary files differ
diff --git a/addons/skin.estuary/media/flags/audiochannel/7.png b/addons/skin.estuary/media/flags/audiochannel/7.png
deleted file mode 100644
index e026a26753..0000000000
--- a/addons/skin.estuary/media/flags/audiochannel/7.png
+++ /dev/null
Binary files differ
diff --git a/addons/skin.estuary/media/flags/audiochannel/8.png b/addons/skin.estuary/media/flags/audiochannel/8.png
deleted file mode 100644
index b32fc36ab4..0000000000
--- a/addons/skin.estuary/media/flags/audiochannel/8.png
+++ /dev/null
Binary files differ
diff --git a/addons/skin.estuary/media/flags/audiocodec/aac.png b/addons/skin.estuary/media/flags/audiocodec/aac.png
deleted file mode 100644
index 55e81409b7..0000000000
--- a/addons/skin.estuary/media/flags/audiocodec/aac.png
+++ /dev/null
Binary files differ
diff --git a/addons/skin.estuary/media/flags/audiocodec/aac_latm.png b/addons/skin.estuary/media/flags/audiocodec/aac_latm.png
deleted file mode 100644
index 55e81409b7..0000000000
--- a/addons/skin.estuary/media/flags/audiocodec/aac_latm.png
+++ /dev/null
Binary files differ
diff --git a/addons/skin.estuary/media/flags/audiocodec/ac3.png b/addons/skin.estuary/media/flags/audiocodec/ac3.png
deleted file mode 100644
index d01a87739e..0000000000
--- a/addons/skin.estuary/media/flags/audiocodec/ac3.png
+++ /dev/null
Binary files differ
diff --git a/addons/skin.estuary/media/flags/audiocodec/aif.png b/addons/skin.estuary/media/flags/audiocodec/aif.png
deleted file mode 100644
index ce4677858b..0000000000
--- a/addons/skin.estuary/media/flags/audiocodec/aif.png
+++ /dev/null
Binary files differ
diff --git a/addons/skin.estuary/media/flags/audiocodec/aifc.png b/addons/skin.estuary/media/flags/audiocodec/aifc.png
deleted file mode 100644
index ed9a26c6bb..0000000000
--- a/addons/skin.estuary/media/flags/audiocodec/aifc.png
+++ /dev/null
Binary files differ
diff --git a/addons/skin.estuary/media/flags/audiocodec/aiff.png b/addons/skin.estuary/media/flags/audiocodec/aiff.png
deleted file mode 100644
index ce4677858b..0000000000
--- a/addons/skin.estuary/media/flags/audiocodec/aiff.png
+++ /dev/null
Binary files differ
diff --git a/addons/skin.estuary/media/flags/audiocodec/alac.png b/addons/skin.estuary/media/flags/audiocodec/alac.png
deleted file mode 100644
index a49527cf6a..0000000000
--- a/addons/skin.estuary/media/flags/audiocodec/alac.png
+++ /dev/null
Binary files differ
diff --git a/addons/skin.estuary/media/flags/audiocodec/ape.png b/addons/skin.estuary/media/flags/audiocodec/ape.png
deleted file mode 100644
index 94e01abf20..0000000000
--- a/addons/skin.estuary/media/flags/audiocodec/ape.png
+++ /dev/null
Binary files differ
diff --git a/addons/skin.estuary/media/flags/audiocodec/avc.png b/addons/skin.estuary/media/flags/audiocodec/avc.png
deleted file mode 100644
index 91aa179870..0000000000
--- a/addons/skin.estuary/media/flags/audiocodec/avc.png
+++ /dev/null
Binary files differ
diff --git a/addons/skin.estuary/media/flags/audiocodec/cdda.png b/addons/skin.estuary/media/flags/audiocodec/cdda.png
deleted file mode 100644
index 3f257dd567..0000000000
--- a/addons/skin.estuary/media/flags/audiocodec/cdda.png
+++ /dev/null
Binary files differ
diff --git a/addons/skin.estuary/media/flags/audiocodec/dca.png b/addons/skin.estuary/media/flags/audiocodec/dca.png
deleted file mode 100644
index 1dc52ec67f..0000000000
--- a/addons/skin.estuary/media/flags/audiocodec/dca.png
+++ /dev/null
Binary files differ
diff --git a/addons/skin.estuary/media/flags/audiocodec/dolbydigital.png b/addons/skin.estuary/media/flags/audiocodec/dolbydigital.png
deleted file mode 100644
index d01a87739e..0000000000
--- a/addons/skin.estuary/media/flags/audiocodec/dolbydigital.png
+++ /dev/null
Binary files differ
diff --git a/addons/skin.estuary/media/flags/audiocodec/dts.png b/addons/skin.estuary/media/flags/audiocodec/dts.png
deleted file mode 100644
index 1dc52ec67f..0000000000
--- a/addons/skin.estuary/media/flags/audiocodec/dts.png
+++ /dev/null
Binary files differ
diff --git a/addons/skin.estuary/media/flags/audiocodec/dtshd_hra.png b/addons/skin.estuary/media/flags/audiocodec/dtshd_hra.png
deleted file mode 100644
index 53ffb9002b..0000000000
--- a/addons/skin.estuary/media/flags/audiocodec/dtshd_hra.png
+++ /dev/null
Binary files differ
diff --git a/addons/skin.estuary/media/flags/audiocodec/dtshd_ma.png b/addons/skin.estuary/media/flags/audiocodec/dtshd_ma.png
deleted file mode 100644
index f20256e591..0000000000
--- a/addons/skin.estuary/media/flags/audiocodec/dtshd_ma.png
+++ /dev/null
Binary files differ
diff --git a/addons/skin.estuary/media/flags/audiocodec/dtsma.png b/addons/skin.estuary/media/flags/audiocodec/dtsma.png
deleted file mode 100644
index f20256e591..0000000000
--- a/addons/skin.estuary/media/flags/audiocodec/dtsma.png
+++ /dev/null
Binary files differ
diff --git a/addons/skin.estuary/media/flags/audiocodec/eac3.png b/addons/skin.estuary/media/flags/audiocodec/eac3.png
deleted file mode 100644
index d01a87739e..0000000000
--- a/addons/skin.estuary/media/flags/audiocodec/eac3.png
+++ /dev/null
Binary files differ
diff --git a/addons/skin.estuary/media/flags/audiocodec/flac.png b/addons/skin.estuary/media/flags/audiocodec/flac.png
deleted file mode 100644
index f173541ebd..0000000000
--- a/addons/skin.estuary/media/flags/audiocodec/flac.png
+++ /dev/null
Binary files differ
diff --git a/addons/skin.estuary/media/flags/audiocodec/mp1.png b/addons/skin.estuary/media/flags/audiocodec/mp1.png
deleted file mode 100644
index d3065f1b95..0000000000
--- a/addons/skin.estuary/media/flags/audiocodec/mp1.png
+++ /dev/null
Binary files differ
diff --git a/addons/skin.estuary/media/flags/audiocodec/mp2.png b/addons/skin.estuary/media/flags/audiocodec/mp2.png
deleted file mode 100644
index ed4e21eefe..0000000000
--- a/addons/skin.estuary/media/flags/audiocodec/mp2.png
+++ /dev/null
Binary files differ
diff --git a/addons/skin.estuary/media/flags/audiocodec/mp3.png b/addons/skin.estuary/media/flags/audiocodec/mp3.png
deleted file mode 100644
index 258d161f5a..0000000000
--- a/addons/skin.estuary/media/flags/audiocodec/mp3.png
+++ /dev/null
Binary files differ
diff --git a/addons/skin.estuary/media/flags/audiocodec/mp3float.png b/addons/skin.estuary/media/flags/audiocodec/mp3float.png
deleted file mode 100644
index 258d161f5a..0000000000
--- a/addons/skin.estuary/media/flags/audiocodec/mp3float.png
+++ /dev/null
Binary files differ
diff --git a/addons/skin.estuary/media/flags/audiocodec/ogg.png b/addons/skin.estuary/media/flags/audiocodec/ogg.png
deleted file mode 100644
index 208200a63e..0000000000
--- a/addons/skin.estuary/media/flags/audiocodec/ogg.png
+++ /dev/null
Binary files differ
diff --git a/addons/skin.estuary/media/flags/audiocodec/opus.png b/addons/skin.estuary/media/flags/audiocodec/opus.png
deleted file mode 100644
index df856a6d57..0000000000
--- a/addons/skin.estuary/media/flags/audiocodec/opus.png
+++ /dev/null
Binary files differ
diff --git a/addons/skin.estuary/media/flags/audiocodec/pcm.png b/addons/skin.estuary/media/flags/audiocodec/pcm.png
deleted file mode 100644
index 0c7a5bdeee..0000000000
--- a/addons/skin.estuary/media/flags/audiocodec/pcm.png
+++ /dev/null
Binary files differ
diff --git a/addons/skin.estuary/media/flags/audiocodec/pcm_bluray.png b/addons/skin.estuary/media/flags/audiocodec/pcm_bluray.png
deleted file mode 100644
index 30b4f8b138..0000000000
--- a/addons/skin.estuary/media/flags/audiocodec/pcm_bluray.png
+++ /dev/null
Binary files differ
diff --git a/addons/skin.estuary/media/flags/audiocodec/pcm_s16le.png b/addons/skin.estuary/media/flags/audiocodec/pcm_s16le.png
deleted file mode 100644
index dc514806e3..0000000000
--- a/addons/skin.estuary/media/flags/audiocodec/pcm_s16le.png
+++ /dev/null
Binary files differ
diff --git a/addons/skin.estuary/media/flags/audiocodec/pcm_s24le.png b/addons/skin.estuary/media/flags/audiocodec/pcm_s24le.png
deleted file mode 100644
index 81ceacc06d..0000000000
--- a/addons/skin.estuary/media/flags/audiocodec/pcm_s24le.png
+++ /dev/null
Binary files differ
diff --git a/addons/skin.estuary/media/flags/audiocodec/truehd.png b/addons/skin.estuary/media/flags/audiocodec/truehd.png
deleted file mode 100644
index 4bcf8c1fda..0000000000
--- a/addons/skin.estuary/media/flags/audiocodec/truehd.png
+++ /dev/null
Binary files differ
diff --git a/addons/skin.estuary/media/flags/audiocodec/vorbis.png b/addons/skin.estuary/media/flags/audiocodec/vorbis.png
deleted file mode 100644
index e7ec2c5361..0000000000
--- a/addons/skin.estuary/media/flags/audiocodec/vorbis.png
+++ /dev/null
Binary files differ
diff --git a/addons/skin.estuary/media/flags/audiocodec/wav.png b/addons/skin.estuary/media/flags/audiocodec/wav.png
deleted file mode 100644
index 76cd02d3fc..0000000000
--- a/addons/skin.estuary/media/flags/audiocodec/wav.png
+++ /dev/null
Binary files differ
diff --git a/addons/skin.estuary/media/flags/audiocodec/wavpack.png b/addons/skin.estuary/media/flags/audiocodec/wavpack.png
deleted file mode 100644
index 6501af9942..0000000000
--- a/addons/skin.estuary/media/flags/audiocodec/wavpack.png
+++ /dev/null
Binary files differ
diff --git a/addons/skin.estuary/media/flags/audiocodec/wma.png b/addons/skin.estuary/media/flags/audiocodec/wma.png
deleted file mode 100644
index 20093c15d5..0000000000
--- a/addons/skin.estuary/media/flags/audiocodec/wma.png
+++ /dev/null
Binary files differ
diff --git a/addons/skin.estuary/media/flags/audiocodec/wmapro.png b/addons/skin.estuary/media/flags/audiocodec/wmapro.png
deleted file mode 100644
index 20093c15d5..0000000000
--- a/addons/skin.estuary/media/flags/audiocodec/wmapro.png
+++ /dev/null
Binary files differ
diff --git a/addons/skin.estuary/media/flags/audiocodec/wmav2.png b/addons/skin.estuary/media/flags/audiocodec/wmav2.png
deleted file mode 100644
index 20093c15d5..0000000000
--- a/addons/skin.estuary/media/flags/audiocodec/wmav2.png
+++ /dev/null
Binary files differ
diff --git a/addons/skin.estuary/media/flags/rds/rds.png b/addons/skin.estuary/media/flags/rds/rds.png
deleted file mode 100644
index e5de4ccfcf..0000000000
--- a/addons/skin.estuary/media/flags/rds/rds.png
+++ /dev/null
Binary files differ
diff --git a/addons/skin.estuary/media/flags/videocodec/av1.png b/addons/skin.estuary/media/flags/videocodec/av1.png
deleted file mode 100644
index f594b9ea9c..0000000000
--- a/addons/skin.estuary/media/flags/videocodec/av1.png
+++ /dev/null
Binary files differ
diff --git a/addons/skin.estuary/media/flags/videocodec/avc1.png b/addons/skin.estuary/media/flags/videocodec/avc1.png
deleted file mode 100644
index 78da5d8936..0000000000
--- a/addons/skin.estuary/media/flags/videocodec/avc1.png
+++ /dev/null
Binary files differ
diff --git a/addons/skin.estuary/media/flags/videocodec/bluray.png b/addons/skin.estuary/media/flags/videocodec/bluray.png
deleted file mode 100644
index b8fe922c6d..0000000000
--- a/addons/skin.estuary/media/flags/videocodec/bluray.png
+++ /dev/null
Binary files differ
diff --git a/addons/skin.estuary/media/flags/videocodec/div3.png b/addons/skin.estuary/media/flags/videocodec/div3.png
deleted file mode 100644
index 65a9a6517c..0000000000
--- a/addons/skin.estuary/media/flags/videocodec/div3.png
+++ /dev/null
Binary files differ
diff --git a/addons/skin.estuary/media/flags/videocodec/divx.png b/addons/skin.estuary/media/flags/videocodec/divx.png
deleted file mode 100644
index 65a9a6517c..0000000000
--- a/addons/skin.estuary/media/flags/videocodec/divx.png
+++ /dev/null
Binary files differ
diff --git a/addons/skin.estuary/media/flags/videocodec/dvd.png b/addons/skin.estuary/media/flags/videocodec/dvd.png
deleted file mode 100644
index 9e9bd97ab0..0000000000
--- a/addons/skin.estuary/media/flags/videocodec/dvd.png
+++ /dev/null
Binary files differ
diff --git a/addons/skin.estuary/media/flags/videocodec/dx50.png b/addons/skin.estuary/media/flags/videocodec/dx50.png
deleted file mode 100644
index 65a9a6517c..0000000000
--- a/addons/skin.estuary/media/flags/videocodec/dx50.png
+++ /dev/null
Binary files differ
diff --git a/addons/skin.estuary/media/flags/videocodec/flv.png b/addons/skin.estuary/media/flags/videocodec/flv.png
deleted file mode 100644
index 8b1b5775d5..0000000000
--- a/addons/skin.estuary/media/flags/videocodec/flv.png
+++ /dev/null
Binary files differ
diff --git a/addons/skin.estuary/media/flags/videocodec/h264.png b/addons/skin.estuary/media/flags/videocodec/h264.png
deleted file mode 100644
index 64cfa04ec9..0000000000
--- a/addons/skin.estuary/media/flags/videocodec/h264.png
+++ /dev/null
Binary files differ
diff --git a/addons/skin.estuary/media/flags/videocodec/hddvd.png b/addons/skin.estuary/media/flags/videocodec/hddvd.png
deleted file mode 100644
index 4a170a91b6..0000000000
--- a/addons/skin.estuary/media/flags/videocodec/hddvd.png
+++ /dev/null
Binary files differ
diff --git a/addons/skin.estuary/media/flags/videocodec/hdmv.png b/addons/skin.estuary/media/flags/videocodec/hdmv.png
deleted file mode 100644
index b8fe922c6d..0000000000
--- a/addons/skin.estuary/media/flags/videocodec/hdmv.png
+++ /dev/null
Binary files differ
diff --git a/addons/skin.estuary/media/flags/videocodec/hev1.png b/addons/skin.estuary/media/flags/videocodec/hev1.png
deleted file mode 100644
index 1e1d3c186b..0000000000
--- a/addons/skin.estuary/media/flags/videocodec/hev1.png
+++ /dev/null
Binary files differ
diff --git a/addons/skin.estuary/media/flags/videocodec/hevc.png b/addons/skin.estuary/media/flags/videocodec/hevc.png
deleted file mode 100644
index 1e1d3c186b..0000000000
--- a/addons/skin.estuary/media/flags/videocodec/hevc.png
+++ /dev/null
Binary files differ
diff --git a/addons/skin.estuary/media/flags/videocodec/hvc1.png b/addons/skin.estuary/media/flags/videocodec/hvc1.png
deleted file mode 100644
index 1e1d3c186b..0000000000
--- a/addons/skin.estuary/media/flags/videocodec/hvc1.png
+++ /dev/null
Binary files differ
diff --git a/addons/skin.estuary/media/flags/videocodec/mp4v.png b/addons/skin.estuary/media/flags/videocodec/mp4v.png
deleted file mode 100644
index da9d967bb8..0000000000
--- a/addons/skin.estuary/media/flags/videocodec/mp4v.png
+++ /dev/null
Binary files differ
diff --git a/addons/skin.estuary/media/flags/videocodec/mpeg1.png b/addons/skin.estuary/media/flags/videocodec/mpeg1.png
deleted file mode 100644
index 8b210cf2d1..0000000000
--- a/addons/skin.estuary/media/flags/videocodec/mpeg1.png
+++ /dev/null
Binary files differ
diff --git a/addons/skin.estuary/media/flags/videocodec/mpeg1video.png b/addons/skin.estuary/media/flags/videocodec/mpeg1video.png
deleted file mode 100644
index 8b210cf2d1..0000000000
--- a/addons/skin.estuary/media/flags/videocodec/mpeg1video.png
+++ /dev/null
Binary files differ
diff --git a/addons/skin.estuary/media/flags/videocodec/mpeg2.png b/addons/skin.estuary/media/flags/videocodec/mpeg2.png
deleted file mode 100644
index f46483b765..0000000000
--- a/addons/skin.estuary/media/flags/videocodec/mpeg2.png
+++ /dev/null
Binary files differ
diff --git a/addons/skin.estuary/media/flags/videocodec/mpeg2video.png b/addons/skin.estuary/media/flags/videocodec/mpeg2video.png
deleted file mode 100644
index f46483b765..0000000000
--- a/addons/skin.estuary/media/flags/videocodec/mpeg2video.png
+++ /dev/null
Binary files differ
diff --git a/addons/skin.estuary/media/flags/videocodec/mpeg4.png b/addons/skin.estuary/media/flags/videocodec/mpeg4.png
deleted file mode 100644
index da9d967bb8..0000000000
--- a/addons/skin.estuary/media/flags/videocodec/mpeg4.png
+++ /dev/null
Binary files differ
diff --git a/addons/skin.estuary/media/flags/videocodec/theora.png b/addons/skin.estuary/media/flags/videocodec/theora.png
deleted file mode 100644
index 8f1af4bb94..0000000000
--- a/addons/skin.estuary/media/flags/videocodec/theora.png
+++ /dev/null
Binary files differ
diff --git a/addons/skin.estuary/media/flags/videocodec/tv.png b/addons/skin.estuary/media/flags/videocodec/tv.png
deleted file mode 100644
index b7cb357442..0000000000
--- a/addons/skin.estuary/media/flags/videocodec/tv.png
+++ /dev/null
Binary files differ
diff --git a/addons/skin.estuary/media/flags/videocodec/vc-1.png b/addons/skin.estuary/media/flags/videocodec/vc-1.png
deleted file mode 100644
index 843497f6a9..0000000000
--- a/addons/skin.estuary/media/flags/videocodec/vc-1.png
+++ /dev/null
Binary files differ
diff --git a/addons/skin.estuary/media/flags/videocodec/vc1.png b/addons/skin.estuary/media/flags/videocodec/vc1.png
deleted file mode 100644
index 843497f6a9..0000000000
--- a/addons/skin.estuary/media/flags/videocodec/vc1.png
+++ /dev/null
Binary files differ
diff --git a/addons/skin.estuary/media/flags/videocodec/vhs.png b/addons/skin.estuary/media/flags/videocodec/vhs.png
deleted file mode 100644
index a1a4ff3198..0000000000
--- a/addons/skin.estuary/media/flags/videocodec/vhs.png
+++ /dev/null
Binary files differ
diff --git a/addons/skin.estuary/media/flags/videocodec/vp8.png b/addons/skin.estuary/media/flags/videocodec/vp8.png
deleted file mode 100644
index d9ad357901..0000000000
--- a/addons/skin.estuary/media/flags/videocodec/vp8.png
+++ /dev/null
Binary files differ
diff --git a/addons/skin.estuary/media/flags/videocodec/vp9.png b/addons/skin.estuary/media/flags/videocodec/vp9.png
deleted file mode 100644
index c09a28cb10..0000000000
--- a/addons/skin.estuary/media/flags/videocodec/vp9.png
+++ /dev/null
Binary files differ
diff --git a/addons/skin.estuary/media/flags/videocodec/wmv.png b/addons/skin.estuary/media/flags/videocodec/wmv.png
deleted file mode 100644
index 34a7cedf3b..0000000000
--- a/addons/skin.estuary/media/flags/videocodec/wmv.png
+++ /dev/null
Binary files differ
diff --git a/addons/skin.estuary/media/flags/videocodec/wmv3.png b/addons/skin.estuary/media/flags/videocodec/wmv3.png
deleted file mode 100644
index 34a7cedf3b..0000000000
--- a/addons/skin.estuary/media/flags/videocodec/wmv3.png
+++ /dev/null
Binary files differ
diff --git a/addons/skin.estuary/media/flags/videocodec/wvc1.png b/addons/skin.estuary/media/flags/videocodec/wvc1.png
deleted file mode 100644
index 843497f6a9..0000000000
--- a/addons/skin.estuary/media/flags/videocodec/wvc1.png
+++ /dev/null
Binary files differ
diff --git a/addons/skin.estuary/media/flags/videocodec/xvid.png b/addons/skin.estuary/media/flags/videocodec/xvid.png
deleted file mode 100644
index b835bf6873..0000000000
--- a/addons/skin.estuary/media/flags/videocodec/xvid.png
+++ /dev/null
Binary files differ
diff --git a/addons/skin.estuary/media/flags/videohdr/dolbyvision.png b/addons/skin.estuary/media/flags/videohdr/dolbyvision.png
deleted file mode 100644
index 5875eb80e7..0000000000
--- a/addons/skin.estuary/media/flags/videohdr/dolbyvision.png
+++ /dev/null
Binary files differ
diff --git a/addons/skin.estuary/media/flags/videohdr/hdr10.png b/addons/skin.estuary/media/flags/videohdr/hdr10.png
deleted file mode 100644
index e4b671fe12..0000000000
--- a/addons/skin.estuary/media/flags/videohdr/hdr10.png
+++ /dev/null
Binary files differ
diff --git a/addons/skin.estuary/media/flags/videohdr/hlg.png b/addons/skin.estuary/media/flags/videohdr/hlg.png
deleted file mode 100644
index a8bc078d70..0000000000
--- a/addons/skin.estuary/media/flags/videohdr/hlg.png
+++ /dev/null
Binary files differ
diff --git a/addons/skin.estuary/media/flags/videoresolution/1080.png b/addons/skin.estuary/media/flags/videoresolution/1080.png
deleted file mode 100644
index fa076b8d2d..0000000000
--- a/addons/skin.estuary/media/flags/videoresolution/1080.png
+++ /dev/null
Binary files differ
diff --git a/addons/skin.estuary/media/flags/videoresolution/3D.png b/addons/skin.estuary/media/flags/videoresolution/3D.png
deleted file mode 100644
index 9b6c26254f..0000000000
--- a/addons/skin.estuary/media/flags/videoresolution/3D.png
+++ /dev/null
Binary files differ
diff --git a/addons/skin.estuary/media/flags/videoresolution/480.png b/addons/skin.estuary/media/flags/videoresolution/480.png
deleted file mode 100644
index 66deb31b80..0000000000
--- a/addons/skin.estuary/media/flags/videoresolution/480.png
+++ /dev/null
Binary files differ
diff --git a/addons/skin.estuary/media/flags/videoresolution/4K.png b/addons/skin.estuary/media/flags/videoresolution/4K.png
deleted file mode 100644
index f3d13bdccf..0000000000
--- a/addons/skin.estuary/media/flags/videoresolution/4K.png
+++ /dev/null
Binary files differ
diff --git a/addons/skin.estuary/media/flags/videoresolution/540.png b/addons/skin.estuary/media/flags/videoresolution/540.png
deleted file mode 100644
index dd6c3823d1..0000000000
--- a/addons/skin.estuary/media/flags/videoresolution/540.png
+++ /dev/null
Binary files differ
diff --git a/addons/skin.estuary/media/flags/videoresolution/576.png b/addons/skin.estuary/media/flags/videoresolution/576.png
deleted file mode 100644
index 8a96be926c..0000000000
--- a/addons/skin.estuary/media/flags/videoresolution/576.png
+++ /dev/null
Binary files differ
diff --git a/addons/skin.estuary/media/flags/videoresolution/720.png b/addons/skin.estuary/media/flags/videoresolution/720.png
deleted file mode 100644
index 0b6406004a..0000000000
--- a/addons/skin.estuary/media/flags/videoresolution/720.png
+++ /dev/null
Binary files differ
diff --git a/addons/skin.estuary/media/flags/videoresolution/8K.png b/addons/skin.estuary/media/flags/videoresolution/8K.png
deleted file mode 100644
index de46f91e9b..0000000000
--- a/addons/skin.estuary/media/flags/videoresolution/8K.png
+++ /dev/null
Binary files differ
diff --git a/addons/skin.estuary/xml/DialogAddonSettings.xml b/addons/skin.estuary/xml/DialogAddonSettings.xml
index 38349c1800..737366dfdd 100644
--- a/addons/skin.estuary/xml/DialogAddonSettings.xml
+++ b/addons/skin.estuary/xml/DialogAddonSettings.xml
@@ -123,6 +123,44 @@
<param name="label" value="" />
</include>
</control>
+ <control type="group">
+ <description>Currently-playing emulator name and icon</description>
+ <visible>Player.HasGame + String.IsEqual(Window(addonsettings).Property(Addon.Type),kodi.gameclient)</visible>
+ <left>1510</left>
+ <width>300</width>
+ <top>528</top>
+ <height>260</height>
+ <control type="label">
+ <description>Emulator name</description>
+ <height>30</height>
+ <font>font23_narrow</font>
+ <textoffsetx>20</textoffsetx>
+ <textcolor>button_focus</textcolor>
+ <shadowcolor>text_shadow</shadowcolor>
+ <align>center</align>
+ <label>$INFO[Window(addonsettings).Property(GameClient.Name)]</label>
+ </control>
+ <control type="label">
+ <description>Emulator version</description>
+ <top>30</top>
+ <height>30</height>
+ <font>font23_narrow</font>
+ <textoffsetx>20</textoffsetx>
+ <textcolor>button_focus</textcolor>
+ <shadowcolor>text_shadow</shadowcolor>
+ <align>center</align>
+ <label>$INFO[Window(addonsettings).Property(Addon.Version)]</label>
+ </control>
+ <control type="image">
+ <description>Emulator icon</description>
+ <left>50</left>
+ <top>70</top>
+ <height>200</height>
+ <width>200</width>
+ <aspectratio>keep</aspectratio>
+ <texture>$INFO[Window(addonsettings).Property(Addon.Icon)]</texture>
+ </control>
+ </control>
<control type="radiobutton" id="20">
<left>29</left>
<top>700</top>
diff --git a/addons/skin.estuary/xml/DialogPVRGuideSearch.xml b/addons/skin.estuary/xml/DialogPVRGuideSearch.xml
index e1e766025e..8ac09e05d0 100644
--- a/addons/skin.estuary/xml/DialogPVRGuideSearch.xml
+++ b/addons/skin.estuary/xml/DialogPVRGuideSearch.xml
@@ -5,12 +5,12 @@
<controls>
<control type="group">
<centertop>50%</centertop>
- <height>890</height>
+ <height>960</height>
<centerleft>50%</centerleft>
<width>1780</width>
<include content="DialogBackgroundCommons">
<param name="width" value="1780" />
- <param name="height" value="890" />
+ <param name="height" value="960" />
<param name="header_label" value="$LOCALIZE[19142]" />
<param name="header_id" value="2" />
</include>
@@ -41,7 +41,7 @@
<left>10</left>
<top>210</top>
<width>1460</width>
- <height>675</height>
+ <height>740</height>
<texture border="40">buttons/dialogbutton-nofo.png</texture>
</control>
<control type="grouplist" id="5000">
@@ -69,16 +69,23 @@
<control type="edit" id="14">
<description>Start Date</description>
<width>710</width>
- <onright>16</onright>
+ <onright>15</onright>
<include>DefaultSettingButton</include>
<label>$LOCALIZE[19128]</label>
</control>
- <control type="edit" id="15">
- <description>Stop Date</description>
+ <control type="radiobutton" id="32">
+ <description>Start any time</description>
+ <width>710</width>
+ <onright>33</onright>
+ <include>DefaultSettingButton</include>
+ <label>$LOCALIZE[810]</label>
+ </control>
+ <control type="edit" id="16">
+ <description>Start time</description>
<width>710</width>
<onright>17</onright>
<include>DefaultSettingButton</include>
- <label>$LOCALIZE[19129]</label>
+ <label>$LOCALIZE[19126]</label>
</control>
<control type="radiobutton" id="30">
<description>Ignore finished broadcasts</description>
@@ -138,17 +145,24 @@
<include>DefaultSettingButton</include>
<label>$LOCALIZE[19131]</label>
</control>
- <control type="edit" id="16">
- <description>Start time</description>
+ <control type="edit" id="15">
+ <description>Stop Date</description>
<width>710</width>
<onleft>14</onleft>
<include>DefaultSettingButton</include>
- <label>$LOCALIZE[19126]</label>
+ <label>$LOCALIZE[19129]</label>
+ </control>
+ <control type="radiobutton" id="33">
+ <description>End any time</description>
+ <width>710</width>
+ <onleft>32</onleft>
+ <include>DefaultSettingButton</include>
+ <label>$LOCALIZE[817]</label>
</control>
<control type="edit" id="17">
<description>Stop time</description>
<width>710</width>
- <onleft>15</onleft>
+ <onleft>16</onleft>
<include>DefaultSettingButton</include>
<label>$LOCALIZE[19127]</label>
</control>
diff --git a/addons/skin.estuary/xml/DialogPVRInfo.xml b/addons/skin.estuary/xml/DialogPVRInfo.xml
index 86a1edd432..561d898d96 100644
--- a/addons/skin.estuary/xml/DialogPVRInfo.xml
+++ b/addons/skin.estuary/xml/DialogPVRInfo.xml
@@ -49,7 +49,7 @@
<width>1050</width>
<height>425</height>
<align>justify</align>
- <label>$INFO[ListItem.ChannelName,[B],[/B][CR]]$INFO[ListItem.Date,[COLOR grey]$LOCALIZE[552]:[/COLOR] ,[CR]]$INFO[ListItem.Duration,[COLOR grey]$LOCALIZE[180]:[/COLOR] ,[CR]]$VAR[RecordingSizeLabel]$VAR[PremieredLabel]$INFO[ListItem.Rating,[COLOR grey]$LOCALIZE[563]:[/COLOR] ,[CR]]$VAR[ExpirationDateTimeLabel]$VAR[PVRInstanceName,,[CR]]$INFO[ListItem.Genre,[COLOR grey]$LOCALIZE[515]: [/COLOR]][CR]$INFO[ListItem.ParentalRatingCode,[COLOR grey]$LOCALIZE[31017]: [/COLOR],[CR]]$INFO[ListItem.Writer,[COLOR grey]$LOCALIZE[20417]:[/COLOR] ,[CR]]$INFO[ListItem.Director,[COLOR grey]$LOCALIZE[20339]:[/COLOR] ,[CR]]$INFO[ListItem.Cast,[COLOR grey]$LOCALIZE[206]:[/COLOR] ,[CR]][CR]$INFO[ListItem.Plot]</label>
+ <label>$INFO[ListItem.ChannelName,[B],[/B][CR]]$INFO[ListItem.Date,[COLOR grey]$LOCALIZE[552]:[/COLOR] ,[CR]]$INFO[ListItem.Duration,[COLOR grey]$LOCALIZE[180]:[/COLOR] ,[CR]]$VAR[RecordingSizeLabel]$VAR[PremieredLabel]$INFO[ListItem.Rating,[COLOR grey]$LOCALIZE[563]:[/COLOR] ,[CR]]$VAR[ExpirationDateTimeLabel]$VAR[PVRInstanceName,,[CR]]$INFO[ListItem.MediaProviders,[COLOR grey]$LOCALIZE[19334]:[/COLOR] ,[CR]]$INFO[ListItem.Genre,[COLOR grey]$LOCALIZE[515]: [/COLOR]][CR]$INFO[ListItem.ParentalRatingCode,[COLOR grey]$LOCALIZE[31017]: [/COLOR],[CR]]$INFO[ListItem.Writer,[COLOR grey]$LOCALIZE[20417]:[/COLOR] ,[CR]]$INFO[ListItem.Director,[COLOR grey]$LOCALIZE[20339]:[/COLOR] ,[CR]]$INFO[ListItem.Cast,[COLOR grey]$LOCALIZE[206]:[/COLOR] ,[CR]][CR]$INFO[ListItem.Plot]</label>
<autoscroll time="3000" delay="4000" repeat="5000">Skin.HasSetting(AutoScroll)</autoscroll>
</control>
<control type="grouplist" id="9000">
@@ -117,7 +117,7 @@
</control>
<include content="InfoDialogTopBarInfo">
<param name="main_label" value="$INFO[ListItem.Title] $INFO[ListItem.Year,([COLOR grey],[/COLOR])]" />
- <param name="sub_label" value="$VAR[FlagDashLabel][COLOR grey]$VAR[SeasonEpisodeLabel][/COLOR]$INFO[ListItem.EpisodeName,[COLOR white][B],[/B][/COLOR]]" />
+ <param name="sub_label" value="$VAR[FlagDashLabel]$VAR[SeasonEpisodeLabel,[COLOR grey],[/COLOR]]$INFO[ListItem.EpisodeName]" />
<param name="posy" value="40" />
</include>
</control>
diff --git a/addons/skin.estuary/xml/DialogSeekBar.xml b/addons/skin.estuary/xml/DialogSeekBar.xml
index 5e870977a5..958757d98b 100644
--- a/addons/skin.estuary/xml/DialogSeekBar.xml
+++ b/addons/skin.estuary/xml/DialogSeekBar.xml
@@ -49,115 +49,14 @@
<label>[COLOR red][B]$LOCALIZE[19158][/B][/COLOR]</label>
</control>
</control>
- <control type="grouplist">
- <right>20</right>
- <centertop>125</centertop>
- <width>800</width>
- <height>50</height>
- <align>right</align>
- <include>Animation_BottomSlide</include>
- <orientation>horizontal</orientation>
- <itemgap>5</itemgap>
- <visible>[Player.ShowInfo | Window.IsActive(fullscreeninfo)] + !Player.ChannelPreviewActive + Window.IsActive(fullscreenvideo)</visible>
- <animation effect="fade" start="0" end="100" time="200" delay="1000">Visible</animation>
- <include content="MediaFlag">
- <param name="texture" value="$INFO[VideoPlayer.VideoCodec,flags/videocodec/,.png]" />
- <param name="visible" value="!String.IsEmpty(VideoPlayer.VideoCodec)" />
- </include>
- <include content="MediaFlag">
- <param name="texture" value="$INFO[VideoPlayer.VideoResolution,flags/videoresolution/,.png]" />
- <param name="visible" value="!String.IsEmpty(VideoPlayer.VideoResolution)" />
- </include>
- <include content="MediaFlag">
- <param name="texture" value="$INFO[VideoPlayer.HdrType,flags/videohdr/,.png]" />
- <param name="visible" value="!String.IsEmpty(VideoPlayer.HdrType)" />
- </include>
- <include content="MediaFlag">
- <param name="texture" value="$INFO[VideoPlayer.VideoAspect,flags/aspectratio/,.png]" />
- <param name="visible" value="!String.IsEmpty(VideoPlayer.VideoAspect)" />
- </include>
- <include content="MediaFlag">
- <param name="texture" value="$INFO[VideoPlayer.AudioCodec,flags/audiocodec/,.png]" />
- <param name="visible" value="!String.IsEmpty(VideoPlayer.AudioCodec)" />
- </include>
- <include content="MediaFlag">
- <param name="texture" value="$INFO[VideoPlayer.AudioChannels,flags/audiochannel/,.png]" />
- <param name="visible" value="!String.IsEmpty(VideoPlayer.AudioChannels)" />
- </include>
- </control>
- <control type="grouplist">
- <right>20</right>
- <centertop>125</centertop>
- <width>800</width>
- <height>50</height>
- <align>right</align>
- <include>Animation_BottomSlide</include>
- <orientation>horizontal</orientation>
- <itemgap>10</itemgap>
- <visible>Player.ShowInfo + !Player.ChannelPreviewActive + Window.IsActive(visualisation)</visible>
- <animation effect="fade" start="0" end="100" time="200" delay="1000">Visible</animation>
- <include content="MediaFlag">
- <param name="texture" value="flags/rds/rds.png" />
- <param name="visible" value="RDS.HasRDS" />
- </include>
- <include content="MediaFlag">
- <param name="texture" value="$INFO[MusicPlayer.Codec,flags/audiocodec/,.png]" />
- <param name="visible" value="!String.IsEmpty(MusicPlayer.Codec)" />
- </include>
- <include content="MediaFlag">
- <param name="texture" value="$INFO[MusicPlayer.Channels,flags/audiochannel/,.png]" />
- <param name="visible" value="!String.IsEmpty(MusicPlayer.Channels)" />
- </include>
- <control type="group">
- <visible>!String.IsEmpty(MusicPlayer.SampleRate)</visible>
- <width>115</width>
- <control type="label">
- <width>115</width>
- <height>60</height>
- <align>center</align>
- <aligny>center</aligny>
- <label>$INFO[MusicPlayer.SampleRate, ,kHz]</label>
- <font>font_flag</font>
- </control>
- <include content="MediaFlag">
- <param name="texture" value="flags/flag.png" />
- </include>
- </control>
- <control type="group">
- <visible>!String.IsEmpty(MusicPlayer.BitRate)</visible>
- <width>115</width>
- <control type="label">
- <width>115</width>
- <height>60</height>
- <align>center</align>
- <aligny>center</aligny>
- <label>$INFO[MusicPlayer.BitRate, ,kbps ]</label>
- <font>font_flag</font>
- </control>
- <include content="MediaFlag">
- <param name="texture" value="flags/flag.png" />
- </include>
- </control>
- <control type="group">
- <visible>!String.IsEmpty(MusicPlayer.BitsPerSample)</visible>
- <width>115</width>
- <control type="label">
- <width>115</width>
- <height>60</height>
- <align>center</align>
- <aligny>center</aligny>
- <label>$INFO[MusicPlayer.BitsPerSample, ,bit]</label>
- <font>font_flag</font>
- </control>
- <include content="MediaFlag">
- <param name="texture" value="flags/flag.png" />
- </include>
- </control>
+ <control type="group">
+ <top>195</top>
+ <include condition="!Skin.HasSetting(hide_mediaflags)">MediaFlags</include>
</control>
</control>
<control type="label">
<centerleft>50%</centerleft>
- <centertop>125</centertop>
+ <centertop>120</centertop>
<width>50%</width>
<height>60</height>
<align>center</align>
@@ -170,8 +69,8 @@
<control type="label" id="40000">
<centerleft>50%</centerleft>
<centertop>175</centertop>
- <width>50%</width>
- <height>50</height>
+ <width>380</width>
+ <height>60</height>
<align>center</align>
<aligny>top</aligny>
<label>$VAR[SeekLabel]</label>
diff --git a/addons/skin.estuary/xml/DialogVideoManager.xml b/addons/skin.estuary/xml/DialogVideoManager.xml
index a02f3398a4..ceb73694a9 100644
--- a/addons/skin.estuary/xml/DialogVideoManager.xml
+++ b/addons/skin.estuary/xml/DialogVideoManager.xml
@@ -84,7 +84,6 @@
<height>565</height>
<onleft condition="Integer.IsGreater(Container(50).NumItems,0)">100</onleft>
<itemgap>dialogbuttons_itemgap</itemgap>
- <align>top</align>
<scrolltime tween="quadratic">200</scrolltime>
<include content="DefaultDialogButton">
<param name="id" value="21" />
diff --git a/addons/skin.estuary/xml/GameOSD.xml b/addons/skin.estuary/xml/GameOSD.xml
index b2f5fd429e..1d4c0dc8b9 100644
--- a/addons/skin.estuary/xml/GameOSD.xml
+++ b/addons/skin.estuary/xml/GameOSD.xml
@@ -77,7 +77,6 @@
<control type="group" id="2000">
<top>80</top>
<control type="list" id="1103">
- <defaultcontrol always="true">2101</defaultcontrol>
<height>560</height>
<orientation>vertical</orientation>
<itemlayout condition="!Control.IsVisible(2200)" width="700" height="80">
diff --git a/addons/skin.estuary/xml/Home.xml b/addons/skin.estuary/xml/Home.xml
index 24d032df23..5ac611a33f 100644
--- a/addons/skin.estuary/xml/Home.xml
+++ b/addons/skin.estuary/xml/Home.xml
@@ -1120,10 +1120,7 @@
<height>70</height>
<animation effect="fade" start="0" end="100" time="300" delay="300">WindowOpen</animation>
<animation effect="fade" start="100" end="0" time="200">WindowClose</animation>
- <include condition="!Skin.HasSetting(hide_mediaflags)" content="MediaFlags">
- <param name="infolabel_prefix" value="Container." />
- <param name="resolution_var" value="$VAR[ContainerResolutionFlagVar]" />
- </include>
+ <include condition="!Skin.HasSetting(hide_mediaflags)">MediaFlags</include>
<control type="rss">
<animation effect="slide" end="0,90" time="300" tween="sine" easing="inout" condition="$EXP[infodialog_active]">conditional</animation>
<left>0</left>
diff --git a/addons/skin.estuary/xml/Includes.xml b/addons/skin.estuary/xml/Includes.xml
index 8dd226ec45..b649528a36 100644
--- a/addons/skin.estuary/xml/Includes.xml
+++ b/addons/skin.estuary/xml/Includes.xml
@@ -106,7 +106,7 @@
<font></font>
</include>
<include name="RatingCircle">
- <param name="animation">False</param>
+ <param name="animation">false</param>
<definition>
<control type="group">
<animation effect="fade" time="0" condition="$PARAM[animation]">VisibleChange</animation>
@@ -339,37 +339,47 @@
<include name="MediaFlag">
<param name="width">115</param>
<param name="height">60</param>
- <param name="visible">true</param>
+ <param name="flag_visible">true</param>
+ <param name="texture">flags/flag.png</param>
<definition>
- <control type="image">
+ <control type="group">
<width>$PARAM[width]</width>
<height>$PARAM[height]</height>
- <fadetime>0</fadetime>
- <aspectratio align="center" aligny="center">keep</aspectratio>
- <texture>$PARAM[texture]</texture>
- <visible>$PARAM[visible]</visible>
+ <visible>$PARAM[flag_visible]</visible>
+ <control type="image">
+ <fadetime>0</fadetime>
+ <align>center</align>
+ <aligny>center</aligny>
+ <aspectratio>keep</aspectratio>
+ <texture>$PARAM[texture]</texture>
+ </control>
+ <control type="label">
+ <align>center</align>
+ <aligny>center</aligny>
+ <label>$PARAM[flag_label]</label>
+ <font>font_flag</font>
+ </control>
</control>
</definition>
</include>
<include name="MediaFlags">
- <param name="infolabel_prefix"></param>
- <param name="resolution_var">$VAR[ResolutionFlagVar]</param>
+ <param name="visible">true</param>
<definition>
<control type="grouplist">
<visible>Window.IsActive(Home) | Window.IsActive(10025)</visible>
<orientation>horizontal</orientation>
<right>20</right>
- <top>0</top>
- <height>70</height>
+ <bottom>0</bottom>
+ <height>60</height>
<align>right</align>
<itemgap>10</itemgap>
<width>1900</width>
<usecontrolcoords>true</usecontrolcoords>
<control type="group">
<width>150</width>
- <visible>System.AddonIsEnabled(resource.images.studios.white) + !String.IsEmpty($PARAM[infolabel_prefix]ListItem.Studio)</visible>
+ <visible>System.AddonIsEnabled(resource.images.studios.white) + !String.IsEmpty(ListItem.Studio)</visible>
<include content="MediaFlag">
- <param name="texture" value="$INFO[$PARAM[infolabel_prefix]ListItem.Studio,resource://resource.images.studios.white/,.png]" />
+ <param name="texture" value="$INFO[ListItem.Studio,resource://resource.images.studios.white/,.png]" />
</include>
</control>
<control type="group">
@@ -378,154 +388,158 @@
<visible>!String.IsEmpty($PARAM[infolabel_prefix]ListItem.Premiered)</visible>
<include content="InfoFlag">
<param name="icon" value="lists/year.png" />
- <param name="label" value="$INFO[$PARAM[infolabel_prefix]ListItem.Premiered]" />
- </include>
- </control>
- <control type="group">
- <width>115</width>
- <visible>!String.IsEmpty($PARAM[infolabel_prefix]ListItem.Duration)</visible>
- <control type="label">
- <width>115</width>
- <height>60</height>
- <align>center</align>
- <aligny>center</aligny>
- <label>$INFO[$PARAM[infolabel_prefix]ListItem.Duration]</label>
- <font>font_flag</font>
- </control>
- <include content="MediaFlag">
- <param name="texture" value="flags/flag.png" />
+ <param name="label" value="$INFO[ListItem.Premiered]" />
</include>
</control>
<include content="MediaFlag">
- <param name="texture" value="$INFO[ListItem.VideoCodec,flags/videocodec/,.png]" />
- <param name="visible" value="!String.IsEmpty($PARAM[infolabel_prefix]ListItem.VideoCodec)" />
+ <param name="flag_visible" value="!String.IsEmpty(ListItem.Duration)" />
+ <param name="flag_label" value="$INFO[ListItem.Duration]" />
</include>
<include content="MediaFlag">
- <param name="texture" value="$PARAM[resolution_var]" />
- <param name="visible" value="!String.IsEmpty($PARAM[infolabel_prefix]ListItem.VideoResolution)" />
+ <param name="flag_visible" value="ListItem.IsStereoscopic" />
+ <param name="flag_label" value="3D" />
</include>
<include content="MediaFlag">
- <param name="texture" value="$INFO[ListItem.HdrType,flags/videohdr/,.png]" />
- <param name="visible" value="!String.IsEmpty($PARAM[infolabel_prefix]ListItem.HdrType)" />
+ <param name="flag_visible" value="!String.IsEmpty(ListItem.VideoCodec)" />
+ <param name="flag_label" value="$VAR[VideoListCodecVar]" />
</include>
<include content="MediaFlag">
- <param name="texture" value="$INFO[ListItem.VideoAspect,flags/aspectratio/,.png]" />
- <param name="visible" value="!String.IsEmpty($PARAM[infolabel_prefix]ListItem.VideoAspect)" />
+ <param name="flag_visible" value="!String.IsEmpty(ListItem.VideoResolution)" />
+ <param name="flag_label" value="$INFO[ListItem.VideoResolution]$VAR[VideoListResolutionTypeVar, ]" />
</include>
<include content="MediaFlag">
- <param name="texture" value="$INFO[$PARAM[infolabel_prefix]ListItem.AudioCodec,flags/audiocodec/,.png]" />
- <param name="visible" value="!String.IsEmpty($PARAM[infolabel_prefix]ListItem.AudioCodec)" />
+ <param name="flag_visible" value="!String.IsEmpty(ListItem.HdrType)" />
+ <param name="flag_label" value="$VAR[VideoListHDRVar]" />
</include>
<include content="MediaFlag">
- <param name="texture" value="$INFO[$PARAM[infolabel_prefix]ListItem.AudioChannels,flags/audiochannel/,.png]" />
- <param name="visible" value="!String.IsEmpty($PARAM[infolabel_prefix]ListItem.AudioChannels)" />
+ <param name="flag_visible" value="!String.IsEmpty(ListItem.VideoAspect)" />
+ <param name="flag_label" value="$INFO[ListItem.VideoAspect,,:1]" />
+ </include>
+ <include content="MediaFlag">
+ <param name="flag_visible" value="!String.IsEmpty(ListItem.AudioCodec)" />
+ <param name="flag_label" value="$VAR[AudioListCodecVar]" />
+ </include>
+ <include content="MediaFlag">
+ <param name="flag_visible" value="!String.IsEmpty(ListItem.AudioChannels)" />
+ <param name="flag_label" value="$VAR[AudioListChannelsVar]" />
+ </include>
+ </control>
+ <control type="grouplist">
+ <visible>[Player.ShowInfo | Window.IsActive(fullscreeninfo)] + !Player.ChannelPreviewActive + Window.IsActive(fullscreenvideo)</visible>
+ <orientation>horizontal</orientation>
+ <right>20</right>
+ <bottom>0</bottom>
+ <height>60</height>
+ <align>right</align>
+ <itemgap>10</itemgap>
+ <width>1900</width>
+ <usecontrolcoords>true</usecontrolcoords>
+ <include content="MediaFlag">
+ <param name="flag_visible" value="VideoPlayer.IsStereoscopic" />
+ <param name="flag_label" value="3D" />
+ </include>
+ <include content="MediaFlag">
+ <param name="flag_visible" value="!String.IsEmpty(VideoPlayer.VideoCodec)" />
+ <param name="flag_label" value="$VAR[VideoPlayerCodecVar]" />
+ </include>
+ <include content="MediaFlag">
+ <param name="flag_visible" value="!String.IsEmpty(VideoPlayer.VideoResolution)" />
+ <param name="flag_label" value="$INFO[VideoPlayer.VideoResolution]$VAR[VideoPlayerResolutionTypeVar, ]" />
+ </include>
+ <include content="MediaFlag">
+ <param name="flag_visible" value="!String.IsEmpty(VideoPlayer.HdrType)" />
+ <param name="flag_label" value="$VAR[VideoPlayerHDRVar]" />
+ </include>
+ <include content="MediaFlag">
+ <param name="flag_visible" value="!String.IsEmpty(VideoPlayer.VideoAspect)" />
+ <param name="flag_label" value="$INFO[VideoPlayer.VideoAspect,,:1]" />
+ </include>
+ <include content="MediaFlag">
+ <param name="flag_visible" value="!String.IsEmpty(VideoPlayer.AudioCodec)" />
+ <param name="flag_label" value="$VAR[VideoPlayerAudioCodecVar]" />
+ </include>
+ <include content="MediaFlag">
+ <param name="flag_visible" value="!String.IsEmpty(VideoPlayer.AudioChannels)" />
+ <param name="flag_label" value="$VAR[VideoPlayerAudioChannelsVar]" />
</include>
</control>
<control type="grouplist">
<visible>Container.Content(albums) | Window.IsActive(10502)</visible>
<orientation>horizontal</orientation>
<right>20</right>
- <top>0</top>
- <height>70</height>
+ <bottom>10</bottom>
+ <height>60</height>
<align>right</align>
<itemgap>10</itemgap>
<width>1900</width>
<usecontrolcoords>true</usecontrolcoords>
- <control type="group">
- <width>115</width>
- <visible>!String.IsEmpty($PARAM[infolabel_prefix]ListItem.Duration)</visible>
- <visible>Container.Content(albums) + ![Window.IsVisible(songinformation) | Window.IsVisible(musicinformation)]</visible>
- <control type="label">
- <width>110</width>
- <height>60</height>
- <align>center</align>
- <aligny>center</aligny>
- <label>$INFO[$PARAM[infolabel_prefix]ListItem.Duration]</label>
- <font>font_flag</font>
- </control>
- <include content="MediaFlag">
- <param name="texture" value="flags/flag.png" />
- </include>
- </control>
- <control type="group">
- <width>115</width>
- <visible>!String.IsEmpty($PARAM[infolabel_prefix]ListItem.FileExtension)</visible>
- <control type="label">
- <width>110</width>
- <height>60</height>
- <align>center</align>
- <aligny>center</aligny>
- <label>$INFO[$PARAM[infolabel_prefix]ListItem.FileExtension]</label>
- <font>font_flag</font>
- </control>
- <include content="MediaFlag">
- <param name="texture" value="flags/flag.png" />
- </include>
- </control>
<include content="MediaFlag">
- <param name="texture" value="$INFO[$PARAM[infolabel_prefix]ListItem.MusicChannels,flags/audiochannel/,.png]" />
- <param name="visible" value="!String.IsEmpty($PARAM[infolabel_prefix]ListItem.MusicChannels)" />
+ <param name="flag_visible" value="!String.IsEmpty(ListItem.Duration) + Container.Content(albums) + ![Window.IsVisible(songinformation) | Window.IsVisible(musicinformation)]" />
+ <param name="flag_label" value="$INFO[ListItem.Duration]" />
+ </include>
+ <include content="MediaFlag">
+ <param name="flag_visible" value="!String.IsEmpty(ListItem.FileExtension)" />
+ <param name="flag_label" value="$INFO[ListItem.FileExtension]" />
+ </include>
+ <include content="MediaFlag">
+ <param name="flag_visible" value="!String.IsEmpty(ListItem.MusicChannels)" />
+ <param name="flag_label" value="$VAR[MusicListChannelsVar]" />
+ </include>
+ <include content="MediaFlag">
+ <param name="flag_visible" value="!String.IsEmpty(ListItem.Samplerate)" />
+ <param name="flag_label" value="$INFO[ListItem.Samplerate, ,kHz]" />
+ </include>
+ <include content="MediaFlag">
+ <param name="flag_visible" value="!String.IsEmpty(ListItem.BitRate)" />
+ <param name="flag_label" value="$INFO[ListItem.BitRate, ,kbps]" />
+ </include>
+ <include content="MediaFlag">
+ <param name="flag_visible" value="!String.IsEmpty(ListItem.MusicBitsPerSample)" />
+ <param name="flag_label" value="$INFO[ListItem.MusicBitsPerSample, ,bit]" />
+ </include>
+ <include content="MediaFlag">
+ <param name="flag_visible" value="!String.IsEmpty(ListItem.BMP)" />
+ <param name="flag_label" value="$INFO[ListItem.BMP, ,bpm]" />
+ </include>
+ </control>
+ <control type="grouplist">
+ <visible>Player.ShowInfo + !Player.ChannelPreviewActive + Window.IsActive(visualisation)</visible>)
+ <orientation>horizontal</orientation>
+ <right>20</right>
+ <bottom>10</bottom>
+ <height>60</height>
+ <align>right</align>
+ <itemgap>10</itemgap>
+ <width>1900</width>
+ <usecontrolcoords>true</usecontrolcoords>
+ <include content="MediaFlag">
+ <param name="flag_visible" value="RDS.HasRDS" />
+ <param name="flag_label" value="RDS" />
+ </include>
+ <include content="MediaFlag">
+ <param name="flag_visible" value="!String.IsEmpty(MusicPlayer.Codec)" />
+ <param name="flag_label" value="$VAR[MusicPlayerCodecVar]" />
+ </include>
+ <include content="MediaFlag">
+ <param name="flag_visible" value="!String.IsEmpty(MusicPlayer.Channels)" />
+ <param name="flag_label" value="$VAR[MusicPlayerAudioChannelsVar]" />
+ </include>
+ <include content="MediaFlag">
+ <param name="flag_visible" value="!String.IsEmpty(MusicPlayer.SampleRate)" />
+ <param name="flag_label" value="$INFO[MusicPlayer.SampleRate, ,kHz]" />
+ </include>
+ <include content="MediaFlag">
+ <param name="flag_visible" value="!String.IsEmpty(MusicPlayer.BitRate)" />
+ <param name="flag_label" value="$INFO[MusicPlayer.BitRate, ,kbps]" />
+ </include>
+ <include content="MediaFlag">
+ <param name="flag_visible" value="!String.IsEmpty(MusicPlayer.BitsPerSample)" />
+ <param name="flag_label" value="$INFO[MusicPlayer.BitsPerSample, ,bit]" />
+ </include>
+ <include content="MediaFlag">
+ <param name="flag_visible" value="!String.IsEmpty(MusicPlayer.BMP)" />
+ <param name="flag_label" value="$INFO[MusicPlayer.BMP, ,bpm]" />
</include>
- <control type="group">
- <visible>!String.IsEmpty(ListItem.Samplerate)</visible>
- <width>115</width>
- <control type="label">
- <width>110</width>
- <height>60</height>
- <align>center</align>
- <aligny>center</aligny>
- <label>$INFO[ListItem.Samplerate, ,kHz]</label>
- <font>font_flag</font>
- </control>
- <include content="MediaFlag">
- <param name="texture" value="flags/flag.png" />
- </include>
- </control>
- <control type="group">
- <visible>!String.IsEmpty(ListItem.BitRate)</visible>
- <width>115</width>
- <control type="label">
- <width>110</width>
- <height>60</height>
- <align>center</align>
- <aligny>center</aligny>
- <label>$INFO[ListItem.BitRate, ,kbps]</label>
- <font>font_flag</font>
- </control>
- <include content="MediaFlag">
- <param name="texture" value="flags/flag.png" />
- </include>
- </control>
- <control type="group">
- <visible>!String.IsEmpty(ListItem.MusicBitsPerSample)</visible>
- <width>115</width>
- <control type="label">
- <width>110</width>
- <height>60</height>
- <align>center</align>
- <aligny>center</aligny>
- <label>$INFO[ListItem.MusicBitsPerSample, ,bit]</label>
- <font>font_flag</font>
- </control>
- <include content="MediaFlag">
- <param name="texture" value="flags/flag.png" />
- </include>
- </control>
- <control type="group">
- <visible>!String.IsEmpty(ListItem.BMP)</visible>
- <width>115</width>
- <control type="label">
- <width>110</width>
- <height>60</height>
- <align>center</align>
- <aligny>center</aligny>
- <label>$INFO[ListItem.BMP, ,bpm]</label>
- <font>font_flag</font>
- </control>
- <include content="MediaFlag">
- <param name="texture" value="flags/flag.png" />
- </include>
- </control>
</control>
</definition>
</include>
@@ -1206,7 +1220,7 @@
<label>$INFO[Container.FolderName, / ]</label>
<include>BreadcrumbsLabel</include>
<visible>![Container.Content() + Window.IsActive(videos)]</visible>
- <visible>![Window.IsActive(MyPVRChannels.xml) | Window.IsActive(MyPVRTimers.xml) | Window.IsActive(MyPVRRecordings.xml) | Window.IsActive(MyPVRSearch.xml)]</visible>
+ <visible>![Window.IsActive(MyPVRChannels.xml) | Window.IsActive(MyPVRTimers.xml) | Window.IsActive(MyPVRRecordings.xml) | Window.IsActive(MyPVRSearch.xml) | Window.IsActive(MyPVRProviders.xml)]</visible>
</control>
<control type="label">
<label>$INFO[Container.PluginCategory, / ]</label>
@@ -1299,7 +1313,7 @@
</definition>
</include>
<include name="BottomBar">
- <param name="info_visible">False</param>
+ <param name="info_visible">false</param>
<definition>
<control type="group">
<animation effect="slide" end="0,112" time="300" tween="sine" easing="inout" condition="$EXP[infodialog_active]">conditional</animation>
diff --git a/addons/skin.estuary/xml/Includes_Animations.xml b/addons/skin.estuary/xml/Includes_Animations.xml
index 1b02def93b..a317b31c9c 100644
--- a/addons/skin.estuary/xml/Includes_Animations.xml
+++ b/addons/skin.estuary/xml/Includes_Animations.xml
@@ -54,31 +54,31 @@
<include condition="!Skin.HasSetting(no_slide_animations)">Vis_FadeSlide_Right</include>
</include>
<include name="Animation_TopSlide">
- <animation type="WindowOpen" reversible="False">
+ <animation type="WindowOpen" reversible="false">
<effect type="fade" start="0" end="100" time="300"/>
<effect type="slide" start="0,-200" end="0,0" time="300" tween="cubic" easing="out" />
</animation>
- <animation type="WindowClose" reversible="False">
+ <animation type="WindowClose" reversible="false">
<effect type="fade" start="100" end="0" time="300"/>
<effect type="slide" start="0,0" end="0,-200" time="300" tween="cubic" easing="out" />
</animation>
</include>
<include name="Animation_BottomSlide">
- <animation type="WindowOpen" reversible="False">
+ <animation type="WindowOpen" reversible="false">
<effect type="fade" start="0" end="100" time="300"/>
<effect type="slide" start="0,200" end="0,0" time="300" tween="cubic" easing="out" />
</animation>
- <animation type="WindowClose" reversible="False">
+ <animation type="WindowClose" reversible="false">
<effect type="fade" start="100" end="0" time="300"/>
<effect type="slide" start="0,0" end="0,200" time="300" tween="cubic" easing="out" />
</animation>
</include>
<include name="Vis_FadeSlide_Right">
- <animation type="Visible" reversible="False">
+ <animation type="Visible" reversible="false">
<effect type="fade" start="0" end="100" time="300" tween="sine" easing="out"/>
<effect type="slide" start="320" end="0" time="400" tween="cubic" easing="out" />
</animation>
- <animation type="Hidden" reversible="False">
+ <animation type="Hidden" reversible="false">
<effect type="fade" start="100" end="0" time="300" tween="sine" easing="out" />
<effect type="slide" start="0" end="320" time="300" tween="cubic" easing="out" />
</animation>
@@ -88,11 +88,11 @@
<include condition="!Skin.HasSetting(no_slide_animations)">Vis_FadeSlide_Left</include>
</include>
<include name="Vis_FadeSlide_Left">
- <animation type="Visible" reversible="False">
+ <animation type="Visible" reversible="false">
<effect type="fade" start="0" end="100" time="300" tween="sine" easing="out" />
<effect type="slide" start="-320" end="0" time="400" tween="cubic" easing="out" />
</animation>
- <animation type="Hidden" reversible="False">
+ <animation type="Hidden" reversible="false">
<effect type="fade" start="100" end="0" time="300" tween="sine" easing="out" />
<effect type="slide" start="0" end="-320" time="300" tween="cubic" easing="out" />
</animation>
diff --git a/addons/skin.estuary/xml/Includes_Buttons.xml b/addons/skin.estuary/xml/Includes_Buttons.xml
index e7deab53c4..f8ace0d34d 100644
--- a/addons/skin.estuary/xml/Includes_Buttons.xml
+++ b/addons/skin.estuary/xml/Includes_Buttons.xml
@@ -126,36 +126,6 @@
</control>
</definition>
</include>
- <include name="DialogToggleButton">
- <param name="width">300</param>
- <param name="height">100</param>
- <param name="wrapmultiline">false</param>
- <param name="font">font25_title</param>
- <param name="onclick"></param>
- <param name="visible">true</param>
- <param name="enable">true</param>
- <param name="usealttexture">false</param>
- <definition>
- <control type="togglebutton" id="$PARAM[id]">
- <width>$PARAM[width]</width>
- <height>$PARAM[height]</height>
- <label>$PARAM[label]</label>
- <font>$PARAM[font]</font>
- <textoffsetx>20</textoffsetx>
- <onclick>noop</onclick>
- <wrapmultiline>$PARAM[wrapmultiline]</wrapmultiline>
- <align>center</align>
- <aligny>center</aligny>
- <texturefocus border="40" colordiffuse="button_focus">buttons/dialogbutton-fo.png</texturefocus>
- <texturenofocus border="40">buttons/dialogbutton-nofo.png</texturenofocus>
- <alttexturefocus border="40" colordiffuse="button_focus">buttons/dialogbutton-fo.png</alttexturefocus>
- <alttexturenofocus border="40" colordiffuse="button_alt_focus">buttons/dialogbutton-fo.png</alttexturenofocus>
- <usealttexture>$PARAM[usealttexture]</usealttexture>
- <visible>$PARAM[visible]</visible>
- <enable>$PARAM[enable]</enable>
- </control>
- </definition>
- </include>
<include name="KeyboardButton">
<width>120</width>
<height>120</height>
diff --git a/addons/skin.estuary/xml/Includes_DialogSelect.xml b/addons/skin.estuary/xml/Includes_DialogSelect.xml
index 3ee9240ba3..7ca4f0d459 100644
--- a/addons/skin.estuary/xml/Includes_DialogSelect.xml
+++ b/addons/skin.estuary/xml/Includes_DialogSelect.xml
@@ -61,7 +61,7 @@
<top>22</top>
<width>80</width>
<height>80</height>
- <aspectratio align="top">keep</aspectratio>
+ <aspectratio>keep</aspectratio>
<aligny>center</aligny>
<texture>icons/pvr/timers/recording.png</texture>
<visible>ListItem.Property(PVR.IsRecordingTimer)</visible>
@@ -71,7 +71,7 @@
<top>22</top>
<width>80</width>
<height>80</height>
- <aspectratio align="top">keep</aspectratio>
+ <aspectratio>keep</aspectratio>
<aligny>center</aligny>
<texture>icons/pvr/timers/bell.png</texture>
<visible>ListItem.Property(PVR.IsRemindingTimer)</visible>
@@ -118,7 +118,7 @@
<top>22</top>
<width>80</width>
<height>80</height>
- <aspectratio align="top">keep</aspectratio>
+ <aspectratio>keep</aspectratio>
<aligny>center</aligny>
<texture>icons/pvr/timers/recording.png</texture>
<visible>ListItem.Property(PVR.IsRecordingTimer)</visible>
@@ -128,7 +128,7 @@
<top>22</top>
<width>80</width>
<height>80</height>
- <aspectratio align="top">keep</aspectratio>
+ <aspectratio>keep</aspectratio>
<aligny>center</aligny>
<texture>icons/pvr/timers/bell.png</texture>
<visible>ListItem.Property(PVR.IsRemindingTimer)</visible>
@@ -342,7 +342,6 @@
<description>Label for Saved with: text</description>
<top>14</top>
<height>20</height>
- <font>font16</font>
<shadowcolor>text_shadow</shadowcolor>
<label>35255</label>
<align>center</align>
diff --git a/addons/skin.estuary/xml/Includes_PVR.xml b/addons/skin.estuary/xml/Includes_PVR.xml
index c482d2b708..75840d8b1c 100644
--- a/addons/skin.estuary/xml/Includes_PVR.xml
+++ b/addons/skin.estuary/xml/Includes_PVR.xml
@@ -108,8 +108,6 @@
<top>66</top>
<width>70</width>
<height>12</height>
- <align>center</align>
- <aligny>center</aligny>
<colordiffuse>88FFFFFF</colordiffuse>
<visible>ListItem.HasEpg + !$PARAM[has_info_icon]</visible>
<info>ListItem.Progress</info>
@@ -140,8 +138,6 @@
<top>66</top>
<width>70</width>
<height>12</height>
- <align>center</align>
- <aligny>center</aligny>
<midtexture border="3">progress/texturebg_white.png</midtexture>
<visible>ListItem.HasEpg + !$PARAM[has_info_icon]</visible>
<info>ListItem.Progress</info>
@@ -440,7 +436,7 @@
<top>465</top>
<width>830</width>
<bottom>list_bottom_offset</bottom>
- <label>$VAR[PVRInstanceName,,[CR]]$VAR[FlagLabel,,[CR]]$INFO[ListItem.Genre,[COLOR grey]$LOCALIZE[515]:[/COLOR] ,[CR]]$INFO[ListItem.ParentalRatingCode,[COLOR grey]$LOCALIZE[31017]: [/COLOR],[CR]]$INFO[ListItem.TimerType,[COLOR grey]$LOCALIZE[803]:[/COLOR] ,[CR]]$VAR[RecordingSizeLabel]$VAR[ExpirationDateTimeLabel]$INFO[ListItem.Plot,[CR]]</label>
+ <label>$VAR[PVRInstanceName,,[CR]]$INFO[ListItem.MediaProviders,[COLOR grey]$LOCALIZE[19334]:[/COLOR] ,[CR]]$VAR[FlagLabel,,[CR]]$INFO[ListItem.Genre,[COLOR grey]$LOCALIZE[515]:[/COLOR] ,[CR]]$INFO[ListItem.ParentalRatingCode,[COLOR grey]$LOCALIZE[31017]: [/COLOR],[CR]]$INFO[ListItem.TimerType,[COLOR grey]$LOCALIZE[803]:[/COLOR] ,[CR]]$VAR[RecordingSizeLabel]$VAR[ExpirationDateTimeLabel]$INFO[ListItem.Plot,[CR]]</label>
<autoscroll delay="10000" time="3000" repeat="10000">Skin.HasSetting(AutoScroll)</autoscroll>
</control>
</control>
@@ -466,25 +462,45 @@
<orientation>vertical</orientation>
<focusedlayout height="100" width="780">
<control type="label">
+ <visible>!String.IsEqual(ListItem.ChannelName, ListItem.Label)</visible>
+ <left>10</left>
+ <height>90</height>
+ <width>830</width>
+ <aligny>center</aligny>
+ <label>$VAR[RecordingDateSizeLabel]$INFO[ListItem.Label]$VAR[SeasonEpisodeLabel]$INFO[ListItem.EpisodeName, (,)]$INFO[ListItem.Property(totalcount), (, $LOCALIZE[31036])]</label>
+ <shadowcolor>text_shadow</shadowcolor>
+ </control>
+ <control type="label">
+ <visible>String.IsEqual(ListItem.ChannelName, ListItem.Label)</visible>
<left>10</left>
<height>90</height>
<width>830</width>
<aligny>center</aligny>
- <label>$VAR[RecordingDateSizeLabel]$INFO[ListItem.Label]$INFO[ListItem.EpisodeName, (,)]</label>
+ <label>[COLOR grey]$INFO[ListItem.ChannelName,,[CR]][/COLOR]$INFO[ListItem.EpgEventTitle]$VAR[SeasonEpisodeLabel]$INFO[ListItem.EpisodeName, (,)]</label>
<shadowcolor>text_shadow</shadowcolor>
</control>
</focusedlayout>
<itemlayout height="100" width="780">
<control type="label">
+ <visible>!String.IsEqual(ListItem.ChannelName, ListItem.Label)</visible>
+ <left>10</left>
+ <height>90</height>
+ <width>830</width>
+ <aligny>center</aligny>
+ <label>$VAR[RecordingDateSizeLabel]$INFO[ListItem.Label]$VAR[SeasonEpisodeLabel]$INFO[ListItem.EpisodeName, (,)]$INFO[ListItem.Property(totalcount), (, $LOCALIZE[31036])]</label>
+ <shadowcolor>text_shadow</shadowcolor>
+ </control>
+ <control type="label">
+ <visible>String.IsEqual(ListItem.ChannelName, ListItem.Label)</visible>
<left>10</left>
<height>90</height>
<width>830</width>
<aligny>center</aligny>
- <label>$VAR[RecordingDateSizeLabel]$INFO[ListItem.Label]$INFO[ListItem.EpisodeName, (,)]</label>
+ <label>[COLOR grey]$INFO[ListItem.ChannelName,,[CR]][/COLOR]$INFO[ListItem.EpgEventTitle]$VAR[SeasonEpisodeLabel]$INFO[ListItem.EpisodeName, (,)]</label>
<shadowcolor>text_shadow</shadowcolor>
</control>
</itemlayout>
- <content sortby="date" sortorder="$PARAM[folder_sortorder]">$INFO[ListItem.FilenameAndPath]</content>
+ <content sortby="$PARAM[folder_sortby]" sortorder="$PARAM[folder_sortorder]">$INFO[ListItem.FilenameAndPath]</content>
</control>
</control>
</control>
@@ -541,7 +557,7 @@
<top>35</top>
<width>100%</width>
<height>30</height>
- <label>$VAR[FlagDashLabel][I][COLOR grey]$VAR[SeasonEpisodeLabel][/COLOR]$INFO[ListItem.EpisodeName,[COLOR white],[/COLOR]][/I]</label>
+ <label>$VAR[FlagDashLabel]$VAR[SeasonEpisodeLabel,[COLOR grey],[/COLOR]]$INFO[ListItem.EpisodeName]</label>
</control>
</control>
<control type="textbox">
diff --git a/addons/skin.estuary/xml/MusicOSD.xml b/addons/skin.estuary/xml/MusicOSD.xml
index 9b97121a19..0fd35b92e4 100644
--- a/addons/skin.estuary/xml/MusicOSD.xml
+++ b/addons/skin.estuary/xml/MusicOSD.xml
@@ -260,18 +260,18 @@
<control type="group">
<bottom>0</bottom>
<height>120</height>
- <animation type="WindowOpen" condition="!Player.ShowInfo" reversible="False">
+ <animation type="WindowOpen" condition="!Player.ShowInfo" reversible="false">
<effect type="fade" start="0" end="100" time="300"/>
<effect type="slide" start="0,200" end="0,0" time="300" tween="cubic" easing="out" />
</animation>
- <animation type="WindowClose" condition="!Player.ShowInfo" reversible="False">
+ <animation type="WindowClose" condition="!Player.ShowInfo" reversible="false">
<effect type="fade" start="100" end="0" time="300"/>
<effect type="slide" start="0,0" end="0,200" time="300" tween="cubic" easing="out" />
</animation>
- <animation type="WindowOpen" condition="Player.ShowInfo" reversible="False">
+ <animation type="WindowOpen" condition="Player.ShowInfo" reversible="false">
<effect type="fade" start="0" end="100" time="300"/>
</animation>
- <animation type="WindowClose" condition="Player.ShowInfo" reversible="False">
+ <animation type="WindowClose" condition="Player.ShowInfo" reversible="false">
<effect type="fade" start="100" end="0" time="300"/>
</animation>
<control type="button" id="87">
diff --git a/addons/skin.estuary/xml/MyPVRProviders.xml b/addons/skin.estuary/xml/MyPVRProviders.xml
new file mode 100644
index 0000000000..eeff33a56f
--- /dev/null
+++ b/addons/skin.estuary/xml/MyPVRProviders.xml
@@ -0,0 +1,78 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<window>
+ <defaultcontrol always="true">50</defaultcontrol>
+ <backgroundcolor>background</backgroundcolor>
+ <views>50</views>
+ <menucontrol>9000</menucontrol>
+ <controls>
+ <include>DefaultBackground</include>
+ <control type="group">
+ <animation effect="fade" start="100" end="0" time="200" tween="sine" condition="$EXP[infodialog_active]">Conditional</animation>
+ <control type="group">
+ <include>OpenClose_Left</include>
+ <control type="fixedlist" id="50">
+ <left>0</left>
+ <top>list_top_offset</top>
+ <right>918</right>
+ <bottom>list_bottom_offset</bottom>
+ <onleft>9000</onleft>
+ <onright>73</onright>
+ <onup>50</onup>
+ <ondown>50</ondown>
+ <movement>4</movement>
+ <focusposition>4</focusposition>
+ <pagecontrol>73</pagecontrol>
+ <scrolltime tween="cubic" easing="out">500</scrolltime>
+ <include content="PVRListItemLayouts">
+ <param name="list_id" value="50" />
+ <param name="label1" value="$INFO[ListItem.Label]" />
+ </include>
+ </control>
+ </control>
+ <control type="group">
+ <depth>DepthContentPanel</depth>
+ <include>OpenClose_Right</include>
+ <width>870</width>
+ <right>0</right>
+ <include content="ContentPanel">
+ <param name="left" value="-72" />
+ <param name="width" value="970" />
+ <param name="top" value="-20" />
+ <param name="flipx" value="true" />
+ </include>
+ <control type="scrollbar" id="73">
+ <left>-50</left>
+ <top>list_top_offset</top>
+ <width>12</width>
+ <bottom>list_bottom_offset</bottom>
+ <onleft>50</onleft>
+ <onright>50</onright>
+ <orientation>vertical</orientation>
+ <animation effect="zoom" start="100,100" end="50,100" center="-50,0" time="300" tween="sine" easing="inout" condition="!Control.HasFocus(73)">conditional</animation>
+ </control>
+ <include content="PVRInfoPanel">
+ <param name="folder_sortby" value="label" />
+ <param name="folder_sortorder" value="ascending" />
+ </include>
+ </control>
+ <include content="TopBar">
+ <param name="breadcrumbs_label" value="$VAR[BreadcrumbsPVRProvidersVar]" />
+ </include>
+ <include content="BottomBar">
+ <param name="info_visible" value="true" />
+ </include>
+ <control type="group">
+ <include>MediaMenuCommon</include>
+ <include>PVRSideBar</include>
+ </control>
+ </control>
+ <control type="label" id="29">
+ <font></font>
+ <include>HiddenObject</include>
+ </control>
+ <control type="label" id="30">
+ <font></font>
+ <include>HiddenObject</include>
+ </control>
+ </controls>
+</window>
diff --git a/addons/skin.estuary/xml/MyPVRRecordings.xml b/addons/skin.estuary/xml/MyPVRRecordings.xml
index 020f1ff093..bc45ed139f 100644
--- a/addons/skin.estuary/xml/MyPVRRecordings.xml
+++ b/addons/skin.estuary/xml/MyPVRRecordings.xml
@@ -54,6 +54,7 @@
<animation effect="zoom" start="100,100" end="50,100" center="-50,0" time="300" tween="sine" easing="inout" condition="!Control.HasFocus(73)">conditional</animation>
</control>
<include content="PVRInfoPanel">
+ <param name="folder_sortby" value="date" />
<param name="folder_sortorder" value="descending" />
</include>
</control>
diff --git a/addons/skin.estuary/xml/MyPVRTimers.xml b/addons/skin.estuary/xml/MyPVRTimers.xml
index 9f36dd5ff5..fdd2929b09 100644
--- a/addons/skin.estuary/xml/MyPVRTimers.xml
+++ b/addons/skin.estuary/xml/MyPVRTimers.xml
@@ -54,6 +54,7 @@
<animation effect="zoom" start="100,100" end="50,100" center="-50,0" time="300" tween="sine" easing="inout" condition="!Control.HasFocus(73)">conditional</animation>
</control>
<include content="PVRInfoPanel">
+ <param name="folder_sortby" value="date" />
<param name="folder_sortorder" value="ascending" />
</include>
</control>
diff --git a/addons/skin.estuary/xml/SettingsCategory.xml b/addons/skin.estuary/xml/SettingsCategory.xml
index c14b63584b..cd305a1682 100644
--- a/addons/skin.estuary/xml/SettingsCategory.xml
+++ b/addons/skin.estuary/xml/SettingsCategory.xml
@@ -184,7 +184,7 @@
</control>
<control type="label" id="2">
<description>breadcrumbs label</description>
- <visible>False</visible>
+ <visible>false</visible>
</control>
</controls>
</window>
diff --git a/addons/skin.estuary/xml/Variables.xml b/addons/skin.estuary/xml/Variables.xml
index 6a9b291a76..fb320882e8 100644
--- a/addons/skin.estuary/xml/Variables.xml
+++ b/addons/skin.estuary/xml/Variables.xml
@@ -83,7 +83,7 @@
<value condition="ListItem.HasVideoVersions + Window.IsActive(videoplaylist)">$INFO[ListItem.Label]$INFO[ListItem.VideoVersionName, [I](,)[/I]]</value>
<value condition="String.IsEqual(ListItem.DbType,episode) + Window.IsActive(videoplaylist)">$INFO[ListItem.TVShowtitle,,: ]$INFO[ListItem.Season,,x]$INFO[ListItem.Episode,,. ]$INFO[ListItem.Title]</value>
<value condition="String.IsEqual(ListItem.DbType,musicvideo) + Window.IsActive(videoplaylist)">$INFO[ListItem.Artist,, - ]$INFO[ListItem.Title]</value>
- <value condition="[!String.IsEmpty(ListItem.Season) | !String.IsEmpty(ListItem.Episode) | !String.IsEmpty(ListItem.EpisodeName)] + Window.IsActive(videoplaylist)">$INFO[ListItem.Title,,: ]$VAR[SeasonEpisodeLabel]$INFO[ListItem.EpisodeName]</value>
+ <value condition="[!String.IsEmpty(ListItem.Season) | !String.IsEmpty(ListItem.Episode) | !String.IsEmpty(ListItem.EpisodeName)] + Window.IsActive(videoplaylist)">$INFO[ListItem.Title,,: ]$VAR[SeasonEpisodeLabel]</value>
<value>$INFO[ListItem.Label]</value>
</variable>
<variable name="ListLabel2Var">
@@ -414,9 +414,9 @@
<variable name="OSDSubLabelVar">
<value condition="Window.IsActive(visualisation) + Integer.IsGreater(Playlist.Length(music),1) + Integer.IsGreater(Playlist.Position(music),0)">$LOCALIZE[554] $INFO[Playlist.Position] / $INFO[Playlist.Length]</value>
<value condition="VideoPlayer.Content(musicvideos)">$VAR[NowPlayingSublabelVar,,[CR]]$INFO[player.chapter,[COLOR button_focus]$LOCALIZE[21396]: [/COLOR]]$INFO[Player.ChapterCount,/]$INFO[Player.ChapterName, - ]</value>
- <value condition="VideoPlayer.Content(episodes) + !player.chaptercount">$INFO[VideoPlayer.Season,[COLOR button_focus][CAPITALIZE]$LOCALIZE[36906][/CAPITALIZE]:[/COLOR] S]$INFO[VideoPlayer.Episode,E,: ]$INFO[VideoPlayer.Title]</value>
- <value condition="VideoPlayer.Content(episodes) + player.chaptercount">$INFO[VideoPlayer.Season,[COLOR button_focus][CAPITALIZE]$LOCALIZE[36906][/CAPITALIZE]:[/COLOR] S]$INFO[VideoPlayer.Episode,E, - ]$INFO[VideoPlayer.Title,,[CR]]$INFO[player.chapter,[COLOR button_focus]$LOCALIZE[21396]:[/COLOR] ]$INFO[Player.ChapterCount,/]$INFO[Player.ChapterName, - ]</value>
- <value condition="VideoPlayer.Content(LiveTV) | PVR.IsPlayingRecording | PVR.IsPlayingEpgTag">$INFO[VideoPlayer.Season,[COLOR button_focus][CAPITALIZE]$LOCALIZE[36906][/CAPITALIZE]:[/COLOR] S]$INFO[VideoPlayer.Episode,E, - ]$INFO[VideoPlayer.EpisodeName]</value>
+ <value condition="VideoPlayer.Content(episodes) + !player.chaptercount + ![String.IsEmpty(VideoPlayer.Episode) | String.IsEmpty(VideoPlayer.Title)]">[COLOR button_focus][CAPITALIZE]$LOCALIZE[36906]:[/CAPITALIZE][/COLOR] $INFO[VideoPlayer.Season,S]$INFO[VideoPlayer.Episode,E]$INFO[VideoPlayer.Title, - ]</value>
+ <value condition="VideoPlayer.Content(episodes) + player.chaptercount + ![String.IsEmpty(VideoPlayer.Episode) | String.IsEmpty(VideoPlayer.Title)]">[COLOR button_focus][CAPITALIZE]$LOCALIZE[36906]:[/CAPITALIZE][/COLOR] $INFO[VideoPlayer.Season,S]$INFO[VideoPlayer.Episode,E]$INFO[VideoPlayer.Title, - ,[CR]]$INFO[player.chapter,[COLOR button_focus]$LOCALIZE[21396]:[/COLOR] ]$INFO[Player.ChapterCount,/]$INFO[Player.ChapterName, - ]</value>
+ <value condition="[VideoPlayer.Content(LiveTV) | PVR.IsPlayingRecording | PVR.IsPlayingEpgTag] ">$VAR[VideoPlayerSeasonEpisodeLabel,[COLOR button_focus][CAPITALIZE]$LOCALIZE[36906]:[/CAPITALIZE][/COLOR]]</value>
<value condition="player.chaptercount + [!VideoPlayer.Content(episodes) + !VideoPlayer.Content(LiveTV)]">$INFO[player.chapter,[COLOR button_focus]$LOCALIZE[21396]:[/COLOR] ]$INFO[Player.ChapterCount,/]$INFO[Player.ChapterName, - ]</value>
<value>$INFO[VideoPlayer.Genre]</value>
</variable>
@@ -511,18 +511,18 @@
<value>$LOCALIZE[3]</value>
</variable>
<variable name="BreadcrumbsPVRChannelsVar">
- <value condition="Window.IsActive(TVChannels)">$LOCALIZE[19020] / $LOCALIZE[19019] / $INFO[Control.GetLabel(29)]</value>
- <value>$LOCALIZE[19021] / $LOCALIZE[19019] / $INFO[Control.GetLabel(29)]</value>
+ <value condition="Window.IsActive(TVChannels)">$LOCALIZE[19020] / $LOCALIZE[19019] / $INFO[Control.GetLabel(29)]$INFO[Control.GetLabel(30), - ]</value>
+ <value>$LOCALIZE[19021] / $LOCALIZE[19019] / $INFO[Control.GetLabel(29)]$INFO[Control.GetLabel(30), - ]</value>
</variable>
<variable name="BreadcrumbsPVRGuideVar">
<value condition="Window.IsActive(TVGuide)">$LOCALIZE[19020] / $LOCALIZE[19069] / $INFO[Control.GetLabel(30)]</value>
<value>$LOCALIZE[19021] / $LOCALIZE[19069] / $INFO[Control.GetLabel(30)]</value>
</variable>
<variable name="BreadcrumbsPVRRecordingsVar">
- <value condition="Window.IsActive(TVRecordings) + String.Contains(Control.GetLabel(7),*)">$LOCALIZE[19020] / $LOCALIZE[19017]$INFO[Control.GetLabel(30), / ] - $LOCALIZE[19179]</value>
- <value condition="Window.IsActive(TVRecordings) + !String.Contains(Control.GetLabel(7),*)">$LOCALIZE[19020] / $LOCALIZE[19017]$INFO[Control.GetLabel(30), / ]</value>
- <value condition="Window.IsActive(RadioRecordings) + String.Contains(Control.GetLabel(7),*)">$LOCALIZE[19021] / $LOCALIZE[19017]$INFO[Control.GetLabel(30), / ] - $LOCALIZE[19179]</value>
- <value>$LOCALIZE[19021] / $LOCALIZE[19017]$INFO[Control.GetLabel(30), / ]</value>
+ <value condition="Window.IsActive(TVRecordings) + String.Contains(Control.GetLabel(7),*)">$LOCALIZE[19020] / $LOCALIZE[19017]$INFO[Control.GetLabel(30), / ]$INFO[Control.GetLabel(29), - ] - $LOCALIZE[19179]</value>
+ <value condition="Window.IsActive(TVRecordings) + !String.Contains(Control.GetLabel(7),*)">$LOCALIZE[19020] / $LOCALIZE[19017]$INFO[Control.GetLabel(30), / ]$INFO[Control.GetLabel(29), - ]</value>
+ <value condition="Window.IsActive(RadioRecordings) + String.Contains(Control.GetLabel(7),*)">$LOCALIZE[19021] / $LOCALIZE[19017]$INFO[Control.GetLabel(30), / ]$INFO[Control.GetLabel(29), - ] - $LOCALIZE[19179]</value>
+ <value>$LOCALIZE[19021] / $LOCALIZE[19017]$INFO[Control.GetLabel(30), / ]$INFO[Control.GetLabel(29), - ]</value>
</variable>
<variable name="BreadcrumbsPVRTimersVar">
<value condition="Window.IsActive(TVTimers)">$LOCALIZE[19020] / $LOCALIZE[19040]</value>
@@ -530,6 +530,10 @@
<value condition="Window.IsActive(TVTimerRules)">$LOCALIZE[19020] / $LOCALIZE[19138]$INFO[Control.GetLabel(29), / ]</value>
<value>$LOCALIZE[19021] / $LOCALIZE[19138]$INFO[Control.GetLabel(29), / ]</value>
</variable>
+ <variable name="BreadcrumbsPVRProvidersVar">
+ <value condition="Window.IsActive(TVProviders)">$LOCALIZE[19020] / $LOCALIZE[19334]$INFO[Control.GetLabel(29), / ]</value>
+ <value condition="Window.IsActive(RadioProviders)">$LOCALIZE[19021] / $LOCALIZE[19334]$INFO[Control.GetLabel(29), / ]</value>
+ </variable>
<variable name="BreadcrumbsPVRSearchVar">
<value condition="Window.IsActive(TVSearch)">$LOCALIZE[19020] / $LOCALIZE[137]$INFO[Control.GetLabel(29), / ]$INFO[Control.GetLabel(30), ]</value>
<value>$LOCALIZE[19021] / $LOCALIZE[137]$INFO[Control.GetLabel(29), / ]$INFO[Control.GetLabel(30), ]</value>
@@ -542,10 +546,6 @@
<variable name="BreadcrumbsGameVar">
<value>$LOCALIZE[15016]</value>
</variable>
- <variable name="RepeatButtonColordiffuseVar">
- <value condition="Control.HasFocus(704)">button_focus</value>
- <value>FFFFFFFF</value>
- </variable>
<variable name="PVRChannelMgrHeader">
<value condition="!String.IsEmpty(Window.Property(IsRadio))">$LOCALIZE[19199] - $LOCALIZE[19024]</value>
<value>$LOCALIZE[19199] - $LOCALIZE[19023]</value>
@@ -586,8 +586,10 @@
<value condition="Player.HasAudio">[COLOR grey]$INFO[MusicPlayer.Album][/COLOR]$INFO[MusicPlayer.Year, [,] ]</value>
</variable>
<variable name="PlayerLabel3">
+ <value condition="VideoPlayer.Content(livetv) | PVR.IsPlayingRecording + !String.IsEmpty(VideoPlayer.Episode) + String.IsEmpty(VideoPlayer.EpisodePart)">$INFO[VideoPlayer.Season,$LOCALIZE[20373]: , ]$INFO[VideoPlayer.Episode,$LOCALIZE[20359]: ]</value>
+ <value condition="VideoPlayer.Content(livetv) | PVR.IsPlayingRecording + !String.IsEmpty(VideoPlayer.Episode) + !String.IsEmpty(VideoPlayer.EpisodePart)">$INFO[VideoPlayer.Season,$LOCALIZE[20373]: , ]$INFO[VideoPlayer.Episode,$LOCALIZE[20359]: ]$INFO[VideoPlayer.EpisodePart,/]</value>
+ <value condition="VideoPlayer.Content(movies) | VideoPlayer.Content(livetv) + String.IsEmpty(VideoPlayer.Episode)">$INFO[VideoPlayer.Genre]</value>
<value condition="VideoPlayer.Content(episodes)">$INFO[VideoPlayer.TvShowTitle]</value>
- <value condition="VideoPlayer.Content(movies) | VideoPlayer.Content(livetv)">$INFO[VideoPlayer.Genre]</value>
<value condition="Player.HasAudio">$INFO[MusicPlayer.TrackNumber,,: ][COLOR=grey]$INFO[Player.Title][/COLOR]</value>
</variable>
<variable name="PVRTimerIcon">
@@ -605,9 +607,15 @@
<value condition="ListItem.IsPlayable">icons/pvr/PVR-HasArchive.png</value>
</variable>
<variable name="SeasonEpisodeLabel">
- <value condition="String.IsEmpty(ListItem.EpisodeName)">$INFO[ListItem.Season,S]$INFO[ListItem.Episode,E]</value>
+ <value condition="String.IsEmpty(ListItem.EpisodeName)">$INFO[ListItem.Season,S]$INFO[ListItem.Episode,E]$INFO[ListItem.EpisodePart,/]</value>
+ <value condition="!String.IsEmpty(ListItem.EpisodeName) + !String.IsEmpty(ListItem.EpisodePart)">$INFO[ListItem.Season,S]$INFO[ListItem.Episode,E]$INFO[ListItem.EpisodePart,/,: ]</value>
<value>$INFO[ListItem.Season,S]$INFO[ListItem.Episode,E,: ]</value>
</variable>
+ <variable name="VideoPlayerSeasonEpisodeLabel">
+ <value condition="!String.IsEmpty(VideoPlayer.Season)"> $INFO[VideoPlayer.Season,S]$INFO[VideoPlayer.Episode,E]$INFO[VideoPlayer.EpisodePart,/]$INFO[VideoPlayer.EpisodeName, - ]</value>
+ <value condition="!String.IsEmpty(VideoPlayer.Episode)"> $INFO[VideoPlayer.Episode]$INFO[VideoPlayer.EpisodePart,/]$INFO[VideoPlayer.EpisodeName, - ]</value>
+ <value condition="!String.IsEmpty(VideoPlayer.EpisodeName)"> $INFO[VideoPlayer.EpisodeName]</value>
+ </variable>
<variable name="FirstAiredLabel">
<value condition="String.IsEqual(ListItem.DBType,movie)">$LOCALIZE[20473]</value>
<value>$LOCALIZE[20416]</value>
@@ -660,7 +668,7 @@
</variable>
<variable name="PVRListItemSubLabelFocused">
<value condition="ListItem.IsFolder">$INFO[ListItem.Timertype]</value>
- <value condition="$EXP[listitem_has_episode_info] + !String.IsEmpty(ListItem.EpgEventTitle) + !String.StartsWith(ListItem.EpisodeName,ListItem.EpgEventTitle)">$INFO[ListItem.EpgEventTitle] | $VAR[SeasonEpisodeLabel]$INFO[ListItem.EpisodeName]</value>
+ <value condition="$EXP[listitem_has_episode_info] + !String.IsEmpty(ListItem.EpgEventTitle) + !String.StartsWith(ListItem.EpisodeName,ListItem.EpgEventTitle)">$INFO[ListItem.EpgEventTitle] | [COLOR grey]$VAR[SeasonEpisodeLabel]$INFO[ListItem.EpisodeName][/COLOR]</value>
<value condition="$EXP[listitem_has_episode_info]">$VAR[SeasonEpisodeLabel]$INFO[ListItem.EpisodeName]</value>
<value>$INFO[ListItem.EpgEventTitle]</value>
</variable>
@@ -695,12 +703,14 @@
</variable>
<variable name="VideoListThumbVar">
<value condition="!String.IsEmpty(Container(6).ListItem.Art(landscape))">$INFO[Container(6).ListItem.Art(landscape)]</value>
+ <value condition="!String.IsEmpty(Container(6).ListItem.Art(poster))">$INFO[Container(6).ListItem.Art(poster)]</value>
<value condition="!String.IsEmpty(Container(6).ListItem.Art(thumb))">$INFO[Container(6).ListItem.Art(thumb)]</value>
<value condition="!String.IsEmpty(Container(50).ListItem.Art(landscape))">$INFO[Container(50).ListItem.Art(landscape)]</value>
+ <value condition="!String.IsEmpty(Container(50).ListItem.Art(poster))">$INFO[Container(50).ListItem.Art(poster)]</value>
<value condition="!String.IsEmpty(Container(50).ListItem.Art(thumb))">$INFO[Container(50).ListItem.Art(thumb)]</value>
<value>$INFO[ListItem.Art(thumb)]</value>
</variable>
- <variable name="VideoResolutionTypeVar">
+ <variable name="VideoListResolutionTypeVar">
<value condition="String.IsEqual(ListItem.VideoResolution,480)">SD</value>
<value condition="String.IsEqual(ListItem.VideoResolution,540)">SD</value>
<value condition="String.IsEqual(ListItem.VideoResolution,576)">SD</value>
@@ -708,8 +718,19 @@
<value condition="String.IsEqual(ListItem.VideoResolution,1080)">HD</value>
<value condition="String.IsEqual(ListItem.VideoResolution,4K)">UHD</value>
<value condition="String.IsEqual(ListItem.VideoResolution,8K)">UHD</value>
- </variable>
- <variable name="VideoCodecVar">
+ <value>$INFO[ListItem.VideoResolution]</value>
+ </variable>
+ <variable name="VideoPlayerResolutionTypeVar">
+ <value condition="String.IsEqual(VideoPlayer.VideoResolution,480)">SD</value>
+ <value condition="String.IsEqual(VideoPlayer.VideoResolution,540)">SD</value>
+ <value condition="String.IsEqual(VideoPlayer.VideoResolution,576)">SD</value>
+ <value condition="String.IsEqual(VideoPlayer.VideoResolution,720)">HD</value>
+ <value condition="String.IsEqual(VideoPlayer.VideoResolution,1080)">HD</value>
+ <value condition="String.IsEqual(VideoPlayer.VideoResolution,4K)">UHD</value>
+ <value condition="String.IsEqual(VideoPlayer.VideoResolution,8K)">UHD</value>
+ <value>$INFO[VideoPlayer.VideoResolution]</value>
+ </variable>
+ <variable name="VideoListCodecVar">
<value condition="String.IsEqual(ListItem.VideoCodec,av1)">AV1</value>
<value condition="String.IsEqual(ListItem.VideoCodec,avc1)">AVC-1</value>
<value condition="String.IsEqual(ListItem.VideoCodec,div3)">DivX</value>
@@ -736,13 +757,46 @@
<value condition="String.IsEqual(ListItem.VideoCodec,xvid)">XviD</value>
<value>$INFO[ListItem.VideoCodec]</value>
</variable>
- <variable name="VideoHDRVar">
- <value condition="String.IsEqual(ListItem.HdrType,dolbyvision)">Dolby Vision</value>
+ <variable name="VideoPlayerCodecVar">
+ <value condition="String.IsEqual(VideoPlayer.VideoCodec,av1)">AV1</value>
+ <value condition="String.IsEqual(VideoPlayer.VideoCodec,avc1)">AVC-1</value>
+ <value condition="String.IsEqual(VideoPlayer.VideoCodec,div3)">DivX</value>
+ <value condition="String.IsEqual(VideoPlayer.VideoCodec,divx)">DivX</value>
+ <value condition="String.IsEqual(VideoPlayer.VideoCodec,dx50)">DivX</value>
+ <value condition="String.IsEqual(VideoPlayer.VideoCodec,flv)">FLV</value>
+ <value condition="String.IsEqual(VideoPlayer.VideoCodec,h264)">H.264</value>
+ <value condition="String.IsEqual(VideoPlayer.VideoCodec,hev1)">H.265</value>
+ <value condition="String.IsEqual(VideoPlayer.VideoCodec,hevc)">H.265</value>
+ <value condition="String.IsEqual(VideoPlayer.VideoCodec,hvc1)">H.265</value>
+ <value condition="String.IsEqual(VideoPlayer.VideoCodec,mpeg1)">MPEG-1</value>
+ <value condition="String.IsEqual(VideoPlayer.VideoCodec,mpeg2)">MPEG-2</value>
+ <value condition="String.IsEqual(VideoPlayer.VideoCodec,mpeg2video)">MPEG-2</value>
+ <value condition="String.IsEqual(VideoPlayer.VideoCodec,mp4v)">MPEG-4</value>
+ <value condition="String.IsEqual(VideoPlayer.VideoCodec,mpeg4)">MPEG-4</value>
+ <value condition="String.IsEqual(VideoPlayer.VideoCodec,theora)">Theora</value>
+ <value condition="String.IsEqual(VideoPlayer.VideoCodec,vc1)">VC-1</value>
+ <value condition="String.IsEqual(VideoPlayer.VideoCodec,vc-1)">VC-1</value>
+ <value condition="String.IsEqual(VideoPlayer.VideoCodec,vp8)">VP8</value>
+ <value condition="String.IsEqual(VideoPlayer.VideoCodec,vp9)">VP9</value>
+ <value condition="String.IsEqual(VideoPlayer.VideoCodec,wmv)">WMV</value>
+ <value condition="String.IsEqual(VideoPlayer.VideoCodec,wmv3)">WMV</value>
+ <value condition="String.IsEqual(VideoPlayer.VideoCodec,wvc1)">VC-1</value>
+ <value condition="String.IsEqual(VideoPlayer.VideoCodec,xvid)">XviD</value>
+ <value>$INFO[VideoPlayer.VideoCodec]</value>
+ </variable>
+ <variable name="VideoListHDRVar">
+ <value condition="String.IsEqual(ListItem.HdrType,dolbyvision)">DoVi</value>
<value condition="String.IsEqual(ListItem.HdrType,hdr10)">HDR10</value>
<value condition="String.IsEqual(ListItem.HdrType,hlg)">HLG</value>
<value>$INFO[ListItem.HdrType]</value>
</variable>
- <variable name="AudioCodecVar">
+ <variable name="VideoPlayerHDRVar">
+ <value condition="String.IsEqual(VideoPlayer.HdrType,dolbyvision)">DoVi</value>
+ <value condition="String.IsEqual(VideoPlayer.HdrType,hdr10)">HDR10</value>
+ <value condition="String.IsEqual(VideoPlayer.HdrType,hlg)">HLG</value>
+ <value>$INFO[VideoPlayer.HdrType]</value>
+ </variable>
+ <variable name="AudioListCodecVar">
<value condition="String.IsEqual(ListItem.AudioCodec,aac)">AAC</value>
<value condition="String.IsEqual(ListItem.AudioCodec,aac_latm)">AAC</value>
<value condition="String.IsEqual(ListItem.AudioCodec,ac3)">Dolby D</value>
@@ -778,7 +832,43 @@
<value condition="String.IsEqual(ListItem.AudioCodec,wmav2)">WMA</value>
<value>$INFO[ListItem.AudioCodec]</value>
</variable>
- <variable name="AudioChannelsVar">
+ <variable name="VideoPlayerAudioCodecVar">
+ <value condition="String.IsEqual(VideoPlayer.AudioCodec,aac)">AAC</value>
+ <value condition="String.IsEqual(VideoPlayer.AudioCodec,aac_latm)">AAC</value>
+ <value condition="String.IsEqual(VideoPlayer.AudioCodec,ac3)">Dolby D</value>
+ <value condition="String.IsEqual(VideoPlayer.AudioCodec,aif)">AIFF</value>
+ <value condition="String.IsEqual(VideoPlayer.AudioCodec,aifc)">AIFF</value>
+ <value condition="String.IsEqual(VideoPlayer.AudioCodec,aiff)">AIFF</value>
+ <value condition="String.IsEqual(VideoPlayer.AudioCodec,alac)">ALAC</value>
+ <value condition="String.IsEqual(VideoPlayer.AudioCodec,ape)">APE</value>
+ <value condition="String.IsEqual(VideoPlayer.AudioCodec,avc)">AVC</value>
+ <value condition="String.IsEqual(VideoPlayer.AudioCodec,cdda)">CDDA</value>
+ <value condition="String.IsEqual(VideoPlayer.AudioCodec,dca)">DTS</value>
+ <value condition="String.IsEqual(VideoPlayer.AudioCodec,dolbydigital)">Dolby D</value>
+ <value condition="String.IsEqual(VideoPlayer.AudioCodec,dts)">DTS</value>
+ <value condition="String.IsEqual(VideoPlayer.AudioCodec,dtshd_hra)">DTSHD-HRA</value>
+ <value condition="String.IsEqual(VideoPlayer.AudioCodec,dtshd_ma)">DTSHD-MA</value>
+ <value condition="String.IsEqual(VideoPlayer.AudioCodec,dtsma)">DTSHD-MA</value>
+ <value condition="String.IsEqual(VideoPlayer.AudioCodec,eac3)">Dolby D+</value>
+ <value condition="String.IsEqual(VideoPlayer.AudioCodec,flac)">FLAC</value>
+ <value condition="String.IsEqual(VideoPlayer.AudioCodec,mp1)">MP1</value>
+ <value condition="String.IsEqual(VideoPlayer.AudioCodec,mp3)">MP3</value>
+ <value condition="String.IsEqual(VideoPlayer.AudioCodec,mp3float)">MP3</value>
+ <value condition="String.IsEqual(VideoPlayer.AudioCodec,ogg)">OGG</value>
+ <value condition="String.IsEqual(VideoPlayer.AudioCodec,opus)">OPUS</value>
+ <value condition="String.IsEqual(VideoPlayer.AudioCodec,pcm)">PCM</value>
+ <value condition="String.IsEqual(VideoPlayer.AudioCodec,pcm_bluray)">PCM</value>
+ <value condition="String.IsEqual(VideoPlayer.AudioCodec,pcm_s16le)">PCM</value>
+ <value condition="String.IsEqual(VideoPlayer.AudioCodec,pcm_s24le)">PCM</value>
+ <value condition="String.IsEqual(VideoPlayer.AudioCodec,truehd)">TrueHD</value>
+ <value condition="String.IsEqual(VideoPlayer.AudioCodec,vorbis)">Vorbis</value>
+ <value condition="String.IsEqual(VideoPlayer.AudioCodec,wav)">WAV</value>
+ <value condition="String.IsEqual(VideoPlayer.AudioCodec,wavpack)">WAVP</value>
+ <value condition="String.IsEqual(VideoPlayer.AudioCodec,wmapro)">WMA-PRO</value>
+ <value condition="String.IsEqual(VideoPlayer.AudioCodec,wmav2)">WMA</value>
+ <value>$INFO[VideoPlayer.AudioCodec]</value>
+ </variable>
+ <variable name="AudioListChannelsVar">
<value condition="String.IsEqual(ListItem.AudioChannels,0)">0.0</value>
<value condition="String.IsEqual(ListItem.AudioChannels,1)">1.0</value>
<value condition="String.IsEqual(ListItem.AudioChannels,2)">2.0</value>
@@ -791,13 +881,88 @@
<value condition="String.IsEqual(ListItem.AudioChannels,10)">9.1</value>
<value>$INFO[ListItem.AudioChannels]</value>
</variable>
+ <variable name="VideoPlayerAudioChannelsVar">
+ <value condition="String.IsEqual(VideoPlayer.AudioChannels,0)">0.0</value>
+ <value condition="String.IsEqual(VideoPlayer.AudioChannels,1)">1.0</value>
+ <value condition="String.IsEqual(VideoPlayer.AudioChannels,2)">2.0</value>
+ <value condition="String.IsEqual(VideoPlayer.AudioChannels,3)">2.1</value>
+ <value condition="String.IsEqual(VideoPlayer.AudioChannels,4)">4.0</value>
+ <value condition="String.IsEqual(VideoPlayer.AudioChannels,5)">4.1</value>
+ <value condition="String.IsEqual(VideoPlayer.AudioChannels,6)">5.1</value>
+ <value condition="String.IsEqual(VideoPlayer.AudioChannels,7)">6.1</value>
+ <value condition="String.IsEqual(VideoPlayer.AudioChannels,8)">7.1</value>
+ <value condition="String.IsEqual(VideoPlayer.AudioChannels,10)">9.1</value>
+ <value>$INFO[VideoPlayer.AudioChannels]</value>
+ </variable>
+ <variable name="MusicPlayerCodecVar">
+ <value condition="String.IsEqual(MusicPlayer.Codec,aac)">AAC</value>
+ <value condition="String.IsEqual(MusicPlayer.Codec,aac_latm)">AAC</value>
+ <value condition="String.IsEqual(MusicPlayer.Codec,ac3)">Dolby D</value>
+ <value condition="String.IsEqual(LMusicPlayer.Codec,aif)">AIFF</value>
+ <value condition="String.IsEqual(MusicPlayer.Codec,aifc)">AIFF</value>
+ <value condition="String.IsEqual(MusicPlayer.Codec,aiff)">AIFF</value>
+ <value condition="String.IsEqual(MusicPlayer.Codec,alac)">ALAC</value>
+ <value condition="String.IsEqual(LMusicPlayer.Codec,ape)">APE</value>
+ <value condition="String.IsEqual(MusicPlayer.Codec,avc)">AVC</value>
+ <value condition="String.IsEqual(MusicPlayer.Codec,cdda)">CDDA</value>
+ <value condition="String.IsEqual(MusicPlayer.Codec,dca)">DTS</value>
+ <value condition="String.IsEqual(MusicPlayer.Codec,dolbydigital)">Dolby D</value>
+ <value condition="String.IsEqual(MusicPlayer.Codec,dts)">DTS</value>
+ <value condition="String.IsEqual(MusicPlayer.Codec,dtshd_hra)">DTSHD-HRA</value>
+ <value condition="String.IsEqual(MusicPlayer.Codec,dtshd_ma)">DTSHD-MA</value>
+ <value condition="String.IsEqual(MusicPlayer.Codec,dtsma)">DTSHD-MA</value>
+ <value condition="String.IsEqual(MusicPlayer.Codec,eac3)">Dolby D+</value>
+ <value condition="String.IsEqual(MusicPlayer.Codec,flac)">FLAC</value>
+ <value condition="String.IsEqual(MusicPlayer.Codec,mp1)">MP1</value>
+ <value condition="String.IsEqual(MusicPlayer.Codec,mp3)">MP3</value>
+ <value condition="String.IsEqual(MusicPlayer.Codec,mp3float)">MP3</value>
+ <value condition="String.IsEqual(MusicPlayer.Codec,ogg)">OGG</value>
+ <value condition="String.IsEqual(MusicPlayer.Codec,opus)">OPUS</value>
+ <value condition="String.IsEqual(MusicPlayer.Codec,pcm)">PCM</value>
+ <value condition="String.IsEqual(MusicPlayer.Codec,pcm_bluray)">PCM</value>
+ <value condition="String.IsEqual(MusicPlayer.Codec,pcm_s16le)">PCM</value>
+ <value condition="String.IsEqual(MusicPlayer.Codec,pcm_s24le)">PCM</value>
+ <value condition="String.IsEqual(MusicPlayer.Codec,truehd)">TrueHD</value>
+ <value condition="String.IsEqual(MusicPlayer.Codec,vorbis)">Vorbis</value>
+ <value condition="String.IsEqual(MusicPlayer.Codec,wav)">WAV</value>
+ <value condition="String.IsEqual(MusicPlayer.Codec,wavpack)">WAVP</value>
+ <value condition="String.IsEqual(MusicPlayer.Codec,wmapro)">WMA-PRO</value>
+ <value condition="String.IsEqual(MusicPlayer.Codec,wmav2)">WMA</value>
+ <value>$INFO[MusicPlayer.Codec]</value>
+ </variable>
+ <variable name="MusicListChannelsVar">
+ <value condition="String.IsEqual(ListItem.MusicChannels,0)">0.0</value>
+ <value condition="String.IsEqual(ListItem.MusicChannels,1)">1.0</value>
+ <value condition="String.IsEqual(ListItem.MusicChannels,2)">2.0</value>
+ <value condition="String.IsEqual(ListItem.MusicChannels,3)">2.1</value>
+ <value condition="String.IsEqual(ListItem.MusicChannels,4)">4.0</value>
+ <value condition="String.IsEqual(ListItem.MusicChannels,5)">4.1</value>
+ <value condition="String.IsEqual(ListItem.MusicChannels,6)">5.1</value>
+ <value condition="String.IsEqual(ListItem.MusicChannels,7)">6.1</value>
+ <value condition="String.IsEqual(ListItem.MusicChannels,8)">7.1</value>
+ <value condition="String.IsEqual(ListItem.MusicChannels,10)">9.1</value>
+ <value>$INFO[ListItem.MusicChannels]</value>
+ </variable>
+ <variable name="MusicPlayerAudioChannelsVar">
+ <value condition="String.IsEqual(MusicPlayer.Channels,0)">0.0</value>
+ <value condition="String.IsEqual(MusicPlayer.Channels,1)">1.0</value>
+ <value condition="String.IsEqual(MusicPlayer.Channels,2)">2.0</value>
+ <value condition="String.IsEqual(MusicPlayer.Channels,3)">2.1</value>
+ <value condition="String.IsEqual(MusicPlayer.Channels,4)">4.0</value>
+ <value condition="String.IsEqual(MusicPlayer.Channels,5)">4.1</value>
+ <value condition="String.IsEqual(MusicPlayer.Channels,6)">5.1</value>
+ <value condition="String.IsEqual(MusicPlayer.Channels,7)">6.1</value>
+ <value condition="String.IsEqual(MusicPlayer.Channels,8)">7.1</value>
+ <value condition="String.IsEqual(MusicPlayer.Channels,10)">9.1</value>
+ <value>$INFO[MusicPlayer.Channels]</value>
+ </variable>
<variable name="MediaInfoListLabelVar">
<value condition="Window.IsVisible(selectvideoversion)">$INFO[ListItem.VideoVersionName]</value>
<value>$INFO[ListItem.Label]</value>
</variable>
<variable name="MediaInfoListLabel2Var">
- <value condition="ListItem.IsStereoscopic">$INFO[ListItem.Duration,$LOCALIZE[180]: ][CR]$VAR[VideoCodecVar,, ]$INFO[ListItem.VideoResolution,| , ]$VAR[VideoResolutionTypeVar,, ]$VAR[VideoHDRVar,| , ]| 3D $INFO[ListItem.VideoAspect,| ,:1 ]$VAR[AudioCodecVar,| , ]$VAR[AudioChannelsVar]</value>
- <value>$INFO[ListItem.Duration,$LOCALIZE[180]: ][CR]$VAR[VideoCodecVar,, ]$INFO[ListItem.VideoResolution,| , ]$VAR[VideoResolutionTypeVar,, ]$VAR[VideoHDRVar,| , ]$INFO[ListItem.VideoAspect,| ,:1 ]$VAR[AudioCodecVar,| , ]$VAR[AudioChannelsVar]</value>
+ <value condition="ListItem.IsStereoscopic">$INFO[ListItem.Duration,$LOCALIZE[180]: ][CR]3D | $VAR[VideoListCodecVar]$INFO[ListItem.VideoResolution, | ]$VAR[VideoListResolutionTypeVar, ]$VAR[VideoListHDRVar, | ]$VAR[VideoListAspectVar, | ]$VAR[AudioListCodecVar, | ]$VAR[AudioListChannelsVar, ]</value>
+ <value>$INFO[ListItem.Duration,$LOCALIZE[180]: ][CR]$VAR[VideoListCodecVar]$INFO[ListItem.VideoResolution, | ]$VAR[VideoListResolutionTypeVar, ]$VAR[VideoListHDRVar, | ]$VAR[VideoListAspectVar, | ]$VAR[AudioListCodecVar, | ]$VAR[AudioListChannelsVar, ]</value>
</variable>
<variable name="PVRInstanceName">
<value condition="Integer.IsGreater(PVR.ClientCount,1)">$INFO[ListItem.PVRClientName,[COLOR grey]$LOCALIZE[31137]:[/COLOR] ,]$INFO[ListItem.PVRInstanceName, (,)]</value>
diff --git a/addons/skin.estuary/xml/VideoOSD.xml b/addons/skin.estuary/xml/VideoOSD.xml
index 6c25ea3a86..41e9fdc3f3 100644
--- a/addons/skin.estuary/xml/VideoOSD.xml
+++ b/addons/skin.estuary/xml/VideoOSD.xml
@@ -141,9 +141,6 @@
<scrolltime tween="sine">200</scrolltime>
<orientation>horizontal</orientation>
<onup>87</onup>
- <ondown condition="Control.HasFocus(70043)">11104</ondown>
- <ondown condition="Control.HasFocus(704)">12104</ondown>
- <ondown condition="Control.HasFocus(255)">13103</ondown>
<onleft>608</onleft>
<onright>600</onright>
<control type="radiobutton" id="804">
@@ -226,18 +223,18 @@
</control>
<control type="group" id="6000">
<top>60</top>
- <animation type="WindowOpen" condition="!Window.IsVisible(fullscreeninfo)" reversible="False">
+ <animation type="WindowOpen" condition="!Window.IsVisible(fullscreeninfo)" reversible="false">
<effect type="fade" start="0" end="100" time="300"/>
<effect type="slide" start="0,200" end="0,0" time="300" tween="cubic" easing="out" />
</animation>
- <animation type="WindowClose" condition="!Window.IsVisible(fullscreeninfo)" reversible="False">
+ <animation type="WindowClose" condition="!Window.IsVisible(fullscreeninfo)" reversible="false">
<effect type="fade" start="100" end="0" time="300"/>
<effect type="slide" start="0,0" end="0,200" time="300" tween="cubic" easing="out" />
</animation>
- <animation type="WindowOpen" condition="Window.IsVisible(fullscreeninfo)" reversible="False">
+ <animation type="WindowOpen" condition="Window.IsVisible(fullscreeninfo)" reversible="false">
<effect type="fade" start="0" end="100" time="300"/>
</animation>
- <animation type="WindowClose" condition="Window.IsVisible(fullscreeninfo)" reversible="False">
+ <animation type="WindowClose" condition="Window.IsVisible(fullscreeninfo)" reversible="false">
<effect type="fade" start="100" end="0" time="300"/>
</animation>
<visible>Player.SeekEnabled</visible>
diff --git a/cmake/modules/FindCurl.cmake b/cmake/modules/FindCurl.cmake
index a26682395c..3ce4ed5829 100644
--- a/cmake/modules/FindCurl.cmake
+++ b/cmake/modules/FindCurl.cmake
@@ -34,10 +34,6 @@ if(NOT TARGET ${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME})
set(PLATFORM_LINK_LIBS crypt32.lib)
endif()
- set(patches "${CORE_SOURCE_DIR}/tools/depends/target/${MODULE_LC}/01-win-nghttp2-add-name.patch")
-
- generate_patchcommand("${patches}")
-
set(CMAKE_ARGS -DBUILD_CURL_EXE=OFF
-DBUILD_SHARED_LIBS=OFF
-DBUILD_STATIC_LIBS=ON
diff --git a/cmake/modules/FindPython.cmake b/cmake/modules/FindPython.cmake
index 3fc61da50c..7874b28187 100644
--- a/cmake/modules/FindPython.cmake
+++ b/cmake/modules/FindPython.cmake
@@ -30,7 +30,7 @@ if(NOT TARGET ${APP_NAME_LC}::${CMAKE_FIND_PACKAGE_NAME})
if(KODI_DEPENDSBUILD)
# Force set to tools/depends python version
- set(PYTHON_VER 3.11)
+ set(PYTHON_VER 3.12)
endif()
endif()
diff --git a/cmake/modules/buildtools/FindWaylandPPScanner.cmake b/cmake/modules/buildtools/FindWaylandPPScanner.cmake
index ff80dbd3f4..52d9ba6899 100644
--- a/cmake/modules/buildtools/FindWaylandPPScanner.cmake
+++ b/cmake/modules/buildtools/FindWaylandPPScanner.cmake
@@ -13,9 +13,10 @@ if(NOT wayland::waylandppscanner)
if(PC_WAYLANDPP_SCANNER_FOUND)
pkg_get_variable(PC_WAYLANDPP_SCANNER wayland-scanner++ wayland_scannerpp)
+ get_filename_component(PC_WAYLANDPP_SCANNER_DIR ${PC_WAYLANDPP_SCANNER} DIRECTORY)
endif()
- find_program(WAYLANDPP_SCANNER wayland-scanner++ HINTS ${PC_WAYLANDPP_SCANNER})
+ find_program(WAYLANDPP_SCANNER wayland-scanner++ HINTS ${PC_WAYLANDPP_SCANNER_DIR})
if(WAYLANDPP_SCANNER)
diff --git a/cmake/scripts/common/HandleDepends.cmake b/cmake/scripts/common/HandleDepends.cmake
index dc022bab64..de994b80e2 100644
--- a/cmake/scripts/common/HandleDepends.cmake
+++ b/cmake/scripts/common/HandleDepends.cmake
@@ -271,7 +271,7 @@ function(add_addon_depends addon searchpath)
externalproject_add(${id}
URL ${url}
- "${URL_HASH_COMMAND}"
+ ${URL_HASH_COMMAND}
DOWNLOAD_DIR ${DOWNLOAD_DIR}
CONFIGURE_COMMAND ${CONFIGURE_COMMAND}
${EXTERNALPROJECT_SETUP})
diff --git a/cmake/scripts/webos/Install.cmake b/cmake/scripts/webos/Install.cmake
index 03dd7ce183..6af5b20973 100644
--- a/cmake/scripts/webos/Install.cmake
+++ b/cmake/scripts/webos/Install.cmake
@@ -39,7 +39,8 @@ file(WRITE ${CMAKE_BINARY_DIR}/install.cmake "
file(INSTALL ${APP_INSTALL_DIRS} DESTINATION ${APP_PACKAGE_DIR})
file(CREATE_LINK python${PYTHON_VERSION} python3 SYMBOLIC)
file(INSTALL python3 DESTINATION ${APP_PACKAGE_DIR}/lib)
- file(INSTALL ${DEPENDS_PATH}/lib/python${PYTHON_VERSION} DESTINATION ${APP_PACKAGE_DIR}/lib FOLLOW_SYMLINK_CHAIN)
+ file(INSTALL ${DEPENDS_PATH}/lib/python${PYTHON_VERSION} DESTINATION ${APP_PACKAGE_DIR}/lib FOLLOW_SYMLINK_CHAIN
+ PATTERN config-${PYTHON_VERSION}-arm-linux-gnueabi EXCLUDE)
file(INSTALL ${APP_TOOLCHAIN_FILES} DESTINATION ${APP_PACKAGE_DIR}/lib FOLLOW_SYMLINK_CHAIN)
file(STRINGS ${CMAKE_BINARY_DIR}/missing_libs.txt missing_libs)
diff --git a/cmake/scripts/windows/ArchSetup.cmake b/cmake/scripts/windows/ArchSetup.cmake
index 8be83e6a78..89adc5e125 100644
--- a/cmake/scripts/windows/ArchSetup.cmake
+++ b/cmake/scripts/windows/ArchSetup.cmake
@@ -1,9 +1,9 @@
-# Minimum SDK version we support
-set(VS_MINIMUM_SDK_VERSION 10.0.22621.0)
+# Minimum SDK version required to build
+set(VS_MINIMUM_BUILD_SDK_VERSION 10.0.22621.0)
-if(CMAKE_VS_WINDOWS_TARGET_PLATFORM_VERSION VERSION_LESS VS_MINIMUM_SDK_VERSION)
+if(CMAKE_VS_WINDOWS_TARGET_PLATFORM_VERSION VERSION_LESS VS_MINIMUM_BUILD_SDK_VERSION)
message(FATAL_ERROR "Detected Windows SDK version is ${CMAKE_VS_WINDOWS_TARGET_PLATFORM_VERSION}.\n"
- "Windows SDK ${VS_MINIMUM_SDK_VERSION} or higher is required.\n"
+ "Windows SDK ${VS_MINIMUM_BUILD_SDK_VERSION} or higher is required.\n"
"INFO: Windows SDKs can be installed from the Visual Studio installer.")
endif()
diff --git a/cmake/scripts/windowsstore/ArchSetup.cmake b/cmake/scripts/windowsstore/ArchSetup.cmake
index a39914cca0..2c1221c48b 100644
--- a/cmake/scripts/windowsstore/ArchSetup.cmake
+++ b/cmake/scripts/windowsstore/ArchSetup.cmake
@@ -1,9 +1,11 @@
-# Minimum SDK version we support
-set(VS_MINIMUM_SDK_VERSION 10.0.22621.0)
+# Minimum SDK version required to build
+set(VS_MINIMUM_BUILD_SDK_VERSION 10.0.22621.0)
+# Minimum OS version to run the app
+set(VS_MINIMUM_SDK_VERSION 10.0.18362.0)
-if(CMAKE_VS_WINDOWS_TARGET_PLATFORM_VERSION VERSION_LESS VS_MINIMUM_SDK_VERSION)
+if(CMAKE_VS_WINDOWS_TARGET_PLATFORM_VERSION VERSION_LESS VS_MINIMUM_BUILD_SDK_VERSION)
message(FATAL_ERROR "Detected Windows SDK version is ${CMAKE_VS_WINDOWS_TARGET_PLATFORM_VERSION}.\n"
- "Windows SDK ${VS_MINIMUM_SDK_VERSION} or higher is required.\n"
+ "Windows SDK ${VS_MINIMUM_BUILD_SDK_VERSION} or higher is required.\n"
"INFO: Windows SDKs can be installed from the Visual Studio installer.")
endif()
diff --git a/system/keymaps/osmc/osmc_remote.xml b/system/keymaps/osmc/osmc_remote.xml
index 79f392eb58..50b6117026 100644
--- a/system/keymaps/osmc/osmc_remote.xml
+++ b/system/keymaps/osmc/osmc_remote.xml
@@ -18,7 +18,7 @@
<!-- Vol- = volume_down <key id="61624"> RW = rewind Vol- = minus Vol- = minus -->
<!-- Vol+ = volume_up <key id="61625"> FF = fastforward Vol+ = equals Vol+ = equals -->
<!-- -->
-<!-- Keymap created by DarwinDesign version 20-11-02 -->
+<!-- Keymap created by DarwinDesign version 24-08-24 -->
<!-- -->
<keymap>
<global>
@@ -50,7 +50,7 @@
<x>Stop</x>
<volume_down>VolumeDown</volume_down>
<volume_up>VolumeUp</volume_up>
- <f2>Notification(OSMC Remote Controller, Low Battery Please Replace,5000)</f2>
+ <f2>Notification(OSMC $LOCALIZE[790],$LOCALIZE[13050],5000,DefaultIconerror.png)</f2> <!-- OSMC Remote Control, Running low on battery -->
</keyboard>
</global>
<Home>
diff --git a/system/keymaps/osmcv3/osmcv3_remote.xml b/system/keymaps/osmcv3/osmcv3_remote.xml
new file mode 100644
index 0000000000..adac2fc6c6
--- /dev/null
+++ b/system/keymaps/osmcv3/osmcv3_remote.xml
@@ -0,0 +1,584 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- OSMC remotes use i and c keys that stop functioning with some keyboard languages -->
+<!-- in OSMC. We have remapped those keys in OSMC to kpleftparen and kprightparen with -->
+<!-- udev to overcome this issue. This file maps those keys to Kodi actions and adds -->
+<!-- tweaks to provide enhanced function.The buttons map in Kodi as follows... -->
+<!-- -->
+<!-- OSMC with udev remap Stock layout -->
+<!-- Home = escape <key id="61467"> Home = escape -->
+<!-- Info = leftbracket <key id="61480"> Info = i -->
+<!-- Up = up <key id="61568"> Up = up -->
+<!-- Down = down <key id="61569"> Down = down -->
+<!-- Left = left <key id="61570"> Left = left -->
+<!-- Right = right <key id="61571"> Right = right -->
+<!-- OK = return <key id="61453"> OK = return -->
+<!-- Back = browser_back <key id="61616"> Back = browser_back -->
+<!-- Menu = rightbracket <key id="61481"> Menu = c -->
+<!-- Play = play_pause <key id="61629"> Play = play_pause -->
+<!-- Stop = stop <key id="61628"> Stop = stop -->
+<!-- Vol- = volume_down <key id="61624"> Vol- = volume_down -->
+<!-- Vol+ = volume_up <key id="61625"> Vol+ = volume_up -->
+<!-- -->
+<!-- Keymap created by DarwinDesign version 24-08-24 -->
+<!-- -->
+<keymap>
+ <global>
+ <keyboard>
+ <escape>PreviousMenu</escape>
+ <home>PreviousMenu</home>
+ <escape mod="longpress">fullscreen</escape>
+ <home mod="longpress">fullscreen</home>
+ <leftbracket>Info</leftbracket>
+ <i>Info</i>
+ <leftbracket mod="longpress">noop</leftbracket> <!-- stops cycling -->
+ <i mod="longpress">noop</i> <!-- stops cycling -->
+ <left>Left</left>
+ <right>Right</right>
+ <up>Up</up>
+ <down>Down</down>
+ <return>Select</return>
+ <return mod="longpress">noop</return> <!-- removes default context menu & stops cycling -->
+ <browser_back>Back</browser_back>
+ <rightbracket>ContextMenu</rightbracket>
+ <c>ContextMenu</c>
+ <rightbracket mod="longpress">Menu</rightbracket>
+ <c mod="longpress">Menu</c>
+ <play_pause>PlayPause</play_pause>
+ <p>PlayPause</p>
+ <play_pause mod="longpress">noop</play_pause> <!-- removes default info & stops cycling -->
+ <p mod="longpress">noop</p>
+ <stop>Stop</stop>
+ <x>Stop</x>
+ <volume_down>VolumeDown</volume_down>
+ <volume_up>VolumeUp</volume_up>
+ <f1>VoiceRecognizer</f1> <!-- Mic Button -->
+ <browser_search>RunScript(script.globalsearch)</browser_search> <!-- OSMC Button -->
+ <f2>Notification(OSMC $LOCALIZE[790],$LOCALIZE[13050],5000,DefaultIconerror.png)</f2> <!-- OSMC Remote Control, Running low on battery -->
+ <f3>Notification(OSMC $LOCALIZE[790],$LOCALIZE[38373],5000)</f3> <!-- OSMC Remote Control, Battery is charging -->
+ <f4>Notification(OSMC $LOCALIZE[790],$LOCALIZE[38374],5000)</f4> <!-- OSMC Remote Control, Battery is fully charged -->
+ <f5>Notification(OSMC $LOCALIZE[790],$LOCALIZE[38375],5000)</f5> <!-- OSMC Remote Control, Battery charger removed -->
+ </keyboard>
+ </global>
+ <Home>
+ <keyboard>
+ <escape>CECActivateSource</escape>
+ <home>CECActivateSource</home>
+ <escape mod="longpress">CECStandby</escape>
+ <home mod="longpress">CECStandby</home>
+ <leftbracket>info</leftbracket>
+ <i>info</i>
+ <browser_back mod="longpress">ActivateWindow(ShutdownMenu)</browser_back>
+ <return mod="longpress">ReloadSkin()</return>
+ <play_pause mod="longpress">UpdateLibrary(video)</play_pause>
+ <p mod="longpress">UpdateLibrary(video)</p>
+ </keyboard>
+ </Home>
+ <VirtualKeyboard>
+ <keyboard>
+ <rightbracket mod="longpress">noop</rightbracket>
+ <c mod="longpress">noop</c>
+ <up mod="longpress">Shift</up>
+ <down mod="longpress">Symbols</down>
+ <return mod="longpress">Enter</return>
+ </keyboard>
+ </VirtualKeyboard>
+ <FileManager>
+ <keyboard>
+ <right mod="longpress">Highlight</right>
+ <left mod="longpress">Highlight</left>
+ </keyboard>
+ </FileManager>
+ <FullscreenVideo>
+ <keyboard>
+ <escape>ActivateWindow(videobookmarks)</escape>
+ <home>ActivateWindow(videobookmarks)</home>
+ <escape mod="longpress">playerdebug</escape>
+ <home mod="longpress">playerdebug</home>
+ <leftbracket>info</leftbracket>
+ <i>info</i>
+ <leftbracket mod="longpress">playerprocessinfo</leftbracket>
+ <i mod="longpress">playerprocessinfo</i>
+ <return mod="longpress">Playlist</return>
+ <up mod="longpress">SkipNext</up>
+ <down mod="longpress">SkipPrevious</down>
+ <left mod="longpress">AudioDelay</left>
+ <right mod="longpress">subtitledelay</right>
+ <rightbracket>ActivateWindow(osdvideosettings)</rightbracket>
+ <c>ActivateWindow(osdvideosettings)</c>
+ <rightbracket mod="longpress">ActivateWindow(osdaudiosettings)</rightbracket>
+ <c mod="longpress">ActivateWindow(osdaudiosettings)</c>
+ <play_pause mod="longpress">showsubtitles</play_pause>
+ <p mod="longpress">showsubtitles</p>
+ <stop mod="longpress">ActivateWindow(osdsubtitlesettings)</stop>
+ <x mod="longpress">ActivateWindow(osdsubtitlesettings)</x>
+ </keyboard>
+ </FullscreenVideo>
+ <FullscreenGame>
+ <keyboard>
+ <leftbracket>info</leftbracket>
+ <i>info</i>
+ <leftbracket mod="longpress">playerprocessinfo</leftbracket>
+ <i mod="longpress">playerprocessinfo</i>
+ <rightbracket>OSD</rightbracket>
+ <c>OSD</c>
+ </keyboard>
+ </FullscreenGame>
+ <FullscreenInfo>
+ <keyboard>
+ <leftbracket>Back</leftbracket>
+ <i>Back</i>
+ <leftbracket mod="longpress">playerprocessinfo</leftbracket>
+ <i mod="longpress">playerprocessinfo</i>
+ <rightbracket>Back</rightbracket>
+ <c>Back</c>
+ </keyboard>
+ </FullscreenInfo>
+ <Visualisation>
+ <keyboard>
+ <leftbracket>info</leftbracket>
+ <i>info</i>
+ <leftbracket mod="longpress">playerprocessinfo</leftbracket>
+ <i mod="longpress">playerprocessinfo</i>
+ <escape>ActivateWindow(Home)</escape>
+ <home>ActivateWindow(Home)</home>
+ <escape mod="longpress">fullscreen</escape>
+ <home mod="longpress">fullscreen</home>
+ <return mod="longpress">ActivateWindow(MusicPlaylist)</return>
+ <rightbracket>Addon.Default.OpenSettings(xbmc.player.musicviz)</rightbracket>
+ <c>Addon.Default.OpenSettings(xbmc.player.musicviz)</c>
+ <rightbracket mod="longpress">ActivateWindow(VisualisationPresetList)</rightbracket>
+ <c mod="longpress">ActivateWindow(VisualisationPresetList)</c>
+ <p/>
+ </keyboard>
+ </Visualisation>
+ <MusicOSD>
+ <keyboard>
+ <escape>back</escape>
+ <home>back</home>
+ <leftbracket>info</leftbracket>
+ <i>info</i>
+ <leftbracket mod="longpress">playerprocessinfo</leftbracket>
+ <i mod="longpress">playerprocessinfo</i>
+ <return mod="longpress">back</return>
+ <rightbracket>Addon.Default.OpenSettings(xbmc.player.musicviz)</rightbracket>
+ <c>Addon.Default.OpenSettings(xbmc.player.musicviz)</c>
+ <rightbracket mod="longpress">ActivateWindow(VisualisationPresetList)</rightbracket>
+ <c mod="longpress">ActivateWindow(VisualisationPresetList)</c>
+ <p/>
+ </keyboard>
+ </MusicOSD>
+ <VisualisationPresetList>
+ <keyboard>
+ <leftbracket>info</leftbracket>
+ <i>info</i>
+ <leftbracket mod="longpress">playerprocessinfo</leftbracket>
+ <i mod="longpress">playerprocessinfo</i>
+ <rightbracket>back</rightbracket>
+ <c>back</c>
+ <p/>
+ </keyboard>
+ </VisualisationPresetList>
+ <slideshow>
+ <keyboard>
+ <escape>back</escape>
+ <home>back</home>
+ <leftbracket>info</leftbracket>
+ <i>info</i>
+ <play_pause>pause</play_pause>
+ <p>pause</p>
+ <up mod="longpress">ZoomIn</up>
+ <down mod="longpress">ZoomOut</down>
+ <return mod="longpress">ZoomNormal</return>
+ <rightbracket></rightbracket> <!-- removes mapping from osmc-classic -->
+ </keyboard>
+ </slideshow>
+ <VideoOSD>
+ <keyboard>
+ <escape>ActivateWindow(videobookmarks)</escape>
+ <home>ActivateWindow(videobookmarks)</home>
+ <escape mod="longpress">playerdebug</escape>
+ <home mod="longpress">playerdebug</home>
+ <leftbracket>info</leftbracket>
+ <i>info</i>
+ <leftbracket mod="longpress">playerprocessinfo</leftbracket>
+ <i mod="longpress">playerprocessinfo</i>
+ <up mod="longpress">SkipNext</up>
+ <down mod="longpress">SkipPrevious</down>
+ <left mod="longpress">AudioDelay</left>
+ <right mod="longpress">subtitledelay</right>
+ <rightbracket>ActivateWindow(osdvideosettings)</rightbracket>
+ <return mod="longpress">back</return>
+ <c>ActivateWindow(osdvideosettings)</c>
+ <rightbracket mod="longpress">ActivateWindow(osdaudiosettings)</rightbracket>
+ <c mod="longpress">ActivateWindow(osdaudiosettings)</c>
+ <play_pause mod="longpress">showsubtitles</play_pause>
+ <p mod="longpress">showsubtitles</p>
+ <stop mod="longpress">ActivateWindow(osdsubtitlesettings)</stop>
+ <x mod="longpress">ActivateWindow(osdsubtitlesettings)</x>
+ </keyboard>
+ </VideoOSD>
+ <VideoMenu>
+ <keyboard>
+ <escape>ActivateWindow(Home)</escape>
+ <home>ActivateWindow(Home)</home>
+ <escape mod="longpress">fullscreen</escape>
+ <home mod="longpress">fullscreen</home>
+ <leftbracket>info</leftbracket>
+ <i>info</i>
+ <leftbracket mod="longpress">playerprocessinfo</leftbracket>
+ <i mod="longpress">playerprocessinfo</i>
+ <rightbracket></rightbracket> <!-- removes mapping from osmc-classic -->
+ </keyboard>
+ </VideoMenu>
+ <OSDVideoSettings>
+ <keyboard>
+ <leftbracket>info</leftbracket>
+ <i>info</i>
+ <leftbracket mod="longpress">playerprocessinfo</leftbracket>
+ <i mod="longpress">playerprocessinfo</i>
+ <rightbracket>back</rightbracket>
+ <c>back</c>
+ <stop>back</stop>
+ <x>back</x>
+ </keyboard>
+ </OSDVideoSettings>
+ <OSDAudioSettings>
+ <keyboard>
+ <leftbracket>info</leftbracket>
+ <i>info</i>
+ <leftbracket mod="longpress">playerprocessinfo</leftbracket>
+ <i mod="longpress">playerprocessinfo</i>
+ <rightbracket>back</rightbracket>
+ <c>back</c>
+ <stop>back</stop>
+ <x>back</x>
+ </keyboard>
+ </OSDAudioSettings>
+ <osdsubtitlesettings>
+ <keyboard>
+ <leftbracket>info</leftbracket>
+ <i>info</i>
+ <leftbracket mod="longpress">playerprocessinfo</leftbracket>
+ <i mod="longpress">playerprocessinfo</i>
+ <rightbracket>back</rightbracket>
+ <c>back</c>
+ <stop>back</stop>
+ <x>back</x>
+ </keyboard>
+ </osdsubtitlesettings>
+ <VideoBookmarks>
+ <keyboard>
+ <escape>back</escape>
+ <home>back</home>
+ <rightbracket mod="longpress">back</rightbracket>
+ <c mod="longpress">back</c>
+ </keyboard>
+ </VideoBookmarks>
+ <Videos>
+ <keyboard>
+ <escape>ActivateWindow(Home)</escape>
+ <home>ActivateWindow(Home)</home>
+ <escape mod="longpress">fullscreen</escape>
+ <home mod="longpress">fullscreen</home>
+ <return mod="longpress">SendClick(14)</return> <!-- Toggle view between unwatched and all videos -->
+ <play_pause mod="longpress">togglewatched</play_pause>
+ <p mod="longpress">togglewatched</p>
+ <browser_search>SendClick(8)</browser_search> <!-- Search -->
+ </keyboard>
+ </Videos>
+ <VideoPlaylist>
+ <keyboard>
+ <escape>ActivateWindow(Home)</escape>
+ <home>ActivateWindow(Home)</home>
+ <escape mod="longpress">fullscreen</escape>
+ <home mod="longpress">fullscreen</home>
+ <return mod="longpress">Back</return>
+ </keyboard>
+ </VideoPlaylist>
+ <ContextMenu>
+ <keyboard>
+ <rightbracket>Back</rightbracket>
+ <c>Back</c>
+ </keyboard>
+ </ContextMenu>
+ <MusicInformation>
+ <keyboard>
+ <escape>back</escape>
+ <home>back</home>
+ <leftbracket>Back</leftbracket>
+ <i>Back</i>
+ <leftbracket mod="longpress">noop</leftbracket> <!-- stops cycling -->
+ <i mod="longpress">noop</i> <!-- stops cycling -->
+ <rightbracket>Back</rightbracket>
+ <c>Back</c>
+ </keyboard>
+ </MusicInformation>
+ <MusicPlaylist>
+ <keyboard>
+ <escape>ActivateWindow(Home)</escape>
+ <home>ActivateWindow(Home)</home>
+ <escape mod="longpress">fullscreen</escape>
+ <home mod="longpress">fullscreen</home>
+ <return mod="longpress">back</return>
+ </keyboard>
+ </MusicPlaylist>
+ <SongInformation>
+ <keyboard>
+ <escape>back</escape>
+ <home>back</home>
+ <leftbracket>Back</leftbracket>
+ <i>Back</i>
+ <leftbracket mod="longpress">noop</leftbracket> <!-- stops cycling -->
+ <i mod="longpress">noop</i> <!-- stops cycling -->
+ <rightbracket>Back</rightbracket>
+ <c>Back</c>
+ </keyboard>
+ </SongInformation>
+ <MovieInformation>
+ <keyboard>
+ <escape>back</escape>
+ <home>back</home>
+ <leftbracket>Back</leftbracket>
+ <i>Back</i>
+ <leftbracket mod="longpress">noop</leftbracket> <!-- stops cycling -->
+ <i mod="longpress">noop</i> <!-- stops cycling -->
+ <rightbracket>Back</rightbracket>
+ <c>Back</c>
+ </keyboard>
+ </MovieInformation>
+ <PictureInfo>
+ <keyboard>
+ <escape>back</escape>
+ <home>back</home>
+ <leftbracket>Back</leftbracket>
+ <i>Back</i>
+ <leftbracket mod="longpress">noop</leftbracket> <!-- stops cycling -->
+ <i mod="longpress">noop</i> <!-- stops cycling -->
+ <rightbracket>Back</rightbracket>
+ <c>Back</c>
+ </keyboard>
+ </PictureInfo>
+ <FullscreenLiveTV>
+ <keyboard>
+ <rightbracket>ActivateWindow(PVROSDChannels)</rightbracket>
+ <c>ActivateWindow(PVROSDChannels)</c>
+ <leftbracket>info</leftbracket>
+ <i>info</i>
+ <leftbracket mod="longpress">playerprocessinfo</leftbracket>
+ <i mod="longpress">playerprocessinfo</i>
+ <left mod="longpress">AudioDelay</left>
+ <right mod="longpress">subtitledelay</right>
+ <return mod="longpress">Record</return>
+ <play_pause mod="longpress">showsubtitles</play_pause>
+ <p mod="longpress">showsubtitles</p>
+ <stop mod="longpress">ActivateWindow(Teletext)</stop>
+ <x mod="longpress">ActivateWindow(Teletext)</x>
+ </keyboard>
+ </FullscreenLiveTV>
+ <TVGuide>
+ <keyboard>
+ <return mod="longpress">Record</return>
+ </keyboard>
+ </TVGuide>
+ <FullscreenRadio>
+ <keyboard>
+ <rightbracket>ActivateWindow(PVROSDChannels)</rightbracket>
+ <c>ActivateWindow(PVROSDChannels)</c>
+ </keyboard>
+ </FullscreenRadio>
+ <AddonInformation>
+ <keyboard>
+ <escape>back</escape>
+ <home>back</home>
+ <leftbracket>Back</leftbracket>
+ <i>Back</i>
+ <leftbracket mod="longpress">noop</leftbracket> <!-- stops cycling -->
+ <i mod="longpress">noop</i> <!-- stops cycling -->
+ <rightbracket>Back</rightbracket>
+ <c>Back</c>
+ </keyboard>
+ </AddonInformation>
+ <PlayerProcessInfo>
+ <keyboard>
+ <leftbracket>back</leftbracket>
+ <i>back</i>
+ <rightbracket>ActivateWindow(osdvideosettings)</rightbracket>
+ <c>ActivateWindow(osdvideosettings)</c>
+ <rightbracket mod="longpress">noop</rightbracket>
+ <c mod="longpress">noop</c>
+ <stop mod="longpress">ActivateWindow(osdsubtitlesettings)</stop>
+ <x mod="longpress">ActivateWindow(osdsubtitlesettings)</x>
+ </keyboard>
+ </PlayerProcessInfo>
+ <yesnodialog> <!-- Added to allow CEC when update dialog box appears -->
+ <keyboard>
+ <escape>CECActivateSource</escape>
+ <home>CECActivateSource</home>
+ <escape mod="longpress">CECStandby</escape>
+ <home mod="longpress">CECStandby</home>
+ </keyboard>
+ </yesnodialog>
+ <selectdialog>
+ <keyboard>
+ <escape>back</escape>
+ <home>back</home>
+ </keyboard>
+ </selectdialog>
+ <contextmenu>
+ <keyboard>
+ <escape>back</escape>
+ <home>back</home>
+ </keyboard>
+ </contextmenu>
+ <addonsettings>
+ <keyboard>
+ <escape>back</escape>
+ <home>back</home>
+ </keyboard>
+ </addonsettings>
+ <addonbrowser>
+ <keyboard>
+ <escape>ActivateWindow(Home)</escape>
+ <home>ActivateWindow(Home)</home>
+ <escape mod="longpress">fullscreen</escape>
+ <home mod="longpress">fullscreen</home>
+ </keyboard>
+ </addonbrowser>
+ <filemanager>
+ <keyboard>
+ <escape>ActivateWindow(Home)</escape>
+ <home>ActivateWindow(Home)</home>
+ <escape mod="longpress">fullscreen</escape>
+ <home mod="longpress">fullscreen</home>
+ </keyboard>
+ </filemanager>
+ <interfacesettings>
+ <keyboard>
+ <escape>ActivateWindow(Home)</escape>
+ <home>ActivateWindow(Home)</home>
+ <escape mod="longpress">fullscreen</escape>
+ <home mod="longpress">fullscreen</home>
+ </keyboard>
+ </interfacesettings>
+ <systeminfo>
+ <keyboard>
+ <escape>ActivateWindow(Home)</escape>
+ <home>ActivateWindow(Home)</home>
+ <escape mod="longpress">fullscreen</escape>
+ <home mod="longpress">fullscreen</home>
+ </keyboard>
+ </systeminfo>
+ <eventlog>
+ <keyboard>
+ <escape>ActivateWindow(Home)</escape>
+ <home>ActivateWindow(Home)</home>
+ <escape mod="longpress">fullscreen</escape>
+ <home mod="longpress">fullscreen</home>
+ </keyboard>
+ </eventlog>
+ <playersettings>
+ <keyboard>
+ <escape>ActivateWindow(Home)</escape>
+ <home>ActivateWindow(Home)</home>
+ <escape mod="longpress">fullscreen</escape>
+ <home mod="longpress">fullscreen</home>
+ </keyboard>
+ </playersettings>
+ <mediasettings>
+ <keyboard>
+ <escape>ActivateWindow(Home)</escape>
+ <home>ActivateWindow(Home)</home>
+ <escape mod="longpress">fullscreen</escape>
+ <home mod="longpress">fullscreen</home>
+ </keyboard>
+ </mediasettings>
+ <pvrsettings>
+ <keyboard>
+ <escape>ActivateWindow(Home)</escape>
+ <home>ActivateWindow(Home)</home>
+ <escape mod="longpress">fullscreen</escape>
+ <home mod="longpress">fullscreen</home>
+ </keyboard>
+ </pvrsettings>
+ <servicesettings>
+ <keyboard>
+ <escape>ActivateWindow(Home)</escape>
+ <home>ActivateWindow(Home)</home>
+ <escape mod="longpress">fullscreen</escape>
+ <home mod="longpress">fullscreen</home>
+ </keyboard>
+ </servicesettings>
+ <gamesettings>
+ <keyboard>
+ <escape>ActivateWindow(Home)</escape>
+ <home>ActivateWindow(Home)</home>
+ <escape mod="longpress">fullscreen</escape>
+ <home mod="longpress">fullscreen</home>
+ </keyboard>
+ </gamesettings>
+ <profiles>
+ <keyboard>
+ <escape>ActivateWindow(Home)</escape>
+ <home>ActivateWindow(Home)</home>
+ <escape mod="longpress">fullscreen</escape>
+ <home mod="longpress">fullscreen</home>
+ </keyboard>
+ </profiles>
+ <systemsettings>
+ <keyboard>
+ <escape>ActivateWindow(Home)</escape>
+ <home>ActivateWindow(Home)</home>
+ <escape mod="longpress">fullscreen</escape>
+ <home mod="longpress">fullscreen</home>
+ </keyboard>
+ </systemsettings>
+ <music>
+ <keyboard>
+ <escape>ActivateWindow(Home)</escape>
+ <home>ActivateWindow(Home)</home>
+ <escape mod="longpress">fullscreen</escape>
+ <home mod="longpress">fullscreen</home>
+ <browser_search>SendClick(8)</browser_search> <!-- Search -->
+ </keyboard>
+ </music>
+ <pictures>
+ <keyboard>
+ <escape>ActivateWindow(Home)</escape>
+ <home>ActivateWindow(Home)</home>
+ <escape mod="longpress">fullscreen</escape>
+ <home mod="longpress">fullscreen</home>
+ </keyboard>
+ </pictures>
+ <skinsettings>
+ <keyboard>
+ <escape>ActivateWindow(Home)</escape>
+ <home>ActivateWindow(Home)</home>
+ <escape mod="longpress">fullscreen</escape>
+ <home mod="longpress">fullscreen</home>
+ </keyboard>
+ </skinsettings>
+ <musicplaylisteditor>
+ <keyboard>
+ <escape>ActivateWindow(Home)</escape>
+ <home>ActivateWindow(Home)</home>
+ <escape mod="longpress">fullscreen</escape>
+ <home mod="longpress">fullscreen</home>
+ </keyboard>
+ </musicplaylisteditor>
+ <games>
+ <keyboard>
+ <escape>ActivateWindow(Home)</escape>
+ <home>ActivateWindow(Home)</home>
+ <escape mod="longpress">fullscreen</escape>
+ <home mod="longpress">fullscreen</home>
+ </keyboard>
+ </games>
+ <programs>
+ <keyboard>
+ <escape>ActivateWindow(Home)</escape>
+ <home>ActivateWindow(Home)</home>
+ <escape mod="longpress">fullscreen</escape>
+ <home mod="longpress">fullscreen</home>
+ </keyboard>
+ </programs>
+ </keymap>
diff --git a/system/peripherals.xml b/system/peripherals.xml
index a110c0db60..66dd13aa9b 100644
--- a/system/peripherals.xml
+++ b/system/peripherals.xml
@@ -57,6 +57,11 @@
<setting key="keymap" value="osmc" label="35007" configurable="1"/>
</peripheral>
+ <peripheral vendor_product="2017:1710,2017:1720" bus="usb" name="OSMC RF Remote" mapTo="hid">
+ <setting key="do_not_use_custom_keymap" type="bool" value="0" label="35009" order="1"/>
+ <setting key="keymap" value="osmcv3" label="35007" configurable="1"/>
+ </peripheral>
+
<peripheral class="joystick" name="joystick" mapTo="joystick">
<setting key="appearance" type="addon" addontype="kodi.game.controller" label="480" order="1"/>
<setting key="left_stick_deadzone" type="float" label="35078" order="2" value="0.2" min="0.0" step="0.05" max="1.0" />
diff --git a/system/settings/settings.xml b/system/settings/settings.xml
index 987e366fd8..7434932d4a 100755
--- a/system/settings/settings.xml
+++ b/system/settings/settings.xml
@@ -1081,14 +1081,14 @@
<group id="1" label="593">
<setting id="myvideos.selectaction" type="integer" label="22079" help="36177">
<level>0</level>
- <default>1</default> <!-- SELECT_ACTION_PLAY_OR_RESUME -->
+ <default>1</default> <!-- ACTION_PLAY_OR_RESUME -->
<constraints>
<options>
- <option label="22080">0</option> <!-- SELECT_ACTION_CHOOSE -->
- <option label="208">1</option> <!-- SELECT_ACTION_PLAY_OR_RESUME -->
- <option label="13404">2</option> <!-- SELECT_ACTION_RESUME -->
- <option label="22081">3</option> <!-- SELECT_ACTION_INFO -->
- <option label="13347">7</option> <!-- SELECT_ACTION_QUEUE -->
+ <option label="22080">0</option> <!-- ACTION_CHOOSE -->
+ <option label="208">1</option> <!-- ACTION_PLAY_OR_RESUME -->
+ <option label="13404">2</option> <!-- ACTION_RESUME -->
+ <option label="22081">3</option> <!-- ACTION_INFO -->
+ <option label="13347">7</option> <!-- ACTION_QUEUE -->
</options>
</constraints>
<control type="list" format="string" />
@@ -1100,11 +1100,11 @@
</setting>
<setting id="myvideos.playaction" type="integer" label="22076" help="36204">
<level>0</level>
- <default>1</default> <!-- PLAY_ACTION_PLAY_OR_RESUME -->
+ <default>1</default> <!-- ACTION_PLAY_OR_RESUME -->
<constraints>
<options>
- <option label="22077">1</option> <!-- PLAY_ACTION_PLAY_OR_RESUME -->
- <option label="22078">2</option> <!-- PLAY_ACTION_RESUME -->
+ <option label="22077">1</option> <!-- ACTION_PLAY_OR_RESUME -->
+ <option label="22078">2</option> <!-- ACTION_RESUME -->
</options>
</constraints>
<control type="list" format="string" />
@@ -2015,6 +2015,18 @@
<default>true</default>
<control type="toggle" />
</setting>
+ <setting id="pvrrecord.deleteafterwatch" type="integer" label="860" help="861">
+ <level>2</level>
+ <default>0</default>
+ <constraints>
+ <options>
+ <option label="862">0</option> <!-- No delete -->
+ <option label="863">1</option> <!-- Ask to delete -->
+ <option label="864">2</option> <!-- Delete -->
+ </options>
+ </constraints>
+ <control type="list" format="string" />
+ </setting>
<setting id="pvrrecord.grouprecordings" type="boolean" label="" help="">
<default>true</default>
<level>4</level>
@@ -3259,6 +3271,18 @@
<default>true</default>
<control type="toggle" />
</setting>
+ <setting id="audiooutput.mixsublevel" type="integer" label="34114" help="34115">
+ <level>2</level>
+ <default>0</default>
+ <constraints>
+ <options>
+ <option label="34116">0</option>
+ <option label="34117">50</option>
+ <option label="34118">100</option>
+ </options>
+ </constraints>
+ <control type="list" format="string" />
+ </setting>
</group>
<group id="2" label="15108">
<setting id="audiooutput.guisoundmode" type="integer" label="34120" help="36373">
diff --git a/system/shaders/GLES/3.1/gles310_yuv2rgb.vert b/system/shaders/GLES/3.1/gles310_yuv2rgb.vert
new file mode 100644
index 0000000000..7f4dcdf649
--- /dev/null
+++ b/system/shaders/GLES/3.1/gles310_yuv2rgb.vert
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2024 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#version 310 es
+
+in vec4 m_attrpos;
+in vec2 m_attrcordY;
+in vec2 m_attrcordU;
+in vec2 m_attrcordV;
+out vec2 m_cordY;
+out vec2 m_cordU;
+out vec2 m_cordV;
+uniform mat4 m_proj;
+uniform mat4 m_model;
+
+void main()
+{
+ mat4 mvp = m_proj * m_model;
+ gl_Position = mvp * m_attrpos;
+ m_cordY = m_attrcordY;
+ m_cordU = m_attrcordU;
+ m_cordV = m_attrcordV;
+}
diff --git a/system/shaders/GLES/3.1/gles310_yuv2rgb_filter.frag b/system/shaders/GLES/3.1/gles310_yuv2rgb_filter.frag
new file mode 100644
index 0000000000..cabd56e3a2
--- /dev/null
+++ b/system/shaders/GLES/3.1/gles310_yuv2rgb_filter.frag
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2024 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#version 310 es
+
+precision highp float;
+
+uniform sampler2D m_sampY;
+uniform sampler2D m_sampU;
+uniform sampler2D m_sampV;
+uniform vec2 m_step;
+uniform mat4 m_yuvmat;
+uniform float m_stretch;
+uniform float m_alpha;
+uniform sampler2D m_kernelTex;
+uniform mat3 m_primMat;
+uniform float m_gammaDstInv;
+uniform float m_gammaSrc;
+uniform float m_toneP1;
+uniform float m_luminance;
+uniform vec3 m_coefsDst;
+in vec2 m_cordY;
+in vec2 m_cordU;
+in vec2 m_cordV;
+out vec4 fragColor;
+
+vec4[4] load4x4_0(sampler2D sampler, vec2 pos)
+{
+ vec4[4] tex4x4;
+ vec4 tex2x2 = textureGather(sampler, pos, 0);
+ tex4x4[0].xy = tex2x2.wz;
+ tex4x4[1].xy = tex2x2.xy;
+ tex2x2 = textureGatherOffset(sampler, pos, ivec2(2,0), 0);
+ tex4x4[0].zw = tex2x2.wz;
+ tex4x4[1].zw = tex2x2.xy;
+ tex2x2 = textureGatherOffset(sampler, pos, ivec2(0,2), 0);
+ tex4x4[2].xy = tex2x2.wz;
+ tex4x4[3].xy = tex2x2.xy;
+ tex2x2 = textureGatherOffset(sampler, pos, ivec2(2,2), 0);
+ tex4x4[2].zw = tex2x2.wz;
+ tex4x4[3].zw = tex2x2.xy;
+ return tex4x4;
+}
+
+float filter_0(sampler2D sampler, vec2 coord)
+{
+ vec2 pos = coord + m_step * 0.5;
+ vec2 f = fract(pos / m_step);
+
+ vec4 linetaps = texture(m_kernelTex, vec2(1.0 - f.x, 0.));
+ vec4 coltaps = texture(m_kernelTex, vec2(1.0 - f.y, 0.));
+ linetaps /= linetaps.r + linetaps.g + linetaps.b + linetaps.a;
+ coltaps /= coltaps.r + coltaps.g + coltaps.b + coltaps.a;
+ mat4 conv;
+ conv[0] = linetaps * coltaps.x;
+ conv[1] = linetaps * coltaps.y;
+ conv[2] = linetaps * coltaps.z;
+ conv[3] = linetaps * coltaps.w;
+
+ vec2 startPos = (-1.0 - f) * m_step + pos;
+ vec4[4] tex4x4 = load4x4_0(sampler, startPos);
+ vec4 imageLine0 = tex4x4[0];
+ vec4 imageLine1 = tex4x4[1];
+ vec4 imageLine2 = tex4x4[2];
+ vec4 imageLine3 = tex4x4[3];
+
+ return dot(imageLine0, conv[0]) +
+ dot(imageLine1, conv[1]) +
+ dot(imageLine2, conv[2]) +
+ dot(imageLine3, conv[3]);
+}
+
+void main()
+{
+ vec4 rgb;
+ vec4 yuv;
+
+#if defined(XBMC_YV12) || defined(XBMC_NV12)
+
+ yuv = vec4(filter_0(m_sampY, m_cordY),
+ texture2D(m_sampU, m_cordU).g,
+ texture2D(m_sampV, m_cordV).a,
+ 1.0);
+
+#elif defined(XBMC_NV12_RRG)
+
+ yuv = vec4(filter_0(m_sampY, m_cordY),
+ texture2D(m_sampU, m_cordU).r,
+ texture2D(m_sampV, m_cordV).g,
+ 1.0);
+
+#endif
+
+ rgb = m_yuvmat * yuv;
+ rgb.a = m_alpha;
+
+#if defined(XBMC_COL_CONVERSION)
+ rgb.rgb = pow(max(vec3(0), rgb.rgb), vec3(m_gammaSrc));
+ rgb.rgb = max(vec3(0), m_primMat * rgb.rgb);
+ rgb.rgb = pow(rgb.rgb, vec3(m_gammaDstInv));
+
+#if defined(KODI_TONE_MAPPING_REINHARD)
+ float luma = dot(rgb.rgb, m_coefsDst);
+ rgb.rgb *= reinhard(luma) / luma;
+
+#elif defined(KODI_TONE_MAPPING_ACES)
+ rgb.rgb = inversePQ(rgb.rgb);
+ rgb.rgb *= (10000.0 / m_luminance) * (2.0 / m_toneP1);
+ rgb.rgb = aces(rgb.rgb);
+ rgb.rgb *= (1.24 / m_toneP1);
+ rgb.rgb = pow(rgb.rgb, vec3(0.27));
+
+#elif defined(KODI_TONE_MAPPING_HABLE)
+ rgb.rgb = inversePQ(rgb.rgb);
+ rgb.rgb *= m_toneP1;
+ float wp = m_luminance / 100.0;
+ rgb.rgb = hable(rgb.rgb * wp) / hable(vec3(wp));
+ rgb.rgb = pow(rgb.rgb, vec3(1.0 / 2.2));
+#endif
+
+#endif
+
+ fragColor = rgb;
+}
diff --git a/tools/android/packaging/Makefile.in b/tools/android/packaging/Makefile.in
index d0e9d3cc7a..d29675783f 100644
--- a/tools/android/packaging/Makefile.in
+++ b/tools/android/packaging/Makefile.in
@@ -57,7 +57,7 @@ python: | xbmc/assets
cd xbmc/assets/python@PYTHON_VERSION@/lib/python@PYTHON_VERSION@/; rm -rf test config config-@PYTHON_VERSION@ lib-dynload ; find . -name "*.so" -exec rm -f {} \;
res:
- mkdir -p xbmc/res xbmc/res/values
+ mkdir -p xbmc/res xbmc/res/values xbmc/res/xml
cp -rfp media/mipmap-* xbmc/res/
cp -fp $(CMAKE_SOURCE_DIR)/media/applaunch_screen.png xbmc/res/drawable/
cp -fp $(CMAKE_SOURCE_DIR)/media/applaunch_screen.png xbmc/res/drawable-xxxhdpi/
diff --git a/tools/android/packaging/xbmc/res/xml/searchable.xml b/tools/android/packaging/xbmc/res/xml/searchable.xml
deleted file mode 100644
index af66a877fe..0000000000
--- a/tools/android/packaging/xbmc/res/xml/searchable.xml
+++ /dev/null
@@ -1,16 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<searchable xmlns:android="http://schemas.android.com/apk/res/android"
- android:label="@string/app_name"
- android:hint="@string/search_hint"
-
- android:searchSuggestAuthority="org.xbmc.kodi.media"
- android:searchSuggestPath="suggestions"
-
- android:searchSuggestIntentAction="android.intent.action.GET_CONTENT"
- android:searchSuggestIntentData="content://org.xbmc.kodi.media/intent"
-
- android:includeInGlobalSearch="true"
- android:searchSettingsDescription="Media"
- android:searchSuggestThreshold="3"
- >
-</searchable> \ No newline at end of file
diff --git a/tools/depends/Makefile.include.in b/tools/depends/Makefile.include.in
index 1903fecd85..8a42fea4bc 100644
--- a/tools/depends/Makefile.include.in
+++ b/tools/depends/Makefile.include.in
@@ -108,7 +108,7 @@ VERSION.TXT := $(CMAKE_SOURCE_DIR)/version.txt
APP_NAME=$(shell awk '/APP_NAME/ {print tolower($$2)}' $(VERSION.TXT))
# Python related vars
-PYTHON_VERSION=3.11
+PYTHON_VERSION=3.12
PYTHON_SITE_PKG=@prefix@/@deps_dir@/lib/python${PYTHON_VERSION}/site-packages
ifeq ($(CPU), arm64)
diff --git a/tools/depends/configure.ac b/tools/depends/configure.ac
index 80bca65150..15c395b005 100644
--- a/tools/depends/configure.ac
+++ b/tools/depends/configure.ac
@@ -797,6 +797,7 @@ cpp = $MESON_CXX
ar = '$AR'
strip = '$STRIP'
pkgconfig = '$prefix/$tool_dir/bin/pkg-config'
+pkg-config = '$prefix/$tool_dir/bin/pkg-config'
[host_machine]
system = '$meson_system'
diff --git a/tools/depends/native/Makefile b/tools/depends/native/Makefile
index 8fcb0a83a5..0ab2fb7579 100644
--- a/tools/depends/native/Makefile
+++ b/tools/depends/native/Makefile
@@ -26,6 +26,7 @@ NATIVE= \
openssl \
pcre2 \
perlmodule-parseyapp \
+ pythonmodule-setuptools \
pkg-config \
python3 \
swig \
@@ -83,12 +84,13 @@ libjpeg-turbo: cmake nasm
libpng: zlib automake
libtool: automake
Mako: MarkupSafe
-meson: python3
+meson: python3 pythonmodule-setuptools
ninja: meson
openssl: zlib
pcre2: cmake
pugixml: cmake
python3: $(EXPAT) $(LIBFFI) pkg-config zlib openssl autoconf-archive
+pythonmodule-setuptools: python3
swig: bison cmake pcre2
tar: xz automake
TexturePacker: cmake libpng liblzo2 giflib libjpeg-turbo
diff --git a/tools/depends/native/Mako/MAKO-VERSION b/tools/depends/native/Mako/MAKO-VERSION
new file mode 100644
index 0000000000..d863d172c7
--- /dev/null
+++ b/tools/depends/native/Mako/MAKO-VERSION
@@ -0,0 +1,4 @@
+LIBNAME=Mako
+VERSION=1.3.5
+ARCHIVE=$(LIBNAME)-$(VERSION).tar.gz
+SHA512=9a2f96bcb650f40cc2a9daa05904e54efca1fa30022ab641c850f6e32b84a38368d4c5d328f94ac4495ed97778d6ab0b661bc93a14740ed7e5d518f03bc9a59f
diff --git a/tools/depends/native/Mako/Makefile b/tools/depends/native/Mako/Makefile
index c31e462db4..559c88b0f2 100644
--- a/tools/depends/native/Mako/Makefile
+++ b/tools/depends/native/Mako/Makefile
@@ -1,14 +1,8 @@
-include ../../Makefile.include
+include ../../Makefile.include MAKO-VERSION ../../download-files.include
PREFIX=$(NATIVEPREFIX)
PLATFORM=$(NATIVEPLATFORM)
-DEPS = ../../Makefile.include Makefile ../../download-files.include
+DEPS = ../../Makefile.include Makefile MAKO-VERSION ../../download-files.include
-# lib name, version
-LIBNAME=Mako
-VERSION=1.1.3
-ARCHIVE=$(LIBNAME)-$(VERSION).tar.gz
-SHA512=a9b94fa34a61e7794b6e4549fa0bada6ff84dfb0d9edb8d5c7f9b95d12184fa4499f42303cfee720b576a9f7e986a57d91ad3aeb26c9f93154dbc08fb2975952
-include ../../download-files.include
all: .installed-$(PLATFORM)
diff --git a/tools/depends/native/MarkupSafe/01-all-GH399-removedistutils.patch b/tools/depends/native/MarkupSafe/01-all-GH399-removedistutils.patch
new file mode 100644
index 0000000000..029157b33f
--- /dev/null
+++ b/tools/depends/native/MarkupSafe/01-all-GH399-removedistutils.patch
@@ -0,0 +1,48 @@
+From 1f6697fcb27824fefd02b5c0890a319cf17fa3de Mon Sep 17 00:00:00 2001
+From: David Lord <davidism@gmail.com>
+Date: Thu, 7 Sep 2023 10:17:17 -0700
+Subject: [PATCH] import from setuptools instead of distutils
+
+---
+ setup.py | 14 +++++++-------
+ 1 files changed, 7 insertions(+), 7 deletions(-)
+
+diff --git a/setup.py b/setup.py
+index 7208cdd7..d19a4faa 100644
+--- a/setup.py
++++ b/setup.py
+@@ -2,12 +2,12 @@
+ import platform
+ import sys
+
+-from distutils.errors import CCompilerError
+-from distutils.errors import DistutilsExecError
+-from distutils.errors import DistutilsPlatformError
+ from setuptools import Extension
+ from setuptools import setup
+ from setuptools.command.build_ext import build_ext
++from setuptools.errors import CCompilerError
++from setuptools.errors import ExecError
++from setuptools.errors import PlatformError
+
+ ext_modules = [Extension("markupsafe._speedups", ["src/markupsafe/_speedups.c"])]
+
+@@ -21,14 +21,14 @@ class ve_build_ext(build_ext):
+
+ def run(self):
+ try:
+- build_ext.run(self)
+- except DistutilsPlatformError as e:
++ super().run()
++ except PlatformError as e:
+ raise BuildFailed() from e
+
+ def build_extension(self, ext):
+ try:
+- build_ext.build_extension(self, ext)
+- except (CCompilerError, DistutilsExecError, DistutilsPlatformError) as e:
++ super().build_extension(ext)
++ except (CCompilerError, ExecError, PlatformError) as e:
+ raise BuildFailed() from e
+ except ValueError as e:
+ # this can happen on Windows 64 bit, see Python issue 7511
diff --git a/tools/depends/native/MarkupSafe/MARKUPSAFE-VERSION b/tools/depends/native/MarkupSafe/MARKUPSAFE-VERSION
new file mode 100644
index 0000000000..d7b442fa7e
--- /dev/null
+++ b/tools/depends/native/MarkupSafe/MARKUPSAFE-VERSION
@@ -0,0 +1,4 @@
+LIBNAME=MarkupSafe
+VERSION=2.1.5
+ARCHIVE=$(LIBNAME)-$(VERSION).tar.gz
+SHA512=3ba5af43d23c266377f5d32b11e1faa7955ea8c67eb1c32886c308527f93e75e387294d0eec7794c0c20aad0c705b27f3d1f86b04202f3b63068d12d4053cc71
diff --git a/tools/depends/native/MarkupSafe/Makefile b/tools/depends/native/MarkupSafe/Makefile
index fa4372735e..358e5ccb39 100644
--- a/tools/depends/native/MarkupSafe/Makefile
+++ b/tools/depends/native/MarkupSafe/Makefile
@@ -1,14 +1,9 @@
-include ../../Makefile.include
+include ../../Makefile.include MARKUPSAFE-VERSION ../../download-files.include
PREFIX=$(NATIVEPREFIX)
PLATFORM=$(NATIVEPLATFORM)
-DEPS = ../../Makefile.include Makefile ../../download-files.include
+DEPS = ../../Makefile.include Makefile MARKUPSAFE-VERSION ../../download-files.include \
+ 01-all-GH399-removedistutils.patch
-# lib name, version
-LIBNAME=MarkupSafe
-VERSION=1.1.1
-ARCHIVE=$(LIBNAME)-$(VERSION).tar.gz
-SHA512=f3014e6131a3ab866914c5635b5397ef71906bffb1b6f8c5f2ed2acf167429ff7914236d38943e872683a57a9be9669f4c5aace6274f3307ab21ef25373db0b6
-include ../../download-files.include
all: .installed-$(PLATFORM)
@@ -17,6 +12,7 @@ $(PLATFORM): $(DEPS) | $(TARBALLS_LOCATION)/$(ARCHIVE).$(HASH_TYPE)
cd $(PLATFORM); $(ARCHIVE_TOOL) $(ARCHIVE_TOOL_FLAGS) $(TARBALLS_LOCATION)/$(ARCHIVE)
.installed-$(PLATFORM): $(PLATFORM)
+ cd $(PLATFORM); patch -p1 -i ../01-all-GH399-removedistutils.patch
cd $(PLATFORM); $(PREFIX)/bin/python3 setup.py install --prefix=$(PREFIX)
touch $@
diff --git a/tools/depends/native/TexturePacker/src/TexturePacker.cpp b/tools/depends/native/TexturePacker/src/TexturePacker.cpp
index 5c8304c5cb..9d4f270b23 100644
--- a/tools/depends/native/TexturePacker/src/TexturePacker.cpp
+++ b/tools/depends/native/TexturePacker/src/TexturePacker.cpp
@@ -56,40 +56,30 @@
namespace
{
-const char *GetFormatString(unsigned int format)
+const char* GetFormatString(KD_TEX_FMT format)
{
switch (format)
{
- case XB_FMT_DXT1:
- return "DXT1 ";
- case XB_FMT_DXT3:
- return "DXT3 ";
- case XB_FMT_DXT5:
- return "DXT5 ";
- case XB_FMT_DXT5_YCoCg:
- return "YCoCg";
- case XB_FMT_A8R8G8B8:
- return "ARGB ";
- case XB_FMT_A8:
- return "A8 ";
- default:
- return "?????";
+ case KD_TEX_FMT_SDR_R8:
+ return "R8 ";
+ case KD_TEX_FMT_SDR_RG8:
+ return "RG8 ";
+ case KD_TEX_FMT_SDR_RGBA8:
+ return "RGBA8";
+ case KD_TEX_FMT_SDR_BGRA8:
+ return "BGRA8";
+ default:
+ return "?????";
}
}
-bool HasAlpha(unsigned char* argb, unsigned int width, unsigned int height)
-{
- unsigned char* p = argb + 3; // offset of alpha
- for (unsigned int i = 0; i < 4 * width * height; i += 4)
- {
- if (p[i] != 0xff)
- return true;
- }
- return false;
-}
-
void Usage()
{
+ puts("Texture Packer Version 3");
+ puts("");
+ puts("Tool to pack XBT 3 texture files, used in Kodi Piers (v22).");
+ puts("Accepts the following file formats as input: PNG (preferred), JPG and GIF.");
+ puts("");
puts("Usage:");
puts(" -help Show this screen.");
puts(" -input <dir> Input directory. Default: current dir");
@@ -122,6 +112,10 @@ private:
bool CheckDupe(MD5Context* ctx, unsigned int pos);
+ void ConvertToSingleChannel(RGBAImage& image, uint32_t channel);
+ void ConvertToDualChannel(RGBAImage& image);
+ void ReduceChannels(RGBAImage& image);
+
DecoderManager decoderManager;
std::map<std::string, unsigned int> m_hashes;
@@ -196,12 +190,13 @@ CXBTFFrame TexturePacker::CreateXBTFFrame(DecodedFrame& decodedFrame, CXBTFWrite
const unsigned int delay = decodedFrame.delay;
const unsigned int width = decodedFrame.rgbaImage.width;
const unsigned int height = decodedFrame.rgbaImage.height;
- const unsigned int size = width * height * 4;
- const XB_FMT format = XB_FMT_A8R8G8B8;
+ const uint32_t bpp = decodedFrame.rgbaImage.bbp;
+ const unsigned int size = width * height * (bpp / 8);
+ const uint32_t format = static_cast<uint32_t>(decodedFrame.rgbaImage.textureFormat) |
+ static_cast<uint32_t>(decodedFrame.rgbaImage.textureAlpha) |
+ static_cast<uint32_t>(decodedFrame.rgbaImage.textureSwizzle);
unsigned char* data = (unsigned char*)decodedFrame.rgbaImage.pixels.data();
- const bool hasAlpha = HasAlpha(data, width, height);
-
CXBTFFrame frame;
lzo_uint packedSize = size;
@@ -246,7 +241,7 @@ CXBTFFrame TexturePacker::CreateXBTFFrame(DecodedFrame& decodedFrame, CXBTFWrite
frame.SetUnpackedSize(size);
frame.SetWidth(width);
frame.SetHeight(height);
- frame.SetFormat(hasAlpha ? format : static_cast<XB_FMT>(format | XB_FMT_OPAQUE));
+ frame.SetFormat(format);
frame.SetDuration(delay);
return frame;
}
@@ -278,6 +273,111 @@ bool TexturePacker::CheckDupe(MD5Context* ctx,
return false;
}
+void TexturePacker::ConvertToSingleChannel(RGBAImage& image, uint32_t channel)
+{
+ uint32_t size = (image.width * image.height);
+ for (uint32_t i = 0; i < size; i++)
+ {
+ image.pixels[i] = image.pixels[i * 4 + channel];
+ }
+
+ image.textureFormat = KD_TEX_FMT_SDR_R8;
+
+ image.bbp = 8;
+ image.pitch = 1 * image.width;
+}
+
+void TexturePacker::ConvertToDualChannel(RGBAImage& image)
+{
+ uint32_t size = (image.width * image.height);
+ for (uint32_t i = 0; i < size; i++)
+ {
+ image.pixels[i * 2] = image.pixels[i * 4];
+ image.pixels[i * 2 + 1] = image.pixels[i * 4 + 3];
+ }
+ image.textureFormat = KD_TEX_FMT_SDR_RG8;
+ image.bbp = 16;
+ image.pitch = 2 * image.width;
+}
+
+void TexturePacker::ReduceChannels(RGBAImage& image)
+{
+ if (image.textureFormat != KD_TEX_FMT_SDR_BGRA8)
+ return;
+
+ uint32_t size = (image.width * image.height);
+ uint8_t red = image.pixels[0];
+ uint8_t green = image.pixels[1];
+ uint8_t blue = image.pixels[2];
+ uint8_t alpha = image.pixels[3];
+ bool uniformRed = true;
+ bool uniformGreen = true;
+ bool uniformBlue = true;
+ bool uniformAlpha = true;
+ bool isGrey = true;
+ bool isIntensity = true;
+
+ // Checks each pixel for various properties.
+ for (uint32_t i = 0; i < size; i++)
+ {
+ if (image.pixels[i * 4] != red)
+ uniformRed = false;
+ if (image.pixels[i * 4 + 1] != green)
+ uniformGreen = false;
+ if (image.pixels[i * 4 + 2] != blue)
+ uniformBlue = false;
+ if (image.pixels[i * 4 + 3] != alpha)
+ uniformAlpha = false;
+ if (image.pixels[i * 4] != image.pixels[i * 4 + 1] ||
+ image.pixels[i * 4] != image.pixels[i * 4 + 2])
+ isGrey = false;
+ if (image.pixels[i * 4] != image.pixels[i * 4 + 1] ||
+ image.pixels[i * 4] != image.pixels[i * 4 + 2] ||
+ image.pixels[i * 4] != image.pixels[i * 4 + 3])
+ isIntensity = false;
+ }
+
+ if (uniformAlpha && alpha != 0xff)
+ printf("WARNING: uniform alpha detected! Consider using diffusecolor!\n");
+
+ bool isWhite = red == 0xff && green == 0xff && blue == 0xff;
+ if (uniformRed && uniformGreen && uniformBlue && !isWhite)
+ printf("WARNING: uniform color detected! Consider using diffusecolor!\n");
+
+ if (isIntensity)
+ {
+ // this is an intensity (GL_INTENSITY) texture
+ ConvertToSingleChannel(image, 0);
+ image.textureSwizzle = KD_TEX_SWIZ_RRRR;
+ }
+ else if (uniformAlpha && alpha == 0xff)
+ {
+ // we have a opaque texture, L or RGBX
+ if (isGrey)
+ {
+ ConvertToSingleChannel(image, 1);
+ image.textureSwizzle = KD_TEX_SWIZ_RRR1;
+ }
+ image.textureAlpha = KD_TEX_ALPHA_OPAQUE;
+ }
+ else if (uniformRed && uniformGreen && uniformBlue && isWhite)
+ {
+ // an alpha only texture
+ ConvertToSingleChannel(image, 3);
+ image.textureSwizzle = KD_TEX_SWIZ_111R;
+ }
+ else if (isGrey)
+ {
+ // a LA texture
+ ConvertToDualChannel(image);
+ image.textureSwizzle = KD_TEX_SWIZ_RRRG;
+ }
+ else
+ {
+ // BGRA
+ }
+}
+
int TexturePacker::createBundle(const std::string& InputDir, const std::string& OutputFile)
{
CXBTFWriter writer(OutputFile);
@@ -312,6 +412,9 @@ int TexturePacker::createBundle(const std::string& InputDir, const std::string&
continue;
}
+ for (unsigned int j = 0; j < frames.frameList.size(); j++)
+ ReduceChannels(frames.frameList[j].rgbaImage);
+
printf("%s\n", output.c_str());
bool skip=false;
if (m_dupecheck)
@@ -342,7 +445,7 @@ int TexturePacker::createBundle(const std::string& InputDir, const std::string&
file.GetFrames().push_back(frame);
printf(" frame %4i (delay:%4i) %s%c (%d,%d @ %" PRIu64
" bytes)\n",
- j, frame.GetDuration(), GetFormatString(frame.GetFormat()),
+ j, frame.GetDuration(), GetFormatString(frame.GetKDFormat()),
frame.HasAlpha() ? ' ' : '*', frame.GetWidth(), frame.GetHeight(),
frame.GetUnpackedSize());
}
diff --git a/tools/depends/native/TexturePacker/src/decoder/IDecoder.h b/tools/depends/native/TexturePacker/src/decoder/IDecoder.h
index 5bc06f2f96..63b5dd8607 100644
--- a/tools/depends/native/TexturePacker/src/decoder/IDecoder.h
+++ b/tools/depends/native/TexturePacker/src/decoder/IDecoder.h
@@ -20,6 +20,8 @@
#pragma once
+#include "guilib/TextureFormats.h"
+
#include <cstdint>
#include <string>
#include <vector>
@@ -52,8 +54,11 @@ public:
std::vector<uint8_t> pixels;
int width = 0; // width
int height = 0; // height
- int bbp = 0; // bits per pixel
+ int bbp = 32; // bits per pixel
int pitch = 0; // rowsize in bytes
+ KD_TEX_FMT textureFormat{KD_TEX_FMT_SDR_BGRA8};
+ KD_TEX_ALPHA textureAlpha{KD_TEX_ALPHA_STRAIGHT};
+ KD_TEX_SWIZ textureSwizzle{KD_TEX_SWIZ_RGBA};
};
class DecodedFrame
diff --git a/tools/depends/native/expat/EXPAT-VERSION b/tools/depends/native/expat/EXPAT-VERSION
index 3631ff3f5b..dd337f57dc 100644
--- a/tools/depends/native/expat/EXPAT-VERSION
+++ b/tools/depends/native/expat/EXPAT-VERSION
@@ -1,5 +1,5 @@
LIBNAME=expat
-VERSION=2.6.2
+VERSION=2.6.3
SOURCE=$(LIBNAME)-$(VERSION)
ARCHIVE=$(SOURCE).tar.xz
-SHA512=47b60967d6346d330dded87ea1a2957aa7d34dd825043386a89aa131054714f618ede57bfe97cf6caa40582a4bc67e198d2a915e7d8dbe8ee4f581857c2e3c2e
+SHA512=e02c4ad88f9d539258aa1c1db71ded7770a8f12c77b5535e5b34f040ae5b1361ef23132f16d96bdb7c096a83acd637a7c907916bdfcc6d5cfb9e35d04020ca0b
diff --git a/tools/depends/native/meson/MESON-VERSION b/tools/depends/native/meson/MESON-VERSION
index cabee64aa2..c2f22ee47b 100644
--- a/tools/depends/native/meson/MESON-VERSION
+++ b/tools/depends/native/meson/MESON-VERSION
@@ -1,4 +1,4 @@
LIBNAME=meson
-VERSION=1.2.1
+VERSION=1.5.1
ARCHIVE=$(LIBNAME)-$(VERSION).tar.gz
-SHA512=6221a14a6046aaba2c6eb601a9a5b928308bbd9da813ccec16b8f7578296b27d741e30e9343723770c3c7825c86b53193b41b9672dd17468d06d3b8d743bf52e
+SHA512=3239d6f3d64dcedddd456dc451278a37aa6c4460708b0efdff1b04b6e8844c20f5f882060de311c59a678bebd51ee09e1906c9384d4b0c85b28015fd1713ab0a
diff --git a/tools/depends/native/openssl/OPENSSL-VERSION b/tools/depends/native/openssl/OPENSSL-VERSION
index 424c6d4036..a7b9b2c717 100644
--- a/tools/depends/native/openssl/OPENSSL-VERSION
+++ b/tools/depends/native/openssl/OPENSSL-VERSION
@@ -1,4 +1,4 @@
LIBNAME=openssl
-VERSION=3.0.13
+VERSION=3.0.15
ARCHIVE=$(LIBNAME)-$(VERSION).tar.gz
-SHA512=22f4096781f0b075f5bf81bd39a0f97e111760dfa73b6f858f6bb54968a7847944d74969ae10f9a51cc21a2f4af20d9a4c463649dc824f5e439e196d6764c4f9
+SHA512=acd80f2f7924d90c1416946a5c61eff461926ad60f4821bb6b08845ea18f8452fd5e88a2c2c5bd0d7590a792cb8341a3f3be042fd0a5b6c9c1b84a497c347bbf
diff --git a/tools/depends/native/python3/01-distutil-flags.patch b/tools/depends/native/python3/01-distutil-flags.patch
deleted file mode 100644
index 28cd2f98ba..0000000000
--- a/tools/depends/native/python3/01-distutil-flags.patch
+++ /dev/null
@@ -1,12 +0,0 @@
---- a/Lib/distutils/sysconfig.py
-+++ b/Lib/distutils/sysconfig.py
-@@ -214,6 +214,9 @@
- (cc, cxx, cflags, ccshared, ldshared, shlib_suffix, ar, ar_flags) = \
- get_config_vars('CC', 'CXX', 'CFLAGS',
- 'CCSHARED', 'LDSHARED', 'SHLIB_SUFFIX', 'AR', 'ARFLAGS')
-+ # get_config_vars returns host vars. clear cflags, ldshared for crosscompile use
-+ cflags = ""
-+ ldshared = cc + " -shared"
-
- if 'CC' in os.environ:
- newcc = os.environ['CC']
diff --git a/tools/depends/native/python3/Makefile b/tools/depends/native/python3/Makefile
index 74ebe061a0..bb93cc87e7 100644
--- a/tools/depends/native/python3/Makefile
+++ b/tools/depends/native/python3/Makefile
@@ -1,7 +1,6 @@
include ../../Makefile.include PYTHON3-VERSION ../../download-files.include
PLATFORM=$(NATIVEPLATFORM)
-DEPS = ../../Makefile.include Makefile PYTHON3-VERSION ../../download-files.include \
- 01-distutil-flags.patch
+DEPS = ../../Makefile.include Makefile PYTHON3-VERSION ../../download-files.include
CONFIGURE=./configure --prefix=$(NATIVEPREFIX) \
--disable-shared \
@@ -32,11 +31,7 @@ $(LIBDYLIB): $(PLATFORM)
cd $(PLATFORM); $(MAKE)
.installed-$(PLATFORM): $(LIBDYLIB)
- cd $(PLATFORM); patch -p1 -i ../01-distutil-flags.patch
cd $(PLATFORM); $(MAKE) install
-# Sed patch setuptools that is installed via ensurepip as we cant patch the source
- cd $(NATIVE_SITEPACKAGES); sed -ie "s|cflags = cflags + ' ' + os.environ\['CFLAGS'\]|cflags = os.environ\['CFLAGS'\]|" setuptools/_distutils/sysconfig.py
- touch $(LIBDYLIB)
touch $@
clean:
diff --git a/tools/depends/native/python3/PYTHON3-VERSION b/tools/depends/native/python3/PYTHON3-VERSION
index c5e1760fb2..9c97bd4018 100644
--- a/tools/depends/native/python3/PYTHON3-VERSION
+++ b/tools/depends/native/python3/PYTHON3-VERSION
@@ -1,4 +1,4 @@
LIBNAME=Python
-VERSION=3.11.7
+VERSION=3.12.5
ARCHIVE=$(LIBNAME)-$(VERSION).tar.xz
-SHA512=11e06f2ffe1f66888cb5b4e9f607de815294d6863a77eda6ec6d7c724ef158df9f51881f4a956d4a6fa973c2fb6fd031d495e3496e9b0bb53793fb1cc8434c63
+SHA512=7a1c30d798434fe24697bc253f6010d75145e7650f66803328425c8525331b9fa6b63d12a652687582db205f8d4c8279c8f73c338168592481517b063351c921
diff --git a/tools/depends/native/pythonmodule-setuptools/01-distutils-flag.patch b/tools/depends/native/pythonmodule-setuptools/01-distutils-flag.patch
new file mode 100644
index 0000000000..21655c228c
--- /dev/null
+++ b/tools/depends/native/pythonmodule-setuptools/01-distutils-flag.patch
@@ -0,0 +1,15 @@
+--- a/setuptools/_distutils/sysconfig.py
++++ b/setuptools/_distutils/sysconfig.py
+@@ -317,6 +317,12 @@
+ 'AR',
+ 'ARFLAGS',
+ )
++
++ # get_config_vars returns host vars. clear cflags, ldshared for crosscompile use
++ cflags = ""
++ cppflags = ""
++ ldflags = ""
++ ldshared = cc + " -shared"
+
+ if 'CC' in os.environ:
+ newcc = os.environ['CC']
diff --git a/tools/depends/native/pythonmodule-setuptools/Makefile b/tools/depends/native/pythonmodule-setuptools/Makefile
new file mode 100644
index 0000000000..f9a2da83a5
--- /dev/null
+++ b/tools/depends/native/pythonmodule-setuptools/Makefile
@@ -0,0 +1,23 @@
+include ../../Makefile.include PYTHONMODULE-SETUPTOOLS-VERSION ../../download-files.include
+PLATFORM=$(NATIVEPLATFORM)
+DEPS= ../../Makefile.include Makefile PYTHONMODULE-SETUPTOOLS-VERSION ../../download-files.include 01-distutils-flag.patch
+
+LIBDYLIB=$(PLATFORM)/dist/$(LIBNAME)-$(VERSION)-py$(PYTHON_VERSION).egg
+
+all: .installed-$(PLATFORM)
+
+$(PLATFORM): $(DEPS) | $(TARBALLS_LOCATION)/$(ARCHIVE).$(HASH_TYPE)
+ rm -rf $(PLATFORM)/*; mkdir -p $(PLATFORM)
+ cd $(PLATFORM); $(ARCHIVE_TOOL) $(ARCHIVE_TOOL_FLAGS) $(TARBALLS_LOCATION)/$(ARCHIVE)
+
+.installed-$(PLATFORM): $(PLATFORM)
+ cd $(PLATFORM); patch -p1 -i ../01-distutils-flag.patch
+ cd $(PLATFORM); PYTHONPATH="$(NATIVEPREFIX)/lib/python${PYTHON_VERSION}/site-packages" $(NATIVEPREFIX)/bin/python3 setup.py install --prefix=$(NATIVEPREFIX)
+ touch $@
+
+clean:
+ $(MAKE) -C $(PLATFORM) clean
+ rm -f .installed-$(PLATFORM)
+
+distclean::
+ rm -rf $(PLATFORM) .installed-$(PLATFORM)
diff --git a/tools/depends/native/pythonmodule-setuptools/PYTHONMODULE-SETUPTOOLS-VERSION b/tools/depends/native/pythonmodule-setuptools/PYTHONMODULE-SETUPTOOLS-VERSION
new file mode 100644
index 0000000000..1bf06499f3
--- /dev/null
+++ b/tools/depends/native/pythonmodule-setuptools/PYTHONMODULE-SETUPTOOLS-VERSION
@@ -0,0 +1,4 @@
+LIBNAME=setuptools
+VERSION=72.1.0
+ARCHIVE=$(LIBNAME)-$(VERSION).tar.gz
+SHA512=d0a34f16dfa6bb9a6df39076cd43528cf854d343f6f801c448ea0ebab2a259aec3d03571e2a26709df6082ed2fcb6c43b86448be556fd559b6af41831b4f38e0
diff --git a/tools/depends/target/curl/01-win-nghttp2-add-name.patch b/tools/depends/target/curl/01-win-nghttp2-add-name.patch
deleted file mode 100644
index e07843010a..0000000000
--- a/tools/depends/target/curl/01-win-nghttp2-add-name.patch
+++ /dev/null
@@ -1,11 +0,0 @@
---- a/CMake/FindNGHTTP2.cmake
-+++ b/CMake/FindNGHTTP2.cmake
-@@ -25,7 +25,7 @@
-
- find_path(NGHTTP2_INCLUDE_DIR "nghttp2/nghttp2.h")
-
--find_library(NGHTTP2_LIBRARY NAMES nghttp2)
-+find_library(NGHTTP2_LIBRARY NAMES nghttp2 nghttp2_static)
-
- find_package_handle_standard_args(NGHTTP2
- FOUND_VAR
diff --git a/tools/depends/target/curl/CURL-VERSION b/tools/depends/target/curl/CURL-VERSION
index f9a42c2939..50facb2358 100644
--- a/tools/depends/target/curl/CURL-VERSION
+++ b/tools/depends/target/curl/CURL-VERSION
@@ -1,6 +1,6 @@
LIBNAME=curl
-VERSION=8.7.1
+VERSION=8.10.0
ARCHIVE=$(LIBNAME)-$(VERSION).tar.xz
-SHA512=5bbde9d5648e9226f5490fa951690aaf159149345f3a315df2ba58b2468f3e59ca32e8a49734338afc861803a4f81caac6d642a4699b72c6310ebfb1f618aad2
+SHA512=055277695ea242fcb0bf26ca6c4867a385cd578cd73ed4c5c4a020233248044c1ecaebcbaeaac47d3ffe07a41300ea5fc86396d7e812137cf75ed3e1b54ca5b2
BYPRODUCT=libcurl.a
BYPRODUCT_WIN=libcurl.lib
diff --git a/tools/depends/target/expat/EXPAT-VERSION b/tools/depends/target/expat/EXPAT-VERSION
index 571fe9f0e6..ba82abe72f 100644
--- a/tools/depends/target/expat/EXPAT-VERSION
+++ b/tools/depends/target/expat/EXPAT-VERSION
@@ -1,6 +1,6 @@
LIBNAME=expat
-VERSION=2.6.2
+VERSION=2.6.3
SOURCE=$(LIBNAME)-$(VERSION)
ARCHIVE=$(SOURCE).tar.xz
-SHA512=47b60967d6346d330dded87ea1a2957aa7d34dd825043386a89aa131054714f618ede57bfe97cf6caa40582a4bc67e198d2a915e7d8dbe8ee4f581857c2e3c2e
+SHA512=e02c4ad88f9d539258aa1c1db71ded7770a8f12c77b5535e5b34f040ae5b1361ef23132f16d96bdb7c096a83acd637a7c907916bdfcc6d5cfb9e35d04020ca0b
BYPRODUCT=libexpat.a
diff --git a/tools/depends/target/mesa/01-all-py312-setuptools.patch b/tools/depends/target/mesa/01-all-py312-setuptools.patch
new file mode 100644
index 0000000000..fda2061207
--- /dev/null
+++ b/tools/depends/target/mesa/01-all-py312-setuptools.patch
@@ -0,0 +1,10 @@
+--- a/meson.build
++++ b/meson.build
+@@ -1022,6 +1022,7 @@
+ has_mako = run_command(
+ prog_python, '-c',
+ '''
++import setuptools
+ from distutils.version import StrictVersion
+ import mako
+ assert StrictVersion(mako.__version__) > StrictVersion("0.8.0")
diff --git a/tools/depends/target/mesa/Makefile b/tools/depends/target/mesa/Makefile
index 4e88970452..9312d35512 100644
--- a/tools/depends/target/mesa/Makefile
+++ b/tools/depends/target/mesa/Makefile
@@ -1,5 +1,6 @@
include ../../Makefile.include
-DEPS =../../Makefile.include Makefile ../../download-files.include
+DEPS =../../Makefile.include Makefile ../../download-files.include \
+ 01-all-py312-setuptools.patch
LIBNAME=mesa
VERSION=23.0.1
@@ -46,13 +47,13 @@ CONFIGURE = $(NATIVEPREFIX)/bin/python3 $(NATIVEPREFIX)/bin/meson \
-Dbuild-tests=false \
-Dselinux=false \
-Dosmesa=false \
- -Ddri3=false \
+ -Ddri3=disabled \
-Dglx=disabled \
-Dglvnd=false \
-Dllvm=false \
- -Dgallium-vdpau=false \
- -Dgallium-va=false \
- -Dgallium-xa=false \
+ -Dgallium-vdpau=disabled \
+ -Dgallium-va=disabled \
+ -Dgallium-xa=disabled \
-Dgles1=disabled \
-Dgles2=enabled \
$(MESA_EXTRA)
@@ -76,6 +77,7 @@ $(PLATFORM): $(DEPS) | $(TARBALLS_LOCATION)/$(ARCHIVE).$(HASH_TYPE)
rm -rf $(PLATFORM)/*; mkdir -p $(PLATFORM)
cd $(PLATFORM); $(ARCHIVE_TOOL) $(ARCHIVE_TOOL_FLAGS) $(TARBALLS_LOCATION)/$(ARCHIVE)
cd $(PLATFORM); rm -rf build; mkdir -p build
+ cd $(PLATFORM); patch -p1 -i ../01-all-py312-setuptools.patch
cd $(PLATFORM); $(CONFIGURE) . build
$(LIBDYLIB): $(PLATFORM)
diff --git a/tools/depends/target/openssl/OPENSSL-VERSION b/tools/depends/target/openssl/OPENSSL-VERSION
index 424c6d4036..a7b9b2c717 100644
--- a/tools/depends/target/openssl/OPENSSL-VERSION
+++ b/tools/depends/target/openssl/OPENSSL-VERSION
@@ -1,4 +1,4 @@
LIBNAME=openssl
-VERSION=3.0.13
+VERSION=3.0.15
ARCHIVE=$(LIBNAME)-$(VERSION).tar.gz
-SHA512=22f4096781f0b075f5bf81bd39a0f97e111760dfa73b6f858f6bb54968a7847944d74969ae10f9a51cc21a2f4af20d9a4c463649dc824f5e439e196d6764c4f9
+SHA512=acd80f2f7924d90c1416946a5c61eff461926ad60f4821bb6b08845ea18f8452fd5e88a2c2c5bd0d7590a792cb8341a3f3be042fd0a5b6c9c1b84a497c347bbf
diff --git a/tools/depends/target/python3/01-py312-cpython118618-1.patch b/tools/depends/target/python3/01-py312-cpython118618-1.patch
new file mode 100644
index 0000000000..5055a39779
--- /dev/null
+++ b/tools/depends/target/python3/01-py312-cpython118618-1.patch
@@ -0,0 +1,369 @@
+From 21f8fbaa7c01a8ec2fa2420f44f5cb05a54f55b6 Mon Sep 17 00:00:00 2001
+From: Neil Schemenauer <nas@arctrix.com>
+Date: Wed, 27 Mar 2024 09:54:02 -0700
+Subject: [PATCH] Use pointer for interp->obmalloc state.
+
+For interpreters that share state with the main interpreter, this points
+to the same static memory structure. For interpreters with their own
+obmalloc state, it is heap allocated. Add free_obmalloc_arenas() which
+will free the obmalloc arenas and radix tree structures for interpreters
+with their own obmalloc state.
+---
+ Include/internal/pycore_interp.h | 12 +-
+ Include/internal/pycore_obmalloc.h | 2 +
+ Include/internal/pycore_obmalloc_init.h | 7 -
+ Include/internal/pycore_runtime_init.h | 1 -
+ ...-12-22-13-21-39.gh-issue-113055.47xBMF.rst | 5 +
+ Objects/obmalloc.c | 121 +++++++++++++++++-
+ Python/pylifecycle.c | 16 +++
+ Python/pystate.c | 13 +-
+ Tools/c-analyzer/cpython/ignored.tsv | 3 +-
+ 9 files changed, 157 insertions(+), 23 deletions(-)
+ create mode 100644 Misc/NEWS.d/next/Core and Builtins/2023-12-22-13-21-39.gh-issue-113055.47xBMF.rst
+
+diff --git a/Include/internal/pycore_interp.h b/Include/internal/pycore_interp.h
+index 37cc88ed081b72..a0ef5990259e29 100644
+--- a/Include/internal/pycore_interp.h
++++ b/Include/internal/pycore_interp.h
+@@ -178,7 +178,17 @@ struct _is {
+ struct _warnings_runtime_state warnings;
+ struct atexit_state atexit;
+
+- struct _obmalloc_state obmalloc;
++ // Per-interpreter state for the obmalloc allocator. For the main
++ // interpreter and for all interpreters that don't have their
++ // own obmalloc state, this points to the static structure in
++ // obmalloc.c obmalloc_state_main. For other interpreters, it is
++ // heap allocated by _PyMem_init_obmalloc() and freed when the
++ // interpreter structure is freed. In the case of a heap allocated
++ // obmalloc state, it is not safe to hold on to or use memory after
++ // the interpreter is freed. The obmalloc state corresponding to
++ // that allocated memory is gone. See free_obmalloc_arenas() for
++ // more comments.
++ struct _obmalloc_state *obmalloc;
+
+ PyObject *audit_hooks;
+ PyType_WatchCallback type_watchers[TYPE_MAX_WATCHERS];
+diff --git a/Include/internal/pycore_obmalloc.h b/Include/internal/pycore_obmalloc.h
+index b1c00654ac1c5d..38427e194956ac 100644
+--- a/Include/internal/pycore_obmalloc.h
++++ b/Include/internal/pycore_obmalloc.h
+@@ -686,6 +686,8 @@ extern Py_ssize_t _Py_GetGlobalAllocatedBlocks(void);
+ _Py_GetGlobalAllocatedBlocks()
+ extern Py_ssize_t _PyInterpreterState_GetAllocatedBlocks(PyInterpreterState *);
+ extern void _PyInterpreterState_FinalizeAllocatedBlocks(PyInterpreterState *);
++extern int _PyMem_init_obmalloc(PyInterpreterState *interp);
++extern bool _PyMem_obmalloc_state_on_heap(PyInterpreterState *interp);
+
+
+ #ifdef WITH_PYMALLOC
+diff --git a/Include/internal/pycore_obmalloc_init.h b/Include/internal/pycore_obmalloc_init.h
+index 8ee72ff2d4126f..e6811b7aeca73c 100644
+--- a/Include/internal/pycore_obmalloc_init.h
++++ b/Include/internal/pycore_obmalloc_init.h
+@@ -59,13 +59,6 @@ extern "C" {
+ .dump_debug_stats = -1, \
+ }
+
+-#define _obmalloc_state_INIT(obmalloc) \
+- { \
+- .pools = { \
+- .used = _obmalloc_pools_INIT(obmalloc.pools), \
+- }, \
+- }
+-
+
+ #ifdef __cplusplus
+ }
+diff --git a/Include/internal/pycore_runtime_init.h b/Include/internal/pycore_runtime_init.h
+index e5f9e17efff24b..d3a64b3d4a7895 100644
+--- a/Include/internal/pycore_runtime_init.h
++++ b/Include/internal/pycore_runtime_init.h
+@@ -88,7 +88,6 @@ extern PyTypeObject _PyExc_MemoryError;
+ { \
+ .id_refcount = -1, \
+ .imports = IMPORTS_INIT, \
+- .obmalloc = _obmalloc_state_INIT(INTERP.obmalloc), \
+ .ceval = { \
+ .recursion_limit = Py_DEFAULT_RECURSION_LIMIT, \
+ }, \
+diff --git a/Misc/NEWS.d/next/Core and Builtins/2023-12-22-13-21-39.gh-issue-113055.47xBMF.rst b/Misc/NEWS.d/next/Core and Builtins/2023-12-22-13-21-39.gh-issue-113055.47xBMF.rst
+new file mode 100644
+index 00000000000000..90f49272218c96
+--- /dev/null
++++ b/Misc/NEWS.d/next/Core and Builtins/2023-12-22-13-21-39.gh-issue-113055.47xBMF.rst
+@@ -0,0 +1,5 @@
++Make interp->obmalloc a pointer. For interpreters that share state with the
++main interpreter, this points to the same static memory structure. For
++interpreters with their own obmalloc state, it is heap allocated. Add
++free_obmalloc_arenas() which will free the obmalloc arenas and radix tree
++structures for interpreters with their own obmalloc state.
+diff --git a/Objects/obmalloc.c b/Objects/obmalloc.c
+index 9620a8fbb44cac..acbefef614195c 100644
+--- a/Objects/obmalloc.c
++++ b/Objects/obmalloc.c
+@@ -3,6 +3,7 @@
+ #include "Python.h"
+ #include "pycore_code.h" // stats
+ #include "pycore_pystate.h" // _PyInterpreterState_GET
++#include "pycore_obmalloc_init.h"
+
+ #include "pycore_obmalloc.h"
+ #include "pycore_pymem.h"
+@@ -852,6 +853,13 @@ static int running_on_valgrind = -1;
+
+ typedef struct _obmalloc_state OMState;
+
++/* obmalloc state for main interpreter and shared by all interpreters without
++ * their own obmalloc state. By not explicitly initalizing this structure, it
++ * will be allocated in the BSS which is a small performance win. The radix
++ * tree arrays are fairly large but are sparsely used. */
++static struct _obmalloc_state obmalloc_state_main;
++static bool obmalloc_state_initialized;
++
+ static inline int
+ has_own_state(PyInterpreterState *interp)
+ {
+@@ -864,10 +872,8 @@ static inline OMState *
+ get_state(void)
+ {
+ PyInterpreterState *interp = _PyInterpreterState_GET();
+- if (!has_own_state(interp)) {
+- interp = _PyInterpreterState_Main();
+- }
+- return &interp->obmalloc;
++ assert(interp->obmalloc != NULL); // otherwise not initialized or freed
++ return interp->obmalloc;
+ }
+
+ // These macros all rely on a local "state" variable.
+@@ -893,7 +899,11 @@ _PyInterpreterState_GetAllocatedBlocks(PyInterpreterState *interp)
+ "the interpreter doesn't have its own allocator");
+ }
+ #endif
+- OMState *state = &interp->obmalloc;
++ OMState *state = interp->obmalloc;
++
++ if (state == NULL) {
++ return 0;
++ }
+
+ Py_ssize_t n = raw_allocated_blocks;
+ /* add up allocated blocks for used pools */
+@@ -915,13 +925,25 @@ _PyInterpreterState_GetAllocatedBlocks(PyInterpreterState *interp)
+ return n;
+ }
+
++static void free_obmalloc_arenas(PyInterpreterState *interp);
++
+ void
+ _PyInterpreterState_FinalizeAllocatedBlocks(PyInterpreterState *interp)
+ {
+- if (has_own_state(interp)) {
++ if (has_own_state(interp) && interp->obmalloc != NULL) {
+ Py_ssize_t leaked = _PyInterpreterState_GetAllocatedBlocks(interp);
+ assert(has_own_state(interp) || leaked == 0);
+ interp->runtime->obmalloc.interpreter_leaks += leaked;
++ if (_PyMem_obmalloc_state_on_heap(interp) && leaked == 0) {
++ // free the obmalloc arenas and radix tree nodes. If leaked > 0
++ // then some of the memory allocated by obmalloc has not been
++ // freed. It might be safe to free the arenas in that case but
++ // it's possible that extension modules are still using that
++ // memory. So, it is safer to not free and to leak. Perhaps there
++ // should be warning when this happens. It should be possible to
++ // use a tool like "-fsanitize=address" to track down these leaks.
++ free_obmalloc_arenas(interp);
++ }
+ }
+ }
+
+@@ -2511,9 +2533,96 @@ _PyDebugAllocatorStats(FILE *out,
+ (void)printone(out, buf2, num_blocks * sizeof_block);
+ }
+
++// Return true if the obmalloc state structure is heap allocated,
++// by PyMem_RawCalloc(). For the main interpreter, this structure
++// allocated in the BSS. Allocating that way gives some memory savings
++// and a small performance win (at least on a demand paged OS). On
++// 64-bit platforms, the obmalloc structure is 256 kB. Most of that
++// memory is for the arena_map_top array. Since normally only one entry
++// of that array is used, only one page of resident memory is actually
++// used, rather than the full 256 kB.
++bool _PyMem_obmalloc_state_on_heap(PyInterpreterState *interp)
++{
++#if WITH_PYMALLOC
++ return interp->obmalloc && interp->obmalloc != &obmalloc_state_main;
++#else
++ return false;
++#endif
++}
++
++#ifdef WITH_PYMALLOC
++static void
++init_obmalloc_pools(PyInterpreterState *interp)
++{
++ // initialize the obmalloc->pools structure. This must be done
++ // before the obmalloc alloc/free functions can be called.
++ poolp temp[OBMALLOC_USED_POOLS_SIZE] =
++ _obmalloc_pools_INIT(interp->obmalloc->pools);
++ memcpy(&interp->obmalloc->pools.used, temp, sizeof(temp));
++}
++#endif /* WITH_PYMALLOC */
++
++int _PyMem_init_obmalloc(PyInterpreterState *interp)
++{
++#ifdef WITH_PYMALLOC
++ /* Initialize obmalloc, but only for subinterpreters,
++ since the main interpreter is initialized statically. */
++ if (_Py_IsMainInterpreter(interp)
++ || _PyInterpreterState_HasFeature(interp,
++ Py_RTFLAGS_USE_MAIN_OBMALLOC)) {
++ interp->obmalloc = &obmalloc_state_main;
++ if (!obmalloc_state_initialized) {
++ init_obmalloc_pools(interp);
++ obmalloc_state_initialized = true;
++ }
++ } else {
++ interp->obmalloc = PyMem_RawCalloc(1, sizeof(struct _obmalloc_state));
++ if (interp->obmalloc == NULL) {
++ return -1;
++ }
++ init_obmalloc_pools(interp);
++ }
++#endif /* WITH_PYMALLOC */
++ return 0; // success
++}
++
+
+ #ifdef WITH_PYMALLOC
+
++static void
++free_obmalloc_arenas(PyInterpreterState *interp)
++{
++ OMState *state = interp->obmalloc;
++ for (uint i = 0; i < maxarenas; ++i) {
++ // free each obmalloc memory arena
++ struct arena_object *ao = &allarenas[i];
++ _PyObject_Arena.free(_PyObject_Arena.ctx,
++ (void *)ao->address, ARENA_SIZE);
++ }
++ // free the array containing pointers to all arenas
++ PyMem_RawFree(allarenas);
++#if WITH_PYMALLOC_RADIX_TREE
++#ifdef USE_INTERIOR_NODES
++ // Free the middle and bottom nodes of the radix tree. These are allocated
++ // by arena_map_mark_used() but not freed when arenas are freed.
++ for (int i1 = 0; i1 < MAP_TOP_LENGTH; i1++) {
++ arena_map_mid_t *mid = arena_map_root.ptrs[i1];
++ if (mid == NULL) {
++ continue;
++ }
++ for (int i2 = 0; i2 < MAP_MID_LENGTH; i2++) {
++ arena_map_bot_t *bot = arena_map_root.ptrs[i1]->ptrs[i2];
++ if (bot == NULL) {
++ continue;
++ }
++ PyMem_RawFree(bot);
++ }
++ PyMem_RawFree(mid);
++ }
++#endif
++#endif
++}
++
+ #ifdef Py_DEBUG
+ /* Is target in the list? The list is traversed via the nextpool pointers.
+ * The list may be NULL-terminated, or circular. Return 1 if target is in
+diff --git a/Python/pylifecycle.c b/Python/pylifecycle.c
+index a0130fde15d574..fb833ba61cbd9b 100644
+--- a/Python/pylifecycle.c
++++ b/Python/pylifecycle.c
+@@ -28,6 +28,7 @@
+ #include "pycore_typeobject.h" // _PyTypes_InitTypes()
+ #include "pycore_typevarobject.h" // _Py_clear_generic_types()
+ #include "pycore_unicodeobject.h" // _PyUnicode_InitTypes()
++#include "pycore_obmalloc.h" // _PyMem_init_obmalloc()
+ #include "opcode.h"
+
+ #include <locale.h> // setlocale()
+@@ -636,6 +637,13 @@ pycore_create_interpreter(_PyRuntimeState *runtime,
+ return status;
+ }
+
++ // initialize the interp->obmalloc state. This must be done after
++ // the settings are loaded (so that feature_flags are set) but before
++ // any calls are made to obmalloc functions.
++ if (_PyMem_init_obmalloc(interp) < 0) {
++ return _PyStatus_NO_MEMORY();
++ }
++
+ /* Auto-thread-state API */
+ status = _PyGILState_Init(interp);
+ if (_PyStatus_EXCEPTION(status)) {
+@@ -2051,6 +2059,14 @@ new_interpreter(PyThreadState **tstate_p, const PyInterpreterConfig *config)
+ return _PyStatus_OK();
+ }
+
++ // initialize the interp->obmalloc state. This must be done after
++ // the settings are loaded (so that feature_flags are set) but before
++ // any calls are made to obmalloc functions.
++ if (_PyMem_init_obmalloc(interp) < 0) {
++ status = _PyStatus_NO_MEMORY();
++ goto error;
++ }
++
+ PyThreadState *tstate = _PyThreadState_New(interp);
+ if (tstate == NULL) {
+ PyInterpreterState_Delete(interp);
+diff --git a/Python/pystate.c b/Python/pystate.c
+index 1337516aa59cbc..a25c3dcf9d09ea 100644
+--- a/Python/pystate.c
++++ b/Python/pystate.c
+@@ -14,6 +14,7 @@
+ #include "pycore_pystate.h"
+ #include "pycore_runtime_init.h" // _PyRuntimeState_INIT
+ #include "pycore_sysmodule.h"
++#include "pycore_obmalloc.h" // _PyMem_obmalloc_state_on_heap()
+
+ /* --------------------------------------------------------------------------
+ CAUTION
+@@ -636,6 +637,11 @@ free_interpreter(PyInterpreterState *interp)
+ // The main interpreter is statically allocated so
+ // should not be freed.
+ if (interp != &_PyRuntime._main_interpreter) {
++ if (_PyMem_obmalloc_state_on_heap(interp)) {
++ // interpreter has its own obmalloc state, free it
++ PyMem_RawFree(interp->obmalloc);
++ interp->obmalloc = NULL;
++ }
+ PyMem_RawFree(interp);
+ }
+ }
+@@ -679,13 +685,6 @@ init_interpreter(PyInterpreterState *interp,
+ assert(next != NULL || (interp == runtime->interpreters.main));
+ interp->next = next;
+
+- /* Initialize obmalloc, but only for subinterpreters,
+- since the main interpreter is initialized statically. */
+- if (interp != &runtime->_main_interpreter) {
+- poolp temp[OBMALLOC_USED_POOLS_SIZE] = \
+- _obmalloc_pools_INIT(interp->obmalloc.pools);
+- memcpy(&interp->obmalloc.pools.used, temp, sizeof(temp));
+- }
+ _PyObject_InitState(interp);
+
+ _PyEval_InitState(interp, pending_lock);
+diff --git a/Tools/c-analyzer/cpython/ignored.tsv b/Tools/c-analyzer/cpython/ignored.tsv
+index 9f36c47ca7ea03..7bcca27ecc32f6 100644
+--- a/Tools/c-analyzer/cpython/ignored.tsv
++++ b/Tools/c-analyzer/cpython/ignored.tsv
+@@ -318,7 +318,8 @@ Objects/obmalloc.c - _PyMem_Debug -
+ Objects/obmalloc.c - _PyMem_Raw -
+ Objects/obmalloc.c - _PyObject -
+ Objects/obmalloc.c - last_final_leaks -
+-Objects/obmalloc.c - usedpools -
++Objects/obmalloc.c - obmalloc_state_main -
++Objects/obmalloc.c - obmalloc_state_initialized -
+ Objects/typeobject.c - name_op -
+ Objects/typeobject.c - slotdefs -
+ Objects/unicodeobject.c - stripfuncnames -
diff --git a/tools/depends/target/python3/01-py312-cpython118618-2.patch b/tools/depends/target/python3/01-py312-cpython118618-2.patch
new file mode 100644
index 0000000000..bbd7a5e0d1
--- /dev/null
+++ b/tools/depends/target/python3/01-py312-cpython118618-2.patch
@@ -0,0 +1,71 @@
+From a867732a619e1cc02369cf0185b53a484d049369 Mon Sep 17 00:00:00 2001
+From: Neil Schemenauer <nas@arctrix.com>
+Date: Mon, 6 May 2024 10:02:17 -0700
+Subject: [PATCH] Fix merge, move _PyMem_init_obmalloc() calls.
+
+---
+ Python/pylifecycle.c | 30 +++++++++++++++---------------
+ 1 file changed, 15 insertions(+), 15 deletions(-)
+
+diff --git a/Python/pylifecycle.c b/Python/pylifecycle.c
+index fb833ba61cbd9b..31a24d4a65aebf 100644
+--- a/Python/pylifecycle.c
++++ b/Python/pylifecycle.c
+@@ -637,13 +637,6 @@ pycore_create_interpreter(_PyRuntimeState *runtime,
+ return status;
+ }
+
+- // initialize the interp->obmalloc state. This must be done after
+- // the settings are loaded (so that feature_flags are set) but before
+- // any calls are made to obmalloc functions.
+- if (_PyMem_init_obmalloc(interp) < 0) {
+- return _PyStatus_NO_MEMORY();
+- }
+-
+ /* Auto-thread-state API */
+ status = _PyGILState_Init(interp);
+ if (_PyStatus_EXCEPTION(status)) {
+@@ -658,6 +651,13 @@ pycore_create_interpreter(_PyRuntimeState *runtime,
+ return status;
+ }
+
++ // initialize the interp->obmalloc state. This must be done after
++ // the settings are loaded (so that feature_flags are set) but before
++ // any calls are made to obmalloc functions.
++ if (_PyMem_init_obmalloc(interp) < 0) {
++ return _PyStatus_NO_MEMORY();
++ }
++
+ PyThreadState *tstate = _PyThreadState_New(interp);
+ if (tstate == NULL) {
+ return _PyStatus_ERR("can't make first thread");
+@@ -2059,14 +2059,6 @@ new_interpreter(PyThreadState **tstate_p, const PyInterpreterConfig *config)
+ return _PyStatus_OK();
+ }
+
+- // initialize the interp->obmalloc state. This must be done after
+- // the settings are loaded (so that feature_flags are set) but before
+- // any calls are made to obmalloc functions.
+- if (_PyMem_init_obmalloc(interp) < 0) {
+- status = _PyStatus_NO_MEMORY();
+- goto error;
+- }
+-
+ PyThreadState *tstate = _PyThreadState_New(interp);
+ if (tstate == NULL) {
+ PyInterpreterState_Delete(interp);
+@@ -2110,6 +2102,14 @@ new_interpreter(PyThreadState **tstate_p, const PyInterpreterConfig *config)
+ goto error;
+ }
+
++ // initialize the interp->obmalloc state. This must be done after
++ // the settings are loaded (so that feature_flags are set) but before
++ // any calls are made to obmalloc functions.
++ if (_PyMem_init_obmalloc(interp) < 0) {
++ status = _PyStatus_NO_MEMORY();
++ goto error;
++ }
++
+ status = init_interp_create_gil(tstate, config->gil);
+ if (_PyStatus_EXCEPTION(status)) {
+ goto error;
diff --git a/tools/depends/target/python3/02-android-cpython114875.patch b/tools/depends/target/python3/02-android-cpython114875.patch
new file mode 100644
index 0000000000..edf2335a8f
--- /dev/null
+++ b/tools/depends/target/python3/02-android-cpython114875.patch
@@ -0,0 +1,36 @@
+--- a/configure.ac
++++ b/configure.ac
+@@ -4906,7 +4906,7 @@
+ copy_file_range ctermid dup dup3 execv explicit_bzero explicit_memset \
+ faccessat fchmod fchmodat fchown fchownat fdopendir fdwalk fexecve \
+ fork fork1 fpathconf fstatat ftime ftruncate futimens futimes futimesat \
+- gai_strerror getegid getentropy geteuid getgid getgrgid getgrgid_r \
++ gai_strerror getegid getentropy geteuid getgid getgrent getgrgid getgrgid_r \
+ getgrnam_r getgrouplist getgroups gethostname getitimer getloadavg getlogin \
+ getpeername getpgid getpid getppid getpriority _getpty \
+ getpwent getpwnam_r getpwuid getpwuid_r getresgid getresuid getrusage getsid getspent \
+@@ -7445,7 +7445,9 @@
+ -a "$ac_cv_header_netinet_in_h" = "yes"]))
+
+ dnl platform specific extensions
+-PY_STDLIB_MOD([grp], [], [test "$ac_cv_func_getgrgid" = yes -o "$ac_cv_func_getgrgid_r" = yes])
++PY_STDLIB_MOD([grp], [],
++ [test "$ac_cv_func_getgrent" = "yes" &&
++ { test "$ac_cv_func_getgrgid" = "yes" || test "$ac_cv_func_getgrgid_r" = "yes"; }])
+ PY_STDLIB_MOD([ossaudiodev],
+ [], [test "$ac_cv_header_linux_soundcard_h" = yes -o "$ac_cv_header_sys_soundcard_h" = yes],
+ [], [$OSSAUDIODEV_LIBS])
+diff --git a/pyconfig.h.in b/pyconfig.h.in
+index d8a9f68951afbd..36a46b1d14909f 100644
+--- a/pyconfig.h.in
++++ b/pyconfig.h.in
+@@ -477,6 +477,9 @@
+ /* Define to 1 if you have the `getgid' function. */
+ #undef HAVE_GETGID
+
++/* Define to 1 if you have the `getgrent' function. */
++#undef HAVE_GETGRENT
++
+ /* Define to 1 if you have the `getgrgid' function. */
+ #undef HAVE_GETGRGID
+
diff --git a/tools/depends/target/python3/10-linux-modules.patch b/tools/depends/target/python3/10-linux-modules.patch
deleted file mode 100644
index 97753d9aa7..0000000000
--- a/tools/depends/target/python3/10-linux-modules.patch
+++ /dev/null
@@ -1,11 +0,0 @@
---- a/Modules/Setup
-+++ b/Modules/Setup
-@@ -182,7 +182,7 @@
- # Modules with some UNIX dependencies
-
- _posixsubprocess _posixsubprocess.c
--_posixshmem -I$(srcdir)/Modules/_multiprocessing _multiprocessing/posixshmem.c # -lrt # _posixshmem
-+_posixshmem -I$(srcdir)/Modules/_multiprocessing _multiprocessing/posixshmem.c -lrt # _posixshmem
- fcntl fcntlmodule.c
- #grp grpmodule.c
- #ossaudiodev ossaudiodev.c
diff --git a/tools/depends/target/python3/10-osx-modules.patch b/tools/depends/target/python3/10-osx-modules.patch
deleted file mode 100644
index 8050f5ae5d..0000000000
--- a/tools/depends/target/python3/10-osx-modules.patch
+++ /dev/null
@@ -1,11 +0,0 @@
---- a/Modules/Setup
-+++ b/Modules/Setup
-@@ -270,7 +270,7 @@
-
- # macOS specific module, needs SystemConfiguration and CoreFoundation framework
- # _scproxy _scproxy.c
--$(OSX_SCPROXY)
-+_scproxy _scproxy.c -framework SystemConfiguration -framework CoreFoundation
-
- # Examples
-
diff --git a/tools/depends/target/python3/Makefile b/tools/depends/target/python3/Makefile
index 53942259aa..c8682f6029 100644
--- a/tools/depends/target/python3/Makefile
+++ b/tools/depends/target/python3/Makefile
@@ -1,16 +1,14 @@
include ../../Makefile.include PYTHON3-VERSION ../../download-files.include
DEPS = ../../Makefile.include Makefile PYTHON3-VERSION ../../download-files.include \
+ 01-py312-cpython118618-1.patch \
+ 01-py312-cpython118618-2.patch \
+ 02-android-cpython114875.patch \
apple.patch \
- crosscompile.patch \
darwin_embedded.patch \
- 10-android-modules.patch \
- 10-linux-modules.patch \
- 10-osx-modules.patch \
- modules.setup
+ 10-android-modules.patch
ifeq ($(findstring apple-darwin, $(HOST)), apple-darwin)
HOSTPLATFORM=_PYTHON_HOST_PLATFORM="darwin"
- LINK_ICONV=-liconv
ifeq ($(OS), darwin_embedded)
EXTRA_CONFIGURE=ac_cv_func_wait3=no ac_cv_func_wait4=no ac_cv_func_waitpid=no \
ac_cv_func_execv=no ac_cv_func_fexecv=no ac_cv_func_getentropy=no \
@@ -19,23 +17,64 @@ ifeq ($(findstring apple-darwin, $(HOST)), apple-darwin)
ac_cv_func_forkpty=no ac_cv_lib_util_forkpty=no \
ac_cv_func_getgroups=no \
ac_cv_func_system=no
+ export SDKROOT
+ endif
+ ifeq ($(OS), osx)
+ export SDKROOT
endif
# required for _localemodule
EXTRA_CONFIGURE+= ac_cv_lib_intl_textdomain=yes
- # uses SDK ffi
- EXTRA_CONFIGURE+= --with-system-ffi
endif
ifeq ($(OS),android)
- LDFLAGS+= -liconv
+ LIBS=-liconv
endif
ifeq ($(OS), linux)
EXTRA_CONFIGURE=ac_cv_pthread=yes
ifeq ($(TARGET_PLATFORM),webos)
- LDFLAGS+= -liconv
+ # Force intl check to succeed
+ EXTRA_CONFIGURE+=ac_cv_lib_intl_textdomain=yes
+ # Export iconv as LIBS for link ordering (After -lintl from configure search)
+ LIBS=-liconv
endif
+endif
+
+# Disabled c extension modules for all platforms
+PY_MODULES = py_cv_module_audioop=n/a \
+ py_cv_module_grp=n/a \
+ py_cv_module_ossaudiodev=n/a \
+ py_cv_module_spwd=n/a \
+ py_cv_module_syslog=n/a \
+ py_cv_module__crypt=n/a \
+ py_cv_module_nis=n/a \
+ py_cv_module__dbm=n/a \
+ py_cv_module__gdbm=n/a \
+ py_cv_module__uuid=n/a \
+ py_cv_module_readline=n/a \
+ py_cv_module__curses=n/a \
+ py_cv_module__curses_panel=n/a \
+ py_cv_module__scproxy=n/a \
+ py_cv_module_xx=n/a \
+ py_cv_module_xxlimited=n/a \
+ py_cv_module_xxlimited_35=n/a \
+ py_cv_module_xxsubtype=n/a \
+ py_cv_module__xxsubinterpreters=n/a \
+ py_cv_module__tkinter=n/a \
+ py_cv_module__curses=n/a \
+ py_cv_module__codecs_jp=n/a \
+ py_cv_module__codecs_kr=n/a \
+ py_cv_module__codecs_tw=n/a
+
+# These modules use "internal" libs for building. The required static archives
+# are not installed outside of the cpython build tree, and cause failure in kodi linking
+# If we wish to support them in the future, we should create "system libs" for them
+PY_MODULES+= py_cv_module__decimal=n/a \
+ py_cv_module__sha2=n/a
+
+ifeq ($(OS), darwin_embedded)
+ PY_MODULES+= py_cv_module__posixsubprocess=n/a
endif
# configuration settings
@@ -49,9 +88,11 @@ CONFIGURE=./configure --prefix=$(PREFIX) \
--with-system-expat=yes \
--disable-test-modules \
MODULE_BUILDTYPE=static \
+ $(PY_MODULES) \
$(EXTRA_CONFIGURE)
export LDFLAGS
+export LIBS
LIBDYLIB=$(PLATFORM)/libpython$(PYTHON_VERSION).a
@@ -60,32 +101,27 @@ all: .installed-$(PLATFORM)
$(PLATFORM): $(DEPS) | $(TARBALLS_LOCATION)/$(ARCHIVE).$(HASH_TYPE)
rm -rf $(PLATFORM)/*; mkdir -p $(PLATFORM)
cd $(PLATFORM); $(ARCHIVE_TOOL) $(ARCHIVE_TOOL_FLAGS) $(TARBALLS_LOCATION)/$(ARCHIVE)
- cd $(PLATFORM); patch -p1 -i ../crosscompile.patch
+ cd $(PLATFORM); patch -p1 -i ../01-py312-cpython118618-1.patch
+ cd $(PLATFORM); patch -p1 -i ../01-py312-cpython118618-2.patch
cd $(PLATFORM); patch -p1 -i ../apple.patch
ifeq ($(OS),darwin_embedded)
cd $(PLATFORM); patch -p1 -i ../darwin_embedded.patch
endif
- cp modules.setup $(PLATFORM)/Modules/Setup
ifeq ($(OS),android)
+ cd $(PLATFORM); patch -p1 -i ../02-android-cpython114875.patch
cd $(PLATFORM); patch -p1 -i ../10-android-modules.patch
endif
-ifeq ($(OS),linux)
- cd $(PLATFORM); patch -p1 -i ../10-linux-modules.patch
-endif
-ifeq ($(OS),osx)
- cd $(PLATFORM); patch -p1 -i ../10-osx-modules.patch
-endif
-
cd $(PLATFORM); $(AUTORECONF)
cd $(PLATFORM); $(CONFIGURE)
$(LIBDYLIB): $(PLATFORM)
- $(MAKE) -C $(PLATFORM) $(HOSTPLATFORM) CROSS_COMPILE_TARGET=yes libpython$(PYTHON_VERSION).a
+ $(MAKE) -C $(PLATFORM) $(HOSTPLATFORM) libpython$(PYTHON_VERSION).a
touch $@
.installed-$(PLATFORM): $(LIBDYLIB)
- $(MAKE) -C $(PLATFORM) $(HOSTPLATFORM) CROSS_COMPILE_TARGET=yes install
+# We specifically use -j1 as some threading issues can occur with install directory creation
+ $(MAKE) -C $(PLATFORM) $(HOSTPLATFORM) install -j1
find $(PREFIX)/lib/python$(PYTHON_VERSION) -type f -name "*.pyc" -delete
touch $(LIBDYLIB)
touch $@
diff --git a/tools/depends/target/python3/PYTHON3-VERSION b/tools/depends/target/python3/PYTHON3-VERSION
index c5e1760fb2..9c97bd4018 100644
--- a/tools/depends/target/python3/PYTHON3-VERSION
+++ b/tools/depends/target/python3/PYTHON3-VERSION
@@ -1,4 +1,4 @@
LIBNAME=Python
-VERSION=3.11.7
+VERSION=3.12.5
ARCHIVE=$(LIBNAME)-$(VERSION).tar.xz
-SHA512=11e06f2ffe1f66888cb5b4e9f607de815294d6863a77eda6ec6d7c724ef158df9f51881f4a956d4a6fa973c2fb6fd031d495e3496e9b0bb53793fb1cc8434c63
+SHA512=7a1c30d798434fe24697bc253f6010d75145e7650f66803328425c8525331b9fa6b63d12a652687582db205f8d4c8279c8f73c338168592481517b063351c921
diff --git a/tools/depends/target/python3/apple.patch b/tools/depends/target/python3/apple.patch
index 4deda311fb..1cad7696d2 100644
--- a/tools/depends/target/python3/apple.patch
+++ b/tools/depends/target/python3/apple.patch
@@ -28,12 +28,3 @@
# On QNX 6.3.2, defining _XOPEN_SOURCE prevents netdb.h from
# defining NI_NUMERICHOST.
QNX/6.3.2)
-@@ -2947,7 +2947,7 @@
- return 1;
- }
- }
-- ]])],[ac_osx_32bit=yes],[ac_osx_32bit=no],[ac_osx_32bit=yes])
-+ ]])],[ac_osx_32bit=yes],[ac_osx_32bit=no],[ac_osx_32bit=no])
-
- if test "${ac_osx_32bit}" = "yes"; then
- case `/usr/bin/arch` in
diff --git a/tools/depends/target/python3/crosscompile.patch b/tools/depends/target/python3/crosscompile.patch
deleted file mode 100644
index 6757c75ace..0000000000
--- a/tools/depends/target/python3/crosscompile.patch
+++ /dev/null
@@ -1,64 +0,0 @@
---- a/configure.ac
-+++ b/configure.ac
-@@ -1625,15 +1625,6 @@
- ARFLAGS="rcs"
- fi
-
--AC_CHECK_TOOLS([READELF], [readelf], [:])
--if test "$cross_compiling" = yes; then
-- case "$READELF" in
-- readelf|:)
-- AC_MSG_ERROR([readelf for the host is required for cross builds])
-- ;;
-- esac
--fi
--AC_SUBST(READELF)
-
-
- case $MACHDEP in
---- a/Makefile.pre.in
-+++ b/Makefile.pre.in
-@@ -2233,10 +2233,11 @@
- # This goes into $(exec_prefix)
- sharedinstall: all
- $(RUNSHARED) $(PYTHON_FOR_BUILD) $(srcdir)/setup.py install \
-+ --skip-build \
- --prefix=$(prefix) \
-- --install-scripts=$(BINDIR) \
-- --install-platlib=$(DESTSHARED) \
-- --root=$(DESTDIR)/
-+ --install-scripts=$(DESTDIR)$(BINDIR) \
-+ --install-platlib=$(DESTDIR)$(DESTSHARED) \
-+ --root=/
- -rm $(DESTDIR)$(DESTSHARED)/_sysconfigdata_$(ABIFLAGS)_$(MACHDEP)_$(MULTIARCH).py
- -rm -r $(DESTDIR)$(DESTSHARED)/__pycache__
-
---- a/setup.py
-+++ b/setup.py
-@@ -77,7 +77,7 @@
- return sys.platform
-
-
--CROSS_COMPILING = ("_PYTHON_HOST_PLATFORM" in os.environ)
-+CROSS_COMPILING = ("_PYTHON_HOST_PLATFORM" in os.environ) or ('CROSS_COMPILE_TARGET' in os.environ)
- HOST_PLATFORM = get_platform()
- MS_WINDOWS = (HOST_PLATFORM == 'win32')
- CYGWIN = (HOST_PLATFORM == 'cygwin')
-@@ -488,6 +488,7 @@
- self.compiler.set_executables(**args)
-
- def build_extensions(self):
-+ return
- self.set_srcdir()
- self.set_compiler_executables()
- self.configure_compiler()
-@@ -1343,7 +1343,7 @@
- # These are extensions are required to bootstrap the interpreter or
- # build process.
- self.detect_simple_extensions()
-- self.detect_test_extensions()
-+ #self.detect_test_extensions()
- self.detect_readline_curses()
- self.detect_crypt()
- self.detect_openssl_hashlib()
-
diff --git a/tools/depends/target/python3/darwin_embedded.patch b/tools/depends/target/python3/darwin_embedded.patch
index 77c9d0f30a..bd27f7ef1f 100644
--- a/tools/depends/target/python3/darwin_embedded.patch
+++ b/tools/depends/target/python3/darwin_embedded.patch
@@ -1,14 +1,3 @@
---- a/configure.ac
-+++ b/configure.ac
-@@ -6827,7 +6827,7 @@
- AS_CASE([$ac_sys_system],
- [AIX], [PY_STDLIB_MOD_SET_NA([_scproxy], [spwd])],
- [VxWorks*], [PY_STDLIB_MOD_SET_NA([_scproxy], [_crypt], [termios], [grp])],
-- [Darwin], [PY_STDLIB_MOD_SET_NA([ossaudiodev], [spwd])],
-+ [Darwin], [PY_STDLIB_MOD_SET_NA([ossaudiodev], [spwd], [_posixsubprocess], [_scproxy], [_tkinter], [_xxsubinterpreters])],
- [CYGWIN*], [PY_STDLIB_MOD_SET_NA([_scproxy], [nis])],
- [QNX*], [PY_STDLIB_MOD_SET_NA([_scproxy], [nis])],
- [FreeBSD*], [PY_STDLIB_MOD_SET_NA([_scproxy], [spwd])],
--- a/Lib/os.py
+++ b/Lib/os.py
@@ -605,6 +605,7 @@
diff --git a/tools/depends/target/python3/modules.setup b/tools/depends/target/python3/modules.setup
deleted file mode 100644
index c974a066c9..0000000000
--- a/tools/depends/target/python3/modules.setup
+++ /dev/null
@@ -1,304 +0,0 @@
-# -*- makefile -*-
-# The file Setup is used by the makesetup script to construct the files
-# Makefile and config.c, from Makefile.pre and config.c.in,
-# respectively. Note that Makefile.pre is created from Makefile.pre.in
-# by the toplevel configure script.
-
-# (VPATH notes: Setup and Makefile.pre are in the build directory, as
-# are Makefile and config.c; the *.in files are in the source directory.)
-
-# Each line in this file describes one or more optional modules.
-# Modules configured here will not be compiled by the setup.py script,
-# so the file can be used to override setup.py's behavior.
-# Tag lines containing just the word "*static*", "*shared*" or "*disabled*"
-# (without the quotes but with the stars) are used to tag the following module
-# descriptions. Tag lines may alternate throughout this file. Modules are
-# built statically when they are preceded by a "*static*" tag line or when
-# there is no tag line between the start of the file and the module
-# description. Modules are built as a shared library when they are preceded by
-# a "*shared*" tag line. Modules are not built at all, not by the Makefile,
-# nor by the setup.py script, when they are preceded by a "*disabled*" tag
-# line.
-
-# Lines have the following structure:
-#
-# <module> ... [<sourcefile> ...] [<cpparg> ...] [<library> ...]
-#
-# <sourcefile> is anything ending in .c (.C, .cc, .c++ are C++ files)
-# <cpparg> is anything starting with -I, -D, -U or -C
-# <library> is anything ending in .a or beginning with -l or -L
-# <module> is anything else but should be a valid Python
-# identifier (letters, digits, underscores, beginning with non-digit)
-#
-# (As the makesetup script changes, it may recognize some other
-# arguments as well, e.g. *.so and *.sl as libraries. See the big
-# case statement in the makesetup script.)
-#
-# Lines can also have the form
-#
-# <name> = <value>
-#
-# which defines a Make variable definition inserted into Makefile.in.
-# You can also use any Make variable that is detected by configure and
-# defined in Makefile.pre.in, e.g. OpenSSL flags $(OPENSSL_INCLUDES).
-#
-# Rules generated by makesetup use additional variables:
-#
-# - All source file rules have a dependency on $(PYTHON_HEADERS) and on
-# optional variable $(MODULES_{mod_upper}_DEPS).
-# - If no <cpparg> and no <library> arguments are given, then makesetup
-# defaults to $(MODULES_{mod_upper}_CFLAGS) cppargs and
-# $(MODULES_{mod_upper}_LDFLAGS) libraries. The variables are typically
-# defined by configure.
-#
-# The build process works like this:
-#
-# 1. Build all modules that are declared as static in Modules/Setup,
-# combine them into libpythonxy.a, combine that into python.
-# 2. Build all modules that are listed as shared in Modules/Setup.
-# 3. Invoke setup.py. That builds all modules that
-# a) are not builtin, and
-# b) are not listed in Modules/Setup, and
-# c) can be build on the target
-#
-# Therefore, modules declared to be shared will not be
-# included in the config.c file, nor in the list of objects to be
-# added to the library archive, and their linker options won't be
-# added to the linker options. Rules to create their .o files and
-# their shared libraries will still be added to the Makefile, and
-# their names will be collected in the Make variable SHAREDMODS. This
-# is used to build modules as shared libraries. (They can be
-# installed using "make sharedinstall", which is implied by the
-# toplevel "make install" target.) (For compatibility,
-# *noconfig* has the same effect as *shared*.)
-#
-# NOTE: As a standard policy, as many modules as can be supported by a
-# platform should be listed below. The distribution comes with all
-# modules enabled that are supported by most platforms and don't
-# require you to download sources from elsewhere.
-#
-# NOTE: Avoid editing this file directly. Local changes should go into
-# Modules/Setup.local file. To enable all modules for testing, run
-#
-# sed -n -E 's/^#([a-z_\*].*)$/\1/p' Modules/Setup > Modules/Setup.local
-
-
-# Some special rules to define PYTHONPATH.
-# Edit the definitions below to indicate which options you are using.
-# Don't add any whitespace or comments!
-
-# Directories where library files get installed.
-# DESTLIB is for Python modules; MACHDESTLIB for shared libraries.
-DESTLIB=$(LIBDEST)
-MACHDESTLIB=$(BINLIBDEST)
-
-# NOTE: all the paths are now relative to the prefix that is computed
-# at run time!
-
-# Standard path -- don't edit.
-# No leading colon since this is the first entry.
-# Empty since this is now just the runtime prefix.
-DESTPATH=
-
-# Site specific path components -- should begin with : if non-empty
-SITEPATH=:site-packages
-
-# Standard path components for test modules
-TESTPATH=
-
-COREPYTHONPATH=$(DESTPATH)$(SITEPATH)$(TESTPATH)
-PYTHONPATH=$(COREPYTHONPATH)
-
-
-# ---
-# Built-in modules required to get a functioning interpreter are listed in
-# Modules/Setup.bootstrap.
-
-# ---
-# The rest of the modules listed in this file are all commented out by
-# default. Usually they can be detected and built as dynamically
-# loaded modules by setup.py. If you're on a platform that doesn't
-# support dynamic loading, want to compile modules statically into the
-# Python binary, or need to specify some odd set of compiler switches,
-# you can uncomment the appropriate lines below.
-
-# Uncommenting the following line tells makesetup that all following
-# modules are to be built as shared libraries (see above for more
-# detail; also note that *static* or *disabled* cancels this effect):
-
-#*shared*
-
-# Modules that should always be present (POSIX and Windows):
-
-_asyncio _asynciomodule.c
-_bisect _bisectmodule.c
-_contextvars _contextvarsmodule.c
-_csv _csv.c
-_datetime _datetimemodule.c
-# _decimal _decimal/_decimal.c
-_heapq _heapqmodule.c
-_json _json.c
-_lsprof _lsprof.c rotatingtree.c
-_multiprocessing -I$(srcdir)/Modules/_multiprocessing _multiprocessing/multiprocessing.c _multiprocessing/semaphore.c
-_opcode _opcode.c
-_pickle _pickle.c
-_queue _queuemodule.c
-_random _randommodule.c
-_socket socketmodule.c
-_statistics _statisticsmodule.c
-_struct _struct.c
-_typing _typingmodule.c
-_zoneinfo _zoneinfo.c
-array arraymodule.c
-#audioop audioop.c
-binascii binascii.c
-cmath cmathmodule.c
-math mathmodule.c
-mmap mmapmodule.c
-select selectmodule.c
-
-# XML
-_elementtree _elementtree.c
-pyexpat pyexpat.c
-
-# hashing builtins
-_blake2 _blake2/blake2module.c _blake2/blake2b_impl.c _blake2/blake2s_impl.c
-_md5 md5module.c
-_sha1 sha1module.c
-_sha256 sha256module.c
-_sha512 sha512module.c
-_sha3 _sha3/sha3module.c
-
-# text encodings and unicode
-_codecs_cn cjkcodecs/_codecs_cn.c
-_codecs_hk cjkcodecs/_codecs_hk.c
-_codecs_iso2022 cjkcodecs/_codecs_iso2022.c
-_codecs_jp cjkcodecs/_codecs_jp.c
-_codecs_kr cjkcodecs/_codecs_kr.c
-_codecs_tw cjkcodecs/_codecs_tw.c
-_multibytecodec cjkcodecs/multibytecodec.c
-unicodedata unicodedata.c
-
-# Modules with some UNIX dependencies
-
-_posixsubprocess _posixsubprocess.c
-_posixshmem -I$(srcdir)/Modules/_multiprocessing _multiprocessing/posixshmem.c # -lrt # _posixshmem
-fcntl fcntlmodule.c
-#grp grpmodule.c
-#ossaudiodev ossaudiodev.c
-resource resource.c
-#spwd spwdmodule.c
-#syslog syslogmodule.c
-termios termios.c
-
-# Modules with UNIX dependencies that require external libraries
-
-#_crypt _cryptmodule.c -lcrypt
-#nis nismodule.c -I/usr/include/tirpc -lnsl -ltirpc
-
-# Modules that require external libraries.
-
-_bz2 _bz2module.c -lbz2
-_ctypes _ctypes/_ctypes.c _ctypes/callbacks.c _ctypes/callproc.c _ctypes/stgdict.c _ctypes/cfield.c -I$(prefix)/include -L$(prefix)/lib -ldl -lffi -DHAVE_FFI_PREP_CIF_VAR -DHAVE_FFI_PREP_CLOSURE_LOC -DHAVE_FFI_CLOSURE_ALLOC
-# The _dbm module supports NDBM, GDBM with compat module, and Berkeley DB.
-#_dbm _dbmmodule.c -lgdbm_compat -DUSE_GDBM_COMPAT
-#_gdbm _gdbmmodule.c -lgdbm
-_lzma _lzmamodule.c -llzma
-#_uuid _uuidmodule.c -luuid
-zlib zlibmodule.c -lz
-
-_sqlite3 _sqlite/blob.c _sqlite/connection.c _sqlite/cursor.c _sqlite/microprotocols.c _sqlite/module.c _sqlite/prepare_protocol.c _sqlite/row.c _sqlite/statement.c _sqlite/util.c -lsqlite3
-
-# The readline module also supports libeditline (-leditline).
-# Some systems may require -ltermcap or -ltermlib.
-#readline readline.c -lreadline -ltermcap
-
-# OpenSSL bindings
-#_ssl _ssl.c $(OPENSSL_INCLUDES) $(OPENSSL_LDFLAGS) $(OPENSSL_LIBS)
-#_hashlib _hashopenssl.c $(OPENSSL_INCLUDES) $(OPENSSL_LDFLAGS) -lcrypto
-
-# To statically link OpenSSL:
- _ssl _ssl.c -I$(prefix)/include -I$(prefix)/include/openssl \
- -L$(prefix)/lib -lintl $(LDFLAGS) -lssl -lcrypto
- _hashlib _hashopenssl.c -I$(prefix)/include -I$(prefix)/include/openssl \
- -L$(prefix)/lib
-
-# The _tkinter module.
-#
-# The command for _tkinter is long and site specific. Please
-# uncomment and/or edit those parts as indicated. If you don't have a
-# specific extension (e.g. Tix or BLT), leave the corresponding line
-# commented out. (Leave the trailing backslashes in! If you
-# experience strange errors, you may want to join all uncommented
-# lines and remove the backslashes -- the backslash interpretation is
-# done by the shell's "read" command and it may not be implemented on
-# every system.
-
-# *** Always uncomment this (leave the leading underscore in!):
-#_tkinter _tkinter.c tkappinit.c -DWITH_APPINIT $(TCLTK_INCLUDES) $(TCLTK_LIBS) \
-# *** Uncomment and edit to reflect where your Tcl/Tk libraries are:
-# -L/usr/local/lib \
-# *** Uncomment and edit to reflect where your Tcl/Tk headers are:
-# -I/usr/local/include \
-# *** Uncomment and edit to reflect where your X11 header files are:
-# -I/usr/X11R6/include \
-# *** Or uncomment this for Solaris:
-# -I/usr/openwin/include \
-# *** Uncomment and edit for Tix extension only:
-# -DWITH_TIX -ltix8.1.8.2 \
-# *** Uncomment and edit for BLT extension only:
-# -DWITH_BLT -I/usr/local/blt/blt8.0-unoff/include -lBLT8.0 \
-# *** Uncomment and edit for PIL (TkImaging) extension only:
-# (See http://www.pythonware.com/products/pil/ for more info)
-# -DWITH_PIL -I../Extensions/Imaging/libImaging tkImaging.c \
-# *** Uncomment and edit for TOGL extension only:
-# -DWITH_TOGL togl.c \
-# *** Uncomment and edit to reflect where your X11 libraries are:
-# -L/usr/X11R6/lib \
-# *** Or uncomment this for Solaris:
-# -L/usr/openwin/lib \
-# *** Uncomment these for TOGL extension only:
-# -lGL -lGLU -lXext -lXmu \
-# *** Uncomment for AIX:
-# -lld \
-# *** Always uncomment this; X11 libraries to link with:
-# -lX11
-
-# Some system have -lcurses
-#_curses -lncurses -lncursesw -ltermcap _cursesmodule.c
-#_curses_panel -lpanel -lncurses _curses_panel.c
-
-# macOS specific module, needs SystemConfiguration and CoreFoundation framework
-# _scproxy _scproxy.c
-$(OSX_SCPROXY)
-
-# Examples
-
-#xx xxmodule.c
-#xxlimited xxlimited.c
-#xxlimited_35 xxlimited_35.c
-xxsubtype xxsubtype.c # Required for the test suite to pass!
-
-# Testing
-
-#_xxsubinterpreters _xxsubinterpretersmodule.c
-#_xxtestfuzz _xxtestfuzz/_xxtestfuzz.c _xxtestfuzz/fuzzer.c
-#_testbuffer _testbuffer.c
-#_testinternalcapi _testinternalcapi.c
-
-# Some testing modules MUST be built as shared libraries.
-
-#*shared*
-#_ctypes_test _ctypes/_ctypes_test.c
-#_testcapi _testcapimodule.c
-#_testimportmultiple _testimportmultiple.c
-#_testmultiphase _testmultiphase.c
-
-# ---
-# Uncommenting the following line tells makesetup that all following modules
-# are not built (see above for more detail).
-#
-#*disabled*
-#
-# _tkinter _curses
-# _codecs_jp _codecs_kr _codecs_tw
diff --git a/tools/depends/target/pythonmodule-setuptools/PYTHONMODULE-SETUPTOOLS-VERSION b/tools/depends/target/pythonmodule-setuptools/PYTHONMODULE-SETUPTOOLS-VERSION
index 1eca9e1bc0..1bf06499f3 100644
--- a/tools/depends/target/pythonmodule-setuptools/PYTHONMODULE-SETUPTOOLS-VERSION
+++ b/tools/depends/target/pythonmodule-setuptools/PYTHONMODULE-SETUPTOOLS-VERSION
@@ -1,4 +1,4 @@
LIBNAME=setuptools
-VERSION=65.5.0
+VERSION=72.1.0
ARCHIVE=$(LIBNAME)-$(VERSION).tar.gz
-SHA512=b3ed6546bfa45c96f9b69fd7f014a87b52e6d8a6591340bf980bd4de98e33dbe0990b089940c348f2ad20a27590b82de84aec44c8ba1dce0510a3835653930d3
+SHA512=d0a34f16dfa6bb9a6df39076cd43528cf854d343f6f801c448ea0ebab2a259aec3d03571e2a26709df6082ed2fcb6c43b86448be556fd559b6af41831b4f38e0
diff --git a/tools/depends/target/samba-gplv3/08-py312-distutils.patch b/tools/depends/target/samba-gplv3/08-py312-distutils.patch
new file mode 100644
index 0000000000..6ddb5f02a1
--- /dev/null
+++ b/tools/depends/target/samba-gplv3/08-py312-distutils.patch
@@ -0,0 +1,11 @@
+--- a/third_party/waf/waflib/Tools/python.py
++++ b/third_party/waf/waflib/Tools/python.py
+@@ -53,7 +53,7 @@
+ Piece of Python code used in :py:class:`waflib.Tools.python.pyo` and :py:class:`waflib.Tools.python.pyc` for byte-compiling python files
+ """
+
+-DISTUTILS_IMP = ['from distutils.sysconfig import get_config_var, get_python_lib']
++DISTUTILS_IMP = ['import setuptools\nfrom distutils.sysconfig import get_config_var, get_python_lib']
+
+ @before_method('process_source')
+ @feature('py')
diff --git a/tools/depends/target/samba-gplv3/Makefile b/tools/depends/target/samba-gplv3/Makefile
index 20a77dfe42..b089dbd563 100644
--- a/tools/depends/target/samba-gplv3/Makefile
+++ b/tools/depends/target/samba-gplv3/Makefile
@@ -3,6 +3,7 @@ DEPS= ../../Makefile.include Makefile SAMBA-GPLV3-VERSION ../../download-files.i
01-fix-dependencies.patch 02-cross_compile.patch \
03-builtin-heimdal.patch 04-built-static.patch \
05-apple-disable-zlib-pkgconfig.patch 06-apple-fix-st_atim.patch \
+ 08-py312-distutils.patch \
samba_android.patch \
no_fork_and_exec.patch \
crt_extensions.patch \
@@ -89,6 +90,7 @@ endif
ifeq ($(TARGET_PLATFORM),webos)
cd $(PLATFORM); patch -p1 -i ../webos-no-readline.patch
endif
+ cd $(PLATFORM); patch -p1 -i ../08-py312-distutils.patch
cd $(PLATFORM); $(CONFIGURE)
$(LIBDYLIB): $(PLATFORM)
diff --git a/xbmc/ContextMenuItem.h b/xbmc/ContextMenuItem.h
index aee6d82e71..6577de3224 100644
--- a/xbmc/ContextMenuItem.h
+++ b/xbmc/ContextMenuItem.h
@@ -8,6 +8,7 @@
#pragma once
+#include <cstdint>
#include <map>
#include <memory>
#include <string>
diff --git a/xbmc/FileItem.cpp b/xbmc/FileItem.cpp
index 9d59e55889..a3fa357232 100644
--- a/xbmc/FileItem.cpp
+++ b/xbmc/FileItem.cpp
@@ -45,6 +45,7 @@
#include "pvr/guilib/PVRGUIActionsChannels.h"
#include "pvr/guilib/PVRGUIActionsEPG.h"
#include "pvr/guilib/PVRGUIActionsUtils.h"
+#include "pvr/providers/PVRProvider.h"
#include "pvr/recordings/PVRRecording.h"
#include "pvr/timers/PVRTimerInfoTag.h"
#include "settings/AdvancedSettings.h"
@@ -115,7 +116,7 @@ CFileItem::CFileItem(const CMusicInfoTag& music)
m_strPath = music.GetURL();
m_bIsFolder = URIUtils::HasSlashAtEnd(m_strPath);
*GetMusicInfoTag() = music;
- FillInDefaultIcon();
+ ART::FillInDefaultIcon(*this);
FillInMimeType(false);
}
@@ -190,7 +191,11 @@ CFileItem::CFileItem(const std::shared_ptr<PVR::CPVREpgSearchFilter>& filter)
if (lastExec.IsValid())
m_dateTime.SetFromUTCDateTime(lastExec);
- SetArt("icon", "DefaultPVRSearch.png");
+ const std::string iconPath = filter->GetIconPath();
+ if (!iconPath.empty())
+ SetArt("icon", iconPath);
+ else
+ SetArt("icon", "DefaultPVRSearch.png");
// Speedup FillInDefaultIcon()
SetProperty("icon_never_overlay", true);
@@ -295,6 +300,31 @@ CFileItem::CFileItem(const std::shared_ptr<CPVRTimerInfoTag>& timer)
FillInMimeType(false);
}
+CFileItem::CFileItem(const std::string& path, const std::shared_ptr<CPVRProvider>& provider)
+{
+ Initialize();
+
+ m_strPath = path;
+ m_bIsFolder = true;
+ m_pvrProviderInfoTag = provider;
+ SetLabel(provider->GetName());
+ m_bCanQueue = false;
+
+ // Set art
+ if (!provider->GetIconPath().empty())
+ SetArt("icon", provider->GetIconPath());
+ else
+ SetArt("icon", "DefaultPVRProvider.png");
+
+ if (!provider->GetThumbPath().empty())
+ SetArt("thumb", provider->GetThumbPath());
+
+ // Speedup FillInDefaultIcon()
+ SetProperty("icon_never_overlay", true);
+
+ FillInMimeType(false);
+}
+
CFileItem::CFileItem(const CArtist& artist)
{
Initialize();
@@ -500,6 +530,7 @@ CFileItem& CFileItem::operator=(const CFileItem& item)
m_pvrChannelGroupMemberInfoTag = item.m_pvrChannelGroupMemberInfoTag;
m_pvrRecordingInfoTag = item.m_pvrRecordingInfoTag;
m_pvrTimerInfoTag = item.m_pvrTimerInfoTag;
+ m_pvrProviderInfoTag = item.m_pvrProviderInfoTag;
m_addonInfo = item.m_addonInfo;
m_eventLogEntry = item.m_eventLogEntry;
@@ -574,6 +605,7 @@ void CFileItem::Reset()
m_pvrChannelGroupMemberInfoTag.reset();
m_pvrRecordingInfoTag.reset();
m_pvrTimerInfoTag.reset();
+ m_pvrProviderInfoTag.reset();
delete m_pictureInfoTag;
m_pictureInfoTag=NULL;
delete m_gameInfoTag;
@@ -780,6 +812,9 @@ void CFileItem::ToSortable(SortItem &sortable, Field field) const
if (HasPVRChannelGroupMemberInfoTag())
GetPVRChannelGroupMemberInfoTag()->ToSortable(sortable, field);
+ if (HasPVRProviderInfoTag())
+ GetPVRProviderInfoTag()->ToSortable(sortable, field);
+
if (HasAddonInfo())
{
switch (field)
@@ -892,6 +927,11 @@ bool CFileItem::IsPVRTimer() const
return HasPVRTimerInfoTag();
}
+bool CFileItem::IsPVRProvider() const
+{
+ return HasPVRProviderInfoTag();
+}
+
bool CFileItem::IsDeleted() const
{
if (HasPVRRecordingInfoTag())
@@ -941,7 +981,8 @@ bool CFileItem::IsPicture() const
return false;
if (HasPVRTimerInfoTag() || HasPVRChannelInfoTag() || HasPVRChannelGroupMemberInfoTag() ||
- HasPVRRecordingInfoTag() || HasEPGInfoTag() || HasEPGSearchFilter())
+ HasPVRRecordingInfoTag() || HasEPGInfoTag() || HasEPGSearchFilter() ||
+ HasPVRProviderInfoTag())
return false;
if (!m_strPath.empty())
@@ -1179,126 +1220,6 @@ bool CFileItem::IsReadOnly() const
return !CUtil::SupportsWriteFileOperations(m_strPath);
}
-void CFileItem::FillInDefaultIcon()
-{
- if (URIUtils::IsPVRGuideItem(m_strPath))
- {
- // epg items never have a default icon. no need to execute this expensive method.
- // when filling epg grid window, easily tens of thousands of epg items are processed.
- return;
- }
-
- //CLog::Log(LOGINFO, "FillInDefaultIcon({})", pItem->GetLabel());
- // find the default icon for a file or folder item
- // for files this can be the (depending on the file type)
- // default picture for photo's
- // default picture for songs
- // default picture for videos
- // default picture for shortcuts
- // default picture for playlists
- //
- // for folders
- // for .. folders the default picture for parent folder
- // for other folders the defaultFolder.png
-
- if (GetArt("icon").empty())
- {
- if (!m_bIsFolder)
- {
- /* To reduce the average runtime of this code, this list should
- * be ordered with most frequently seen types first. Also bear
- * in mind the complexity of the code behind the check in the
- * case of IsWhatever() returns false.
- */
- if (IsPVRChannel())
- {
- if (GetPVRChannelInfoTag()->IsRadio())
- SetArt("icon", "DefaultMusicSongs.png");
- else
- SetArt("icon", "DefaultTVShows.png");
- }
- else if ( IsLiveTV() )
- {
- // Live TV Channel
- SetArt("icon", "DefaultTVShows.png");
- }
- else if ( URIUtils::IsArchive(m_strPath) )
- { // archive
- SetArt("icon", "DefaultFile.png");
- }
- else if ( IsUsablePVRRecording() )
- {
- // PVR recording
- SetArt("icon", "DefaultVideo.png");
- }
- else if ( IsDeletedPVRRecording() )
- {
- // PVR deleted recording
- SetArt("icon", "DefaultVideoDeleted.png");
- }
- else if (PLAYLIST::IsPlayList(*this) || PLAYLIST::IsSmartPlayList(*this))
- {
- SetArt("icon", "DefaultPlaylist.png");
- }
- else if (MUSIC::IsAudio(*this))
- {
- // audio
- SetArt("icon", "DefaultAudio.png");
- }
- else if (VIDEO::IsVideo(*this))
- {
- // video
- SetArt("icon", "DefaultVideo.png");
- }
- else if (IsPVRTimer())
- {
- SetArt("icon", "DefaultVideo.png");
- }
- else if ( IsPicture() )
- {
- // picture
- SetArt("icon", "DefaultPicture.png");
- }
- else if ( IsPythonScript() )
- {
- SetArt("icon", "DefaultScript.png");
- }
- else if (IsFavourite())
- {
- SetArt("icon", "DefaultFavourites.png");
- }
- else
- {
- // default icon for unknown file type
- SetArt("icon", "DefaultFile.png");
- }
- }
- else
- {
- if (PLAYLIST::IsPlayList(*this) || PLAYLIST::IsSmartPlayList(*this))
- {
- SetArt("icon", "DefaultPlaylist.png");
- }
- else if (IsParentFolder())
- {
- SetArt("icon", "DefaultFolderBack.png");
- }
- else
- {
- SetArt("icon", "DefaultFolder.png");
- }
- }
- }
- // Set the icon overlays (if applicable)
- if (!HasOverlay() && !HasProperty("icon_never_overlay"))
- {
- if (URIUtils::IsInRAR(m_strPath))
- SetOverlayImage(CGUIListItem::ICON_OVERLAY_RAR);
- else if (URIUtils::IsInZIP(m_strPath))
- SetOverlayImage(CGUIListItem::ICON_OVERLAY_ZIP);
- }
-}
-
void CFileItem::RemoveExtension()
{
if (m_bIsFolder)
@@ -1551,6 +1472,11 @@ void CFileItem::UpdateInfo(const CFileItem &item, bool replaceLabels /*=true*/)
m_pvrTimerInfoTag = item.m_pvrTimerInfoTag;
SetInvalid();
}
+ if (item.HasPVRProviderInfoTag())
+ {
+ m_pvrProviderInfoTag = item.m_pvrProviderInfoTag;
+ SetInvalid();
+ }
if (item.HasEPGInfoTag())
{
m_epgInfoTag = item.m_epgInfoTag;
@@ -1619,6 +1545,11 @@ void CFileItem::MergeInfo(const CFileItem& item)
m_pvrTimerInfoTag = item.m_pvrTimerInfoTag;
SetInvalid();
}
+ if (item.HasPVRProviderInfoTag())
+ {
+ m_pvrProviderInfoTag = item.m_pvrProviderInfoTag;
+ SetInvalid();
+ }
if (item.HasEPGInfoTag())
{
m_epgInfoTag = item.m_epgInfoTag;
@@ -1671,7 +1602,7 @@ void CFileItem::SetFromVideoInfoTag(const CVideoInfoTag &video)
if (video.m_iSeason == 0)
SetProperty("isspecial", "true");
- FillInDefaultIcon();
+ ART::FillInDefaultIcon(*this);
FillInMimeType(false);
}
@@ -1739,7 +1670,7 @@ void CFileItem::SetFromMusicInfoTag(const MUSIC_INFO::CMusicInfoTag& music)
SetArt("thumb", thumb.GetValueToSave(GetArt("thumb")));
*GetMusicInfoTag() = music;
- FillInDefaultIcon();
+ ART::FillInDefaultIcon(*this);
FillInMimeType(false);
}
@@ -1937,7 +1868,7 @@ std::string CFileItem::GetUserMusicThumb(bool alwaysCheckRemote /* = false */, b
for (const auto& i : thumbs)
{
std::string strFileName = i.asString();
- std::string folderThumb(GetFolderThumb(strFileName));
+ std::string folderThumb(ART::GetFolderThumb(*this, strFileName));
if (CFile::Exists(folderThumb)) // folder.jpg
return folderThumb;
size_t period = strFileName.find_last_of('.');
@@ -2013,110 +1944,19 @@ std::string CFileItem::FindLocalArt(const std::string &artFile, bool useFolder)
std::string thumb;
if (!m_bIsFolder)
{
- thumb = GetLocalArt(artFile, false);
+ thumb = ART::GetLocalArt(*this, artFile, false);
if (!thumb.empty() && CFile::Exists(thumb))
return thumb;
}
if ((useFolder || (m_bIsFolder && !IsFileFolder())) && !artFile.empty())
{
- std::string thumb2 = GetLocalArt(artFile, true);
+ std::string thumb2 = ART::GetLocalArt(*this, artFile, true);
if (!thumb2.empty() && thumb2 != thumb && CFile::Exists(thumb2))
return thumb2;
}
return "";
}
-std::string CFileItem::GetLocalArtBaseFilename() const
-{
- bool useFolder = false;
- return GetLocalArtBaseFilename(useFolder);
-}
-
-std::string CFileItem::GetLocalArtBaseFilename(bool& useFolder) const
-{
- std::string strFile;
- if (IsStack())
- {
- std::string strPath;
- URIUtils::GetParentPath(m_strPath,strPath);
- strFile = URIUtils::AddFileToFolder(
- strPath, URIUtils::GetFileName(CStackDirectory::GetStackedTitlePath(m_strPath)));
- }
-
- std::string file = strFile.empty() ? m_strPath : strFile;
- if (URIUtils::IsInRAR(file) || URIUtils::IsInZIP(file))
- {
- std::string strPath = URIUtils::GetDirectory(file);
- std::string strParent;
- URIUtils::GetParentPath(strPath,strParent);
- strFile = URIUtils::AddFileToFolder(strParent, URIUtils::GetFileName(file));
- }
-
- if (IsMultiPath())
- strFile = CMultiPathDirectory::GetFirstPath(m_strPath);
-
- if (IsOpticalMediaFile())
- { // optical media files should be treated like folders
- useFolder = true;
- strFile = GetLocalMetadataPath();
- }
- else if (useFolder && !(m_bIsFolder && !IsFileFolder()))
- {
- file = strFile.empty() ? m_strPath : strFile;
- strFile = URIUtils::GetDirectory(file);
- }
-
- if (strFile.empty())
- strFile = GetDynPath();
-
- return strFile;
-}
-
-std::string CFileItem::GetLocalArt(const std::string& artFile, bool useFolder) const
-{
- // no retrieving of empty art files from folders
- if (useFolder && artFile.empty())
- return "";
-
- std::string strFile = GetLocalArtBaseFilename(useFolder);
- if (strFile.empty()) // empty filepath -> nothing to find
- return "";
-
- if (useFolder)
- {
- if (!artFile.empty())
- return URIUtils::AddFileToFolder(strFile, artFile);
- }
- else
- {
- if (artFile.empty()) // old thumbnail matching
- return URIUtils::ReplaceExtension(strFile, ".tbn");
- else
- return URIUtils::ReplaceExtension(strFile, "-" + artFile);
- }
- return "";
-}
-
-std::string CFileItem::GetFolderThumb(const std::string &folderJPG /* = "folder.jpg" */) const
-{
- std::string strFolder = m_strPath;
-
- if (IsStack() ||
- URIUtils::IsInRAR(strFolder) ||
- URIUtils::IsInZIP(strFolder))
- {
- URIUtils::GetParentPath(m_strPath,strFolder);
- }
-
- if (IsMultiPath())
- strFolder = CMultiPathDirectory::GetFirstPath(m_strPath);
-
- if (IsPlugin())
- return "";
-
- return URIUtils::AddFileToFolder(strFolder, folderJPG);
-}
-
std::string CFileItem::GetMovieName(bool bUseFolderNames /* = false */) const
{
if (IsPlugin() && HasVideoInfoTag() && !GetVideoInfoTag()->m_strTitle.empty())
@@ -2514,89 +2354,6 @@ const std::shared_ptr<PVR::CPVRChannel> CFileItem::GetPVRChannelInfoTag() const
: std::shared_ptr<CPVRChannel>();
}
-std::string CFileItem::FindTrailer() const
-{
- std::string strFile2;
- std::string strFile = m_strPath;
- if (IsStack())
- {
- std::string strPath;
- URIUtils::GetParentPath(m_strPath,strPath);
- CStackDirectory dir;
- std::string strPath2;
- strPath2 = dir.GetStackedTitlePath(strFile);
- strFile = URIUtils::AddFileToFolder(strPath,URIUtils::GetFileName(strPath2));
- CFileItem item(dir.GetFirstStackedFile(m_strPath),false);
- std::string strTBNFile(URIUtils::ReplaceExtension(ART::GetTBNFile(item), "-trailer"));
- strFile2 = URIUtils::AddFileToFolder(strPath,URIUtils::GetFileName(strTBNFile));
- }
- if (URIUtils::IsInRAR(strFile) || URIUtils::IsInZIP(strFile))
- {
- std::string strPath = URIUtils::GetDirectory(strFile);
- std::string strParent;
- URIUtils::GetParentPath(strPath,strParent);
- strFile = URIUtils::AddFileToFolder(strParent,URIUtils::GetFileName(m_strPath));
- }
-
- // no local trailer available for these
- if (NETWORK::IsInternetStream(*this) || URIUtils::IsUPnP(strFile) ||
- URIUtils::IsBluray(strFile) || IsLiveTV() || IsPlugin() || IsDVD())
- return "";
-
- std::string strDir = URIUtils::GetDirectory(strFile);
- CFileItemList items;
- CDirectory::GetDirectory(strDir, items, CServiceBroker::GetFileExtensionProvider().GetVideoExtensions(), DIR_FLAG_READ_CACHE | DIR_FLAG_NO_FILE_INFO | DIR_FLAG_NO_FILE_DIRS);
- URIUtils::RemoveExtension(strFile);
- strFile += "-trailer";
- std::string strFile3 = URIUtils::AddFileToFolder(strDir, "movie-trailer");
-
- // Precompile our REs
- VECCREGEXP matchRegExps;
- CRegExp tmpRegExp(true, CRegExp::autoUtf8);
- const std::vector<std::string>& strMatchRegExps = CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_trailerMatchRegExps;
-
- std::vector<std::string>::const_iterator strRegExp = strMatchRegExps.begin();
- while (strRegExp != strMatchRegExps.end())
- {
- if (tmpRegExp.RegComp(*strRegExp))
- {
- matchRegExps.push_back(tmpRegExp);
- }
- ++strRegExp;
- }
-
- std::string strTrailer;
- for (int i = 0; i < items.Size(); i++)
- {
- std::string strCandidate = items[i]->m_strPath;
- URIUtils::RemoveExtension(strCandidate);
- if (StringUtils::EqualsNoCase(strCandidate, strFile) ||
- StringUtils::EqualsNoCase(strCandidate, strFile2) ||
- StringUtils::EqualsNoCase(strCandidate, strFile3))
- {
- strTrailer = items[i]->m_strPath;
- break;
- }
- else
- {
- VECCREGEXP::iterator expr = matchRegExps.begin();
-
- while (expr != matchRegExps.end())
- {
- if (expr->RegFind(strCandidate) != -1)
- {
- strTrailer = items[i]->m_strPath;
- i = items.Size();
- break;
- }
- ++expr;
- }
- }
- }
-
- return strTrailer;
-}
-
VideoDbContentType CFileItem::GetVideoContentType() const
{
VideoDbContentType type = VideoDbContentType::MOVIES;
diff --git a/xbmc/FileItem.h b/xbmc/FileItem.h
index a6ffd48cec..ffdac95d49 100644
--- a/xbmc/FileItem.h
+++ b/xbmc/FileItem.h
@@ -56,6 +56,7 @@ class CPVRChannel;
class CPVRChannelGroupMember;
class CPVREpgInfoTag;
class CPVREpgSearchFilter;
+class CPVRProvider;
class CPVRRecording;
class CPVRTimerInfoTag;
}
@@ -123,6 +124,7 @@ public:
explicit CFileItem(const std::shared_ptr<PVR::CPVRChannelGroupMember>& channelGroupMember);
explicit CFileItem(const std::shared_ptr<PVR::CPVRRecording>& record);
explicit CFileItem(const std::shared_ptr<PVR::CPVRTimerInfoTag>& timer);
+ explicit CFileItem(const std::string& path, const std::shared_ptr<PVR::CPVRProvider>& provider);
explicit CFileItem(const CMediaSource& share);
explicit CFileItem(std::shared_ptr<const ADDON::IAddon> addonInfo);
explicit CFileItem(const EventPtr& eventLogEntry);
@@ -205,6 +207,7 @@ public:
bool IsDeletedPVRRecording() const;
bool IsInProgressPVRRecording() const;
bool IsPVRTimer() const;
+ bool IsPVRProvider() const;
bool IsType(const char *ext) const;
bool IsVirtualDirectoryRoot() const;
bool IsReadOnly() const;
@@ -223,7 +226,6 @@ public:
void RemoveExtension();
void CleanString();
- void FillInDefaultIcon();
void SetFileSizeLabel();
void SetLabel(const std::string &strLabel) override;
VideoDbContentType GetVideoContentType() const;
@@ -301,6 +303,13 @@ public:
return m_pvrTimerInfoTag;
}
+ inline bool HasPVRProviderInfoTag() const { return m_pvrProviderInfoTag != nullptr; }
+
+ inline const std::shared_ptr<PVR::CPVRProvider> GetPVRProviderInfoTag() const
+ {
+ return m_pvrProviderInfoTag;
+ }
+
/*!
\brief return the item to play. will be almost 'this', but can be different (e.g. "Play recording" from PVR EPG grid window)
\return the item to play
@@ -388,32 +397,6 @@ public:
CPictureInfoTag* GetPictureInfoTag();
- /*!
- \brief Assemble the base filename of local artwork for an item,
- accounting for archives, stacks and multi-paths, and BDMV/VIDEO_TS folders.
- `useFolder` is set to false
- \return the path to the base filename for artwork lookup.
- \sa GetLocalArt
- */
- std::string GetLocalArtBaseFilename() const;
- /*!
- \brief Assemble the base filename of local artwork for an item,
- accounting for archives, stacks and multi-paths, and BDMV/VIDEO_TS folders.
- \param useFolder whether to look in the folder for the art file. Defaults to false.
- \return the path to the base filename for artwork lookup.
- \sa GetLocalArt
- */
- std::string GetLocalArtBaseFilename(bool& useFolder) const;
-
- /*! \brief Assemble the filename of a particular piece of local artwork for an item.
- No file existence check is typically performed.
- \param artFile the art file to search for.
- \param useFolder whether to look in the folder for the art file. Defaults to false.
- \return the path to the local artwork.
- \sa FindLocalArt
- */
- std::string GetLocalArt(const std::string& artFile, bool useFolder = false) const;
-
/*! \brief Assemble the filename of a particular piece of local artwork for an item,
and check for file existence.
\param artFile the art file to search for.
@@ -436,8 +419,6 @@ public:
*/
std::string GetThumbHideIfUnwatched(const CFileItem* item) const;
- // Gets the folder image associated with this item (defaults to folder.jpg)
- std::string GetFolderThumb(const std::string &folderJPG = "folder.jpg") const;
// Gets the correct movie title
std::string GetMovieName(bool bUseFolderNames = false) const;
@@ -467,9 +448,6 @@ public:
*/
std::string GetLocalMetadataPath() const;
- // finds a matching local trailer file
- std::string FindTrailer() const;
-
bool LoadMusicTag();
bool LoadGameTag();
@@ -622,6 +600,7 @@ private:
std::shared_ptr<PVR::CPVRRecording> m_pvrRecordingInfoTag;
std::shared_ptr<PVR::CPVRTimerInfoTag> m_pvrTimerInfoTag;
std::shared_ptr<PVR::CPVRChannelGroupMember> m_pvrChannelGroupMemberInfoTag;
+ std::shared_ptr<PVR::CPVRProvider> m_pvrProviderInfoTag;
CPictureInfoTag* m_pictureInfoTag;
std::shared_ptr<const ADDON::IAddon> m_addonInfo;
KODI::GAME::CGameInfoTag* m_gameInfoTag;
diff --git a/xbmc/FileItemList.cpp b/xbmc/FileItemList.cpp
index cb8e918001..5f9bfbb8dd 100644
--- a/xbmc/FileItemList.cpp
+++ b/xbmc/FileItemList.cpp
@@ -23,6 +23,7 @@
#include "settings/Settings.h"
#include "settings/SettingsComponent.h"
#include "utils/Archive.h"
+#include "utils/ArtUtils.h"
#include "utils/Crc32.h"
#include "utils/FileExtensionProvider.h"
#include "utils/Random.h"
@@ -545,7 +546,7 @@ void CFileItemList::FillInDefaultIcons()
for (int i = 0; i < (int)m_items.size(); ++i)
{
CFileItemPtr pItem = m_items[i];
- pItem->FillInDefaultIcon();
+ ART::FillInDefaultIcon(*pItem);
}
}
diff --git a/xbmc/GUIInfoManager.cpp b/xbmc/GUIInfoManager.cpp
index b768999315..4bfc07d3d1 100644
--- a/xbmc/GUIInfoManager.cpp
+++ b/xbmc/GUIInfoManager.cpp
@@ -26,6 +26,7 @@
#include "messaging/ApplicationMessenger.h"
#include "playlists/PlayListTypes.h"
#include "settings/SkinSettings.h"
+#include "utils/ArtUtils.h"
#include "utils/CharsetConverter.h"
#include "utils/FileUtils.h"
#include "utils/StringUtils.h"
@@ -2883,6 +2884,14 @@ const infomap musicpartymode[] = {{ "enabled", MUSICPM_ENABLED },
/// @skinning_v19 **[New Infolabel]** \link MusicPlayer_Station `MusicPlayer.Station`\endlink
/// <p>
/// }
+/// \table_row3{ <b>`MusicPlayer.MediaProviders`</b>,
+/// \anchor MusicPlayer_MediaProviders
+/// _string_,
+/// @return string containing the names of the providers of the currently playing media\, separated by commas if muliple are present.
+/// <p><hr>
+/// @skinning_v22 **[New Infolabel]** \link MusicPlayer_MediaProviders `MusicPlayer.MediaProviders`\endlink
+/// <p>
+/// }
/// \table_end
///
/// -----------------------------------------------------------------------------
@@ -2931,7 +2940,8 @@ const infomap musicplayer[] = {{ "title", MUSICPLAYER_TITLE },
{ "bpm", MUSICPLAYER_BPM },
{ "ismultidisc", MUSICPLAYER_ISMULTIDISC },
{ "totaldiscs", MUSICPLAYER_TOTALDISCS },
- { "station", MUSICPLAYER_STATIONNAME }
+ { "station", MUSICPLAYER_STATIONNAME },
+ { "mediaproviders", MUSICPLAYER_MEDIAPROVIDERS },
};
// clang-format on
@@ -3889,6 +3899,33 @@ const infomap musicplayer[] = {{ "title", MUSICPLAYER_TITLE },
/// @return The parental rating of the currently playing programme (PVR).
/// <p>
/// }
+/// \table_row3{ <b>`VideoPlayer.ParentalRatingCode`</b>,
+/// \anchor VideoPlayer_ParentalRatingCode
+/// _string_,
+/// @return The parental rating code (eg: 'PG'\, etc) of the currently playing programme (PVR).
+/// <p><hr>
+/// @skinning_v22 **[New Infolabel]** \link VideoPlayer_ParentalRatingCode `VideoPlayer.ParentalRatingCode`\endlink
+/// <p>
+/// }
+/// \table_row3{ <b>`VideoPlayer.ParentalRatingIcon`</b>,
+/// \anchor VideoPlayer_ParentalRatingIcon
+/// _string_,
+/// @return The parental rating icon path of the currently playing programme (PVR).
+/// <p><hr>
+/// @skinning_v22 **[New Infolabel]** \link VideoPlayer_ParentalRatingIcon `VideoPlayer.ParentalRatingIcon`\endlink
+/// <p>
+/// }
+/// \table_row3{ <b>`VideoPlayer.ParentalRatingSource`</b>,
+/// \anchor VideoPlayer_ParentalRatingSource
+/// _string_,
+/// @return The source used to determine the parental rating of the currently playing programme (PVR).
+/// Values could include the Country alpha-3 code or the name/abbreviation
+/// of the authority issuing the rating code. Can be used with
+/// the \link VideoPlayer_ParentalRatingCode `ParentalRatingCode`\endlink for skin-derived icons if required.
+/// <p><hr>
+/// @skinning_v22 **[New Infolabel]** \link VideoPlayer_ParentalRatingSource `VideoPlayer.ParentalRatingSource`\endlink
+/// <p>
+/// }
/// \table_row3{ <b>`VideoPlayer.DBID`</b>,
/// \anchor VideoPlayer_DBID
/// _string_,
@@ -3954,6 +3991,23 @@ const infomap musicplayer[] = {{ "title", MUSICPLAYER_TITLE },
/// <p><hr>
/// @skinning_v21 **[New Infolabel]** \link VideoPlayer_VideoVersionName `VideoPlayer.VideoVersionName`\endlink
/// }
+/// \table_row3{ <b>`VideoPlayer.EpisodePart`</b>,
+/// \anchor VideoPlayer_EpisodePart
+/// _string_,
+/// @return string containing the number of parts of a single episode - empty if no data provided
+/// <p><hr>
+/// @skinning_v22 **[Infolabel Updated]** \link VideoPlayer_EpisodePart `VideoPlayer.EpisodePart`\endlink
+/// also supports EPG.
+/// <p>
+/// }
+/// \table_row3{ <b>`VideoPlayer.MediaProviders`</b>,
+/// \anchor VideoPlayer_MediaProviders
+/// _string_,
+/// @return string containing the names of the providers of the currently playing media\, separated by commas if muliple are present.
+/// <p><hr>
+/// @skinning_v22 **[New Infolabel]** \link VideoPlayer_MediaProviders `VideoPlayer.MediaProviders`\endlink
+/// <p>
+/// }
/// \table_end
///
/// -----------------------------------------------------------------------------
@@ -4020,6 +4074,9 @@ const infomap videoplayer[] = {{ "title", VIDEOPLAYER_TITLE },
{ "channelgroup", VIDEOPLAYER_CHANNEL_GROUP },
{ "hasepg", VIDEOPLAYER_HAS_EPG },
{ "parentalrating", VIDEOPLAYER_PARENTAL_RATING },
+ { "parentalratingcode", VIDEOPLAYER_PARENTAL_RATING_CODE },
+ { "parentalratingicon", VIDEOPLAYER_PARENTAL_RATING_ICON },
+ { "parentalratingsource", VIDEOPLAYER_PARENTAL_RATING_SOURCE },
{ "isstereoscopic", VIDEOPLAYER_IS_STEREOSCOPIC },
{ "stereoscopicmode", VIDEOPLAYER_STEREOSCOPIC_MODE },
{ "canresumelivetv", VIDEOPLAYER_CAN_RESUME_LIVE_TV },
@@ -4032,7 +4089,9 @@ const infomap videoplayer[] = {{ "title", VIDEOPLAYER_TITLE },
{ "hdrtype", VIDEOPLAYER_HDR_TYPE },
{ "art", VIDEOPLAYER_ART},
{ "videoversionname", VIDEOPLAYER_VIDEOVERSION_NAME},
- { "hasvideoversions", VIDEOPLAYER_HAS_VIDEOVERSIONS}
+ { "hasvideoversions", VIDEOPLAYER_HAS_VIDEOVERSIONS},
+ { "episodepart", VIDEOPLAYER_EPISODEPART},
+ { "mediaproviders", VIDEOPLAYER_MEDIAPROVIDERS },
};
// clang-format on
@@ -6794,6 +6853,25 @@ const infomap container_str[] = {{ "property", CONTAINER_PROPERTY },
/// @skinning_v21 **[New Infolabel]** \link ListItem_ParentalRatingCode `ListItem.ParentalRatingCode`\endlink
/// <p>
/// }
+/// \table_row3{ <b>`ListItem.ParentalRatingIcon`</b>,
+/// \anchor ListItem_ParentalRatingIcon
+/// _string_,
+/// @return The parental rating icon path of the list item (PVR).
+/// <p><hr>
+/// @skinning_v22 **[New Infolabel]** \link ListItem_ParentalRatingIcon `ListItem.ParentalRatingIcon`\endlink
+/// <p>
+/// }
+/// \table_row3{ <b>`ListItem.ParentalRatingSource`</b>,
+/// \anchor ListItem_ParentalRatingSource
+/// _string_,
+/// @return The source used to determine the parental rating of the list item (PVR).
+/// Values could include the Country alpha-3 code or the name/abbreviation
+/// of the authority issuing the rating code. Can be used with
+/// the \link ListItem_ParentalRatingCode `ParentalRatingCode`\endlink for skin-derived icons if required.
+/// <p><hr>
+/// @skinning_v22 **[New Infolabel]** \link ListItem_ParentalRatingSource `ListItem.ParentalRatingSource`\endlink
+/// <p>
+/// }
/// \table_row3{ <b>`ListItem.CurrentItem`</b>,
/// \anchor ListItem_CurrentItem
/// _string_,
@@ -7015,6 +7093,23 @@ const infomap container_str[] = {{ "property", CONTAINER_PROPERTY },
/// @skinning_v22 **[New Infolabel]** \link ListItem_PVRGroupOrigin `ListItem.PVRGroupOrigin`\endlink
/// <p>
/// }
+/// \table_row3{ <b>`ListItem.EpisodePart`</b>,
+/// \anchor ListItem_EpisodePart
+/// _string_,
+/// @return string containing the number of parts of a single episode - empty if no data provided
+/// <p><hr>
+/// @skinning_v22 **[New Infolabel]** \link ListItem_EpisodePart `ListItem.EpisodePart`\endlink
+/// <p>
+/// }
+/// \table_row3{ <b>`ListItem.MediaProviders`</b>,
+/// \anchor ListItem_MediaProviders
+/// _string_,
+/// @return string containing the names of the media providers of the item\, separated by commas if muliple are present.
+/// <p><hr>
+/// @skinning_v22 **[New Infolabel]** \link ListItem_MediaProviders `ListItem.MediaProviders`\endlink
+/// <p>
+/// }
+///
/// \table_end
///
/// -----------------------------------------------------------------------------
@@ -7214,6 +7309,8 @@ const infomap listitem_labels[]= {{ "thumb", LISTITEM_THUMB },
{ "property", LISTITEM_PROPERTY },
{ "parentalrating", LISTITEM_PARENTAL_RATING },
{ "parentalratingcode", LISTITEM_PARENTAL_RATING_CODE },
+ { "parentalratingicon", LISTITEM_PARENTAL_RATING_ICON },
+ { "parentalratingsource", LISTITEM_PARENTAL_RATING_SOURCE },
{ "currentitem", LISTITEM_CURRENTITEM },
{ "isnew", LISTITEM_IS_NEW },
{ "isboxset", LISTITEM_IS_BOXSET },
@@ -7240,6 +7337,8 @@ const infomap listitem_labels[]= {{ "thumb", LISTITEM_THUMB },
{ "pvrclientname", LISTITEM_PVR_CLIENT_NAME },
{ "pvrinstancename", LISTITEM_PVR_INSTANCE_NAME },
{ "pvrgrouporigin", LISTITEM_PVR_GROUP_ORIGIN },
+ { "episodepart", LISTITEM_EPISODEPART },
+ { "mediaproviders", LISTITEM_MEDIAPROVIDERS },
};
// clang-format on
@@ -11215,7 +11314,7 @@ void CGUIInfoManager::UpdateCurrentItem(const CFileItem &item)
void CGUIInfoManager::SetCurrentItem(const CFileItem &item)
{
*m_currentFile = item;
- m_currentFile->FillInDefaultIcon();
+ ART::FillInDefaultIcon(*m_currentFile);
m_infoProviders.InitCurrentItem(m_currentFile);
@@ -11229,7 +11328,7 @@ void CGUIInfoManager::SetCurrentAlbumThumb(const std::string &thumbFileName)
else
{
m_currentFile->SetArt("thumb", "");
- m_currentFile->FillInDefaultIcon();
+ ART::FillInDefaultIcon(*m_currentFile);
}
}
diff --git a/xbmc/ThumbLoader.cpp b/xbmc/ThumbLoader.cpp
index 40db919d52..1dfce68659 100644
--- a/xbmc/ThumbLoader.cpp
+++ b/xbmc/ThumbLoader.cpp
@@ -114,7 +114,7 @@ std::string CProgramThumbLoader::GetLocalThumb(const CFileItem &item)
// look for the thumb
if (item.m_bIsFolder)
{
- std::string folderThumb = item.GetFolderThumb();
+ const std::string folderThumb = ART::GetFolderThumb(item);
if (CFileUtils::Exists(folderThumb))
return folderThumb;
}
diff --git a/xbmc/URL.cpp b/xbmc/URL.cpp
index 3102a4d69f..8bc0f98c7f 100644
--- a/xbmc/URL.cpp
+++ b/xbmc/URL.cpp
@@ -131,13 +131,16 @@ void CURL::Parse(std::string strURL1)
// ones that come to mind are iso9660, cdda, musicdb, etc.
// they are all local protocols and have no server part, port number, special options, etc.
// this removes the need for special handling below.
+ // clang-format off
if (
IsProtocol("stack") ||
IsProtocol("virtualpath") ||
IsProtocol("multipath") ||
IsProtocol("special") ||
- IsProtocol("resource")
+ IsProtocol("resource") ||
+ IsProtocol("file")
)
+ // clang-format on
{
SetFileName(std::move(strURL).substr(iPos));
return;
diff --git a/xbmc/Util.cpp b/xbmc/Util.cpp
index 0b507b502b..cd761165e3 100644
--- a/xbmc/Util.cpp
+++ b/xbmc/Util.cpp
@@ -158,7 +158,7 @@ std::string GetHomePath(const std::string& strTarget, std::string strPath)
strPath = CUtil::ResolveExecutablePath();
auto last_sep = strPath.find_last_of(PATH_SEPARATOR_CHAR);
if (last_sep != std::string::npos)
- strPath = strPath.substr(0, last_sep);
+ strPath.resize(last_sep);
g_charsetConverter.utf8ToW(strPath, strPathW);
if (IsDirectoryValidRoot(strPathW))
diff --git a/xbmc/addons/AddonManager.h b/xbmc/addons/AddonManager.h
index fd7ee70f66..0679ffd81f 100644
--- a/xbmc/addons/AddonManager.h
+++ b/xbmc/addons/AddonManager.h
@@ -11,6 +11,7 @@
#include "threads/CriticalSection.h"
#include "utils/EventStream.h"
+#include <cstdint>
#include <map>
#include <memory>
#include <mutex>
diff --git a/xbmc/addons/IAddon.h b/xbmc/addons/IAddon.h
index 0bc383055f..d242f60f0e 100644
--- a/xbmc/addons/IAddon.h
+++ b/xbmc/addons/IAddon.h
@@ -8,6 +8,7 @@
#pragma once
+#include <cstdint>
#include <map>
#include <memory>
#include <string>
diff --git a/xbmc/addons/Repository.cpp b/xbmc/addons/Repository.cpp
index ec9704634e..2704729fa9 100644
--- a/xbmc/addons/Repository.cpp
+++ b/xbmc/addons/Repository.cpp
@@ -165,7 +165,7 @@ bool CRepository::FetchChecksum(const std::string& url,
ss.write(temp, read);
if (read <= -1)
return false;
- checksum = ss.str();
+ checksum = std::move(ss).str();
std::size_t pos = checksum.find_first_of(" \n");
if (pos != std::string::npos)
{
diff --git a/xbmc/addons/binary-addons/AddonInstanceHandler.cpp b/xbmc/addons/binary-addons/AddonInstanceHandler.cpp
index 34c303b965..695efd3290 100644
--- a/xbmc/addons/binary-addons/AddonInstanceHandler.cpp
+++ b/xbmc/addons/binary-addons/AddonInstanceHandler.cpp
@@ -79,6 +79,11 @@ std::string IAddonInstanceHandler::ID() const
return m_addon ? m_addon->ID() : "";
}
+AddonInstanceId IAddonInstanceHandler::InstanceID() const
+{
+ return m_instanceId;
+}
+
std::string IAddonInstanceHandler::Name() const
{
return m_addon ? m_addon->Name() : "";
diff --git a/xbmc/addons/binary-addons/AddonInstanceHandler.h b/xbmc/addons/binary-addons/AddonInstanceHandler.h
index 27ba95b2be..d3b40e03d4 100644
--- a/xbmc/addons/binary-addons/AddonInstanceHandler.h
+++ b/xbmc/addons/binary-addons/AddonInstanceHandler.h
@@ -70,6 +70,7 @@ public:
const std::string& UniqueWorkID() { return m_uniqueWorkID; }
std::string ID() const;
+ AddonInstanceId InstanceID() const;
std::string Name() const;
std::string Author() const;
std::string Icon() const;
diff --git a/xbmc/addons/gui/GUIDialogAddonSettings.cpp b/xbmc/addons/gui/GUIDialogAddonSettings.cpp
index 60cbaa2a2e..8efb42876b 100644
--- a/xbmc/addons/gui/GUIDialogAddonSettings.cpp
+++ b/xbmc/addons/gui/GUIDialogAddonSettings.cpp
@@ -14,10 +14,12 @@
#include "GUIUserMessages.h"
#include "ServiceBroker.h"
#include "addons/AddonManager.h"
+#include "addons/addoninfo/AddonInfo.h"
#include "addons/addoninfo/AddonType.h"
#include "addons/settings/AddonSettings.h"
#include "dialogs/GUIDialogSelect.h"
#include "dialogs/GUIDialogYesNo.h"
+#include "games/addons/GameClient.h"
#include "guilib/GUIComponent.h"
#include "guilib/GUIWindowManager.h"
#include "guilib/LocalizeStrings.h"
@@ -37,7 +39,14 @@
#define CONTROL_BTN_LEVELS 20
using namespace ADDON;
-using namespace KODI::MESSAGING;
+using namespace KODI;
+using namespace MESSAGING;
+
+namespace
+{
+// Fallback icon shown when no add-on icon is available
+constexpr const char* DEFAULT_ADDON_ICON = "DefaultAddon.png";
+} // namespace
CGUIDialogAddonSettings::CGUIDialogAddonSettings()
: CGUIDialogSettingsManagerBase(WINDOW_DIALOG_ADDON_SETTINGS, "DialogAddonSettings.xml")
@@ -418,8 +427,25 @@ void CGUIDialogAddonSettings::SetupView()
CGUIDialogSettingsManagerBase::SetupView();
- // set addon id as window property
+ // set addon properties as window properties
SetProperty("Addon.ID", m_addon->ID());
+ SetProperty("Addon.Type", ADDON::CAddonInfo::TranslateType(m_addon->Type(), false));
+ if (!m_addon->Icon().empty())
+ SetProperty("Addon.Icon", m_addon->Icon());
+ else
+ SetProperty("Addon.Icon", DEFAULT_ADDON_ICON);
+ SetProperty("Addon.Version", m_addon->Version().asString());
+
+ if (m_addon->Type() == ADDON::AddonType::GAMEDLL)
+ {
+ std::shared_ptr<GAME::CGameClient> gameClient =
+ std::dynamic_pointer_cast<GAME::CGameClient>(m_addon);
+ if (gameClient)
+ {
+ SetProperty("GameClient.Name", gameClient->GetEmulatorName());
+ SetProperty("GameClient.Platforms", gameClient->GetPlatforms());
+ }
+ }
// set heading
SetHeading(StringUtils::Format("$LOCALIZE[10004] - {}",
diff --git a/xbmc/addons/kodi-dev-kit/include/kodi/AddonBase.h b/xbmc/addons/kodi-dev-kit/include/kodi/AddonBase.h
index c64ce05a11..1ff8e0a26a 100644
--- a/xbmc/addons/kodi-dev-kit/include/kodi/AddonBase.h
+++ b/xbmc/addons/kodi-dev-kit/include/kodi/AddonBase.h
@@ -207,14 +207,17 @@ class CStructHdl
public:
CStructHdl() : m_cStructure(new C_STRUCT()), m_owner(true) {}
- CStructHdl(const CPP_CLASS& cppClass)
+ CStructHdl(const CStructHdl& cppClass)
: m_cStructure(new C_STRUCT(*cppClass.m_cStructure)), m_owner(true)
{
}
- CStructHdl(const C_STRUCT* cStructure) : m_cStructure(new C_STRUCT(*cStructure)), m_owner(true) {}
+ explicit CStructHdl(const C_STRUCT* cStructure)
+ : m_cStructure(new C_STRUCT(*cStructure)), m_owner(true)
+ {
+ }
- CStructHdl(C_STRUCT* cStructure) : m_cStructure(cStructure) { assert(cStructure); }
+ explicit CStructHdl(C_STRUCT* cStructure) : m_cStructure(cStructure) { assert(cStructure); }
const CStructHdl& operator=(const CStructHdl& right)
{
@@ -284,21 +287,27 @@ template<class CPP_CLASS, typename C_STRUCT>
class DynamicCStructHdl
{
public:
- DynamicCStructHdl() : m_cStructure(new C_STRUCT()), m_owner(true) {}
+ DynamicCStructHdl() : m_cStructure(new C_STRUCT()), m_owner(true)
+ {
+ memset(m_cStructure, 0, sizeof(C_STRUCT));
+ }
- DynamicCStructHdl(const CPP_CLASS& cppClass)
+ DynamicCStructHdl(const DynamicCStructHdl& cppClass)
: m_cStructure(new C_STRUCT(*cppClass.m_cStructure)), m_owner(true)
{
CPP_CLASS::AllocResources(cppClass.m_cStructure, m_cStructure);
}
- DynamicCStructHdl(const C_STRUCT* cStructure)
+ explicit DynamicCStructHdl(const C_STRUCT* cStructure)
: m_cStructure(new C_STRUCT(*cStructure)), m_owner(true)
{
CPP_CLASS::AllocResources(cStructure, m_cStructure);
}
- DynamicCStructHdl(C_STRUCT* cStructure) : m_cStructure(cStructure) { assert(cStructure); }
+ explicit DynamicCStructHdl(C_STRUCT* cStructure) : m_cStructure(cStructure)
+ {
+ assert(cStructure);
+ }
const DynamicCStructHdl& operator=(const DynamicCStructHdl& right)
{
diff --git a/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/PVR.h b/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/PVR.h
index 2f0a1bea3b..83ca8b6ccb 100644
--- a/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/PVR.h
+++ b/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/PVR.h
@@ -291,7 +291,8 @@ namespace addon
/// PVR_ERROR GetProviders(std::vector<kodi::addon::PVRProvider>& providers) override;
/// PVR_ERROR GetChannelsAmount(int& amount) override;
/// PVR_ERROR GetChannels(bool radio, std::vector<kodi::addon::PVRChannel>& channels) override;
-/// PVR_ERROR GetChannelStreamProperties(const kodi::addon::PVRChannel& channel,
+/// PVR_ERROR GetChannelStreamProperties(const kodi::addon::PVRChannel& channel,
+/// PVR_SOURCE source,
/// std::vector<kodi::addon::PVRStreamProperty>& properties) override;
///
/// private:
@@ -351,6 +352,7 @@ namespace addon
/// }
///
/// PVR_ERROR CMyPVRClient::GetChannelStreamProperties(const kodi::addon::PVRChannel& channel,
+/// PVR_SOURCE source,
/// std::vector<kodi::addon::PVRStreamProperty>& properties)
/// {
/// if (channel.GetUniqueId() == 123)
@@ -968,6 +970,9 @@ public:
/// @brief Get the stream properties for a channel from the backend.
///
/// @param[in] channel The channel to get the stream properties for.
+ /// @param[in] source PVR_SOURCE_EPG_AS_LIVE if this call resulted from
+ /// PVR_STREAM_PROPERTY_EPGPLAYBACKASLIVE being set from GetEPGTagStreamProperties(), DEFAULT
+ /// otherwise
/// @param[out] properties the properties required to play the stream.
/// @return @ref PVR_ERROR_NO_ERROR if the stream is available.
///
@@ -988,6 +993,7 @@ public:
/// ~~~~~~~~~~~~~{.cpp}
/// ...
/// PVR_ERROR CMyPVRInstance::GetChannelStreamProperties(const kodi::addon::PVRChannel& channel,
+ /// PVR_SOURCE source,
/// std::vector<kodi::addon::PVRStreamProperty>& properties)
/// {
/// ...
@@ -1002,6 +1008,7 @@ public:
///
virtual PVR_ERROR GetChannelStreamProperties(
const kodi::addon::PVRChannel& channel,
+ PVR_SOURCE source,
std::vector<kodi::addon::PVRStreamProperty>& properties)
{
return PVR_ERROR_NOT_IMPLEMENTED;
@@ -2416,6 +2423,17 @@ public:
//----------------------------------------------------------------------------
//============================================================================
+ /// @brief The currently playing stream has been closed
+ ///
+ /// @remarks Called if both @ref PVRCapabilities::SetHandlesInputStream() or
+ /// @ref PVRCapabilities::SetHandlesDemuxing() are set to false. Allows add-ons
+ /// to do any cleanup required prior to a stream being opened.
+ /// @return @ref PVR_ERROR_NO_ERROR if the properties have been fetched successfully.
+ ///
+ virtual PVR_ERROR StreamClosed() { return PVR_ERROR_NOT_IMPLEMENTED; }
+ //----------------------------------------------------------------------------
+
+ //============================================================================
/// @brief Read the next packet from the demultiplexer, if there is one.
///
/// @return The next packet.
@@ -2804,6 +2822,7 @@ private:
instance->pvr->toAddon->SeekLiveStream = ADDON_SeekLiveStream;
instance->pvr->toAddon->LengthLiveStream = ADDON_LengthLiveStream;
instance->pvr->toAddon->GetStreamProperties = ADDON_GetStreamProperties;
+ instance->pvr->toAddon->StreamClosed = ADDON_StreamClosed;
instance->pvr->toAddon->GetStreamReadChunkSize = ADDON_GetStreamReadChunkSize;
instance->pvr->toAddon->IsRealTimeStream = ADDON_IsRealTimeStream;
//--==----==----==----==----==----==----==----==----==----==----==----==----==
@@ -2927,13 +2946,14 @@ private:
inline static PVR_ERROR ADDON_GetChannelStreamProperties(const AddonInstance_PVR* instance,
const PVR_CHANNEL* channel,
+ PVR_SOURCE source,
PVR_NAMED_VALUE*** properties,
unsigned int* propertiesCount)
{
*propertiesCount = 0;
std::vector<PVRStreamProperty> propertiesList;
PVR_ERROR error = static_cast<CInstancePVRClient*>(instance->toAddon->addonInstance)
- ->GetChannelStreamProperties(channel, propertiesList);
+ ->GetChannelStreamProperties(channel, source, propertiesList);
if (error == PVR_ERROR_NO_ERROR && !propertiesList.empty())
{
*properties = AllocAndCopyPointerArray<PVRStreamProperty, PVR_NAMED_VALUE>(propertiesList,
@@ -3438,6 +3458,11 @@ private:
return err;
}
+ inline static PVR_ERROR ADDON_StreamClosed(const AddonInstance_PVR* instance)
+ {
+ return static_cast<CInstancePVRClient*>(instance->toAddon->addonInstance)->StreamClosed();
+ }
+
inline static PVR_ERROR ADDON_GetStreamReadChunkSize(const AddonInstance_PVR* instance,
int* chunksize)
{
diff --git a/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/pvr/ChannelGroups.h b/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/pvr/ChannelGroups.h
index 44fbed3c79..82f26f9aac 100644
--- a/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/pvr/ChannelGroups.h
+++ b/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/pvr/ChannelGroups.h
@@ -40,7 +40,7 @@ class PVRChannelGroup : public DynamicCStructHdl<PVRChannelGroup, PVR_CHANNEL_GR
public:
/*! \cond PRIVATE */
- PVRChannelGroup() { memset(m_cStructure, 0, sizeof(PVR_CHANNEL_GROUP)); }
+ PVRChannelGroup() = default;
PVRChannelGroup(const PVRChannelGroup& group) : DynamicCStructHdl(group) {}
/*! \endcond */
@@ -66,7 +66,10 @@ public:
}
/// @brief To get with @ref SetGroupName changed values.
- std::string GetGroupName() const { return m_cStructure->strGroupName; }
+ std::string GetGroupName() const
+ {
+ return m_cStructure->strGroupName ? m_cStructure->strGroupName : "";
+ }
/// @brief **required**\n
/// **true** If this is a radio channel group, **false** otherwise.
@@ -158,7 +161,7 @@ class PVRChannelGroupMember
public:
/*! \cond PRIVATE */
- PVRChannelGroupMember() { memset(m_cStructure, 0, sizeof(PVR_CHANNEL_GROUP_MEMBER)); }
+ PVRChannelGroupMember() = default;
PVRChannelGroupMember(const PVRChannelGroupMember& member) : DynamicCStructHdl(member) {}
/*! \endcond */
@@ -186,7 +189,10 @@ public:
}
/// @brief To get with @ref SetGroupName changed values.
- std::string GetGroupName() const { return m_cStructure->strGroupName; }
+ std::string GetGroupName() const
+ {
+ return m_cStructure->strGroupName ? m_cStructure->strGroupName : "";
+ }
/// @brief **required**\n
/// Unique id of the member.
diff --git a/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/pvr/Channels.h b/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/pvr/Channels.h
index cbbb4c3abd..8ca90882a8 100644
--- a/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/pvr/Channels.h
+++ b/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/pvr/Channels.h
@@ -41,11 +41,7 @@ class PVRChannel : public DynamicCStructHdl<PVRChannel, PVR_CHANNEL>
public:
/*! \cond PRIVATE */
- PVRChannel()
- {
- memset(m_cStructure, 0, sizeof(PVR_CHANNEL));
- m_cStructure->iClientProviderUid = PVR_PROVIDER_INVALID_UID;
- }
+ PVRChannel() { m_cStructure->iClientProviderUid = PVR_PROVIDER_INVALID_UID; }
PVRChannel(const PVRChannel& channel) : DynamicCStructHdl(channel) {}
/*! \endcond */
@@ -114,7 +110,10 @@ public:
}
/// @brief To get with @ref SetChannelName changed values.
- std::string GetChannelName() const { return m_cStructure->strChannelName; }
+ std::string GetChannelName() const
+ {
+ return m_cStructure->strChannelName ? m_cStructure->strChannelName : "";
+ }
/// @brief **optional**\n
/// Input format mime type.
@@ -128,7 +127,10 @@ public:
}
/// @brief To get with @ref SetMimeType changed values.
- std::string GetMimeType() const { return m_cStructure->strMimeType; }
+ std::string GetMimeType() const
+ {
+ return m_cStructure->strMimeType ? m_cStructure->strMimeType : "";
+ }
/// @brief **optional**\n
/// The encryption ID or CaID of this channel (Conditional access systems).
@@ -153,7 +155,10 @@ public:
}
/// @brief To get with @ref SetIconPath changed values.
- std::string GetIconPath() const { return m_cStructure->strIconPath; }
+ std::string GetIconPath() const
+ {
+ return m_cStructure->strIconPath ? m_cStructure->strIconPath : "";
+ }
/// @brief **optional**\n
/// **true** if this channel is marked as hidden.
@@ -300,7 +305,10 @@ public:
}
/// @brief To get with @ref SetAdapterName changed values.
- std::string GetAdapterName() const { return m_cStructure->strAdapterName; }
+ std::string GetAdapterName() const
+ {
+ return m_cStructure->strAdapterName ? m_cStructure->strAdapterName : "";
+ }
/// @brief **optional**\n
/// Status of the adapter that's being used.
@@ -310,7 +318,10 @@ public:
}
/// @brief To get with @ref SetAdapterStatus changed values.
- std::string GetAdapterStatus() const { return m_cStructure->strAdapterStatus; }
+ std::string GetAdapterStatus() const
+ {
+ return m_cStructure->strAdapterStatus ? m_cStructure->strAdapterStatus : "";
+ }
/// @brief **optional**\n
/// Name of the current service.
@@ -320,7 +331,10 @@ public:
}
/// @brief To get with @ref SetServiceName changed values.
- std::string GetServiceName() const { return m_cStructure->strServiceName; }
+ std::string GetServiceName() const
+ {
+ return m_cStructure->strServiceName ? m_cStructure->strServiceName : "";
+ }
/// @brief **optional**\n
/// Name of the current service's provider.
@@ -330,7 +344,10 @@ public:
}
/// @brief To get with @ref SetProviderName changed values.
- std::string GetProviderName() const { return m_cStructure->strProviderName; }
+ std::string GetProviderName() const
+ {
+ return m_cStructure->strProviderName ? m_cStructure->strProviderName : "";
+ }
/// @brief **optional**\n
/// Name of the current mux.
@@ -340,7 +357,10 @@ public:
}
/// @brief To get with @ref SetMuxName changed values.
- std::string GetMuxName() const { return m_cStructure->strMuxName; }
+ std::string GetMuxName() const
+ {
+ return m_cStructure->strMuxName ? m_cStructure->strMuxName : "";
+ }
/// @brief **optional**\n
/// Signal/noise ratio.
@@ -514,7 +534,10 @@ public:
}
/// @brief To get with @ref SetCardSystem changed values.
- std::string GetCardSystem() const { return m_cStructure->strCardSystem; }
+ std::string GetCardSystem() const
+ {
+ return m_cStructure->strCardSystem ? m_cStructure->strCardSystem : "";
+ }
/// @brief **optional**\n
/// Empty string if not available.
@@ -524,7 +547,7 @@ public:
}
/// @brief To get with @ref SetReader changed values.
- std::string GetReader() const { return m_cStructure->strReader; }
+ std::string GetReader() const { return m_cStructure->strReader ? m_cStructure->strReader : ""; }
/// @brief **optional**\n
/// Empty string if not available.
@@ -534,7 +557,7 @@ public:
}
/// @brief To get with @ref SetFrom changed values.
- std::string GetFrom() const { return m_cStructure->strFrom; }
+ std::string GetFrom() const { return m_cStructure->strFrom ? m_cStructure->strFrom : ""; }
/// @brief **optional**\n
/// Empty string if not available.
@@ -544,7 +567,10 @@ public:
}
/// @brief To get with @ref SetProtocol changed values.
- std::string GetProtocol() const { return m_cStructure->strProtocol; }
+ std::string GetProtocol() const
+ {
+ return m_cStructure->strProtocol ? m_cStructure->strProtocol : "";
+ }
///@}
static void AllocResources(const PVR_DESCRAMBLE_INFO* source, PVR_DESCRAMBLE_INFO* target)
diff --git a/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/pvr/EPG.h b/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/pvr/EPG.h
index 41d9258cc9..1252bfbbb2 100644
--- a/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/pvr/EPG.h
+++ b/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/pvr/EPG.h
@@ -44,7 +44,6 @@ public:
/*! \cond PRIVATE */
PVREPGTag()
{
- memset(m_cStructure, 0, sizeof(EPG_TAG));
m_cStructure->iSeriesNumber = EPG_TAG_INVALID_SERIES_EPISODE;
m_cStructure->iEpisodeNumber = EPG_TAG_INVALID_SERIES_EPISODE;
m_cStructure->iEpisodePartNumber = EPG_TAG_INVALID_SERIES_EPISODE;
@@ -76,8 +75,10 @@ public:
/// | **Genre sub type** | `int` | @ref PVREPGTag::SetGenreSubType "SetGenreSubType" | @ref PVREPGTag::GetGenreSubType "GetGenreSubType" | *optional*
/// | **Genre description** | `std::string` | @ref PVREPGTag::SetGenreDescription "SetGenreDescription" | @ref PVREPGTag::GetGenreDescription "GetGenreDescription" | *optional*
/// | **First aired** | `time_t` | @ref PVREPGTag::SetFirstAired "SetFirstAired" | @ref PVREPGTag::GetFirstAired "GetFirstAired" | *optional*
- /// | **Parental rating** | `int` | @ref PVREPGTag::SetParentalRating "SetParentalRating" | @ref PVREPGTag::GetParentalRating "GetParentalRating" | *optional*
- /// | **Parental rating code** | `int` | @ref PVREPGTag::SetParentalRatingCode "SetParentalRatingCode" | @ref PVREPGTag::GetParentalRatingCode "GetParentalRatingCode" | *optional*
+ /// | **Parental rating** | `unsigned int` | @ref PVREPGTag::SetParentalRating "SetParentalRating" | @ref PVREPGTag::GetParentalRating "GetParentalRating" | *optional*
+ /// | **Parental rating code** | `std::string` | @ref PVREPGTag::SetParentalRatingCode "SetParentalRatingCode" | @ref PVREPGTag::GetParentalRatingCode "GetParentalRatingCode" | *optional*
+ /// | **Parental rating icon** | `std::string` | @ref PVREPGTag::SetParentalRatingIcon "SetParentalRatingIcon" | @ref PVREPGTag::GetParentalRatingIcon "GetParentalRatingIcon" | *optional*
+ /// | **Parental rating source** | `std::string` | @ref PVREPGTag::SetParentalRatingSource "SetParentalRatingSource" | @ref PVREPGTag::GetParentalRatingSource "GetParentalRatingSource" | *optional*
/// | **Star rating** | `int` | @ref PVREPGTag::SetStarRating "SetStarRating" | @ref PVREPGTag::GetStarRating "GetStarRating" | *optional*
/// | **Series number** | `int` | @ref PVREPGTag::SetSeriesNumber "SetSeriesNumber" | @ref PVREPGTag::GetSeriesNumber "GetSeriesNumber" | *optional*
/// | **Episode number** | `int` | @ref PVREPGTag::SetEpisodeNumber "SetEpisodeNumber" | @ref PVREPGTag::GetEpisodeNumber "GetEpisodeNumber" | *optional*
@@ -118,7 +119,7 @@ public:
}
/// @brief To get with @ref SetTitle changed values.
- std::string GetTitle() const { return m_cStructure->strTitle; }
+ std::string GetTitle() const { return m_cStructure->strTitle ? m_cStructure->strTitle : ""; }
/// @brief **required**\n
/// Start time in UTC.
@@ -146,7 +147,10 @@ public:
}
/// @brief To get with @ref SetPlotOutline changed values.
- std::string GetPlotOutline() const { return m_cStructure->strPlotOutline; }
+ std::string GetPlotOutline() const
+ {
+ return m_cStructure->strPlotOutline ? m_cStructure->strPlotOutline : "";
+ }
/// @brief **optional**\n
/// Plot name.
@@ -156,7 +160,7 @@ public:
}
/// @brief To get with @ref GetPlot changed values.
- std::string GetPlot() const { return m_cStructure->strPlot; }
+ std::string GetPlot() const { return m_cStructure->strPlot ? m_cStructure->strPlot : ""; }
/// @brief **optional**\n
/// Original title.
@@ -166,7 +170,10 @@ public:
}
/// @brief To get with @ref SetOriginalTitle changed values
- std::string GetOriginalTitle() const { return m_cStructure->strOriginalTitle; }
+ std::string GetOriginalTitle() const
+ {
+ return m_cStructure->strOriginalTitle ? m_cStructure->strOriginalTitle : "";
+ }
/// @brief **optional**\n
/// Cast name(s).
@@ -178,7 +185,7 @@ public:
}
/// @brief To get with @ref SetCast changed values
- std::string GetCast() const { return m_cStructure->strCast; }
+ std::string GetCast() const { return m_cStructure->strCast ? m_cStructure->strCast : ""; }
/// @brief **optional**\n
/// Director name(s).
@@ -190,7 +197,10 @@ public:
}
/// @brief To get with @ref SetDirector changed values.
- std::string GetDirector() const { return m_cStructure->strDirector; }
+ std::string GetDirector() const
+ {
+ return m_cStructure->strDirector ? m_cStructure->strDirector : "";
+ }
/// @brief **optional**\n
/// Writer name(s).
@@ -202,7 +212,7 @@ public:
}
/// @brief To get with @ref SetDirector changed values
- std::string GetWriter() const { return m_cStructure->strWriter; }
+ std::string GetWriter() const { return m_cStructure->strWriter ? m_cStructure->strWriter : ""; }
/// @brief **optional**\n
/// Year.
@@ -219,7 +229,10 @@ public:
}
/// @brief To get with @ref SetIMDBNumber changed values.
- std::string GetIMDBNumber() const { return m_cStructure->strIMDBNumber; }
+ std::string GetIMDBNumber() const
+ {
+ return m_cStructure->strIMDBNumber ? m_cStructure->strIMDBNumber : "";
+ }
/// @brief **optional**\n
/// Icon path.
@@ -229,7 +242,10 @@ public:
}
/// @brief To get with @ref SetIconPath changed values.
- std::string GetIconPath() const { return m_cStructure->strIconPath; }
+ std::string GetIconPath() const
+ {
+ return m_cStructure->strIconPath ? m_cStructure->strIconPath : "";
+ }
/// @brief **optional**\n
/// Genre type.
@@ -327,7 +343,10 @@ public:
}
/// @brief To get with @ref SetGenreDescription changed values.
- std::string GetGenreDescription() const { return m_cStructure->strGenreDescription; }
+ std::string GetGenreDescription() const
+ {
+ return m_cStructure->strGenreDescription ? m_cStructure->strGenreDescription : "";
+ }
/// @brief **optional**\n
/// First aired in UTC.
@@ -337,16 +356,22 @@ public:
}
/// @brief To get with @ref SetFirstAired changed values.
- std::string GetFirstAired() const { return m_cStructure->strFirstAired; }
+ std::string GetFirstAired() const
+ {
+ return m_cStructure->strFirstAired ? m_cStructure->strFirstAired : "";
+ }
/// @brief **optional**\n
/// Parental rating.
- void SetParentalRating(int parentalRating) { m_cStructure->iParentalRating = parentalRating; }
+ void SetParentalRating(unsigned int parentalRating)
+ {
+ m_cStructure->iParentalRating = parentalRating;
+ }
- /// @brief To get with @ref SetParentalRatinge changed values.
- int GetParentalRating() const { return m_cStructure->iParentalRating; }
+ /// @brief To get with @ref SetParentalRating changed values.
+ unsigned int GetParentalRating() const { return m_cStructure->iParentalRating; }
- /// @brief **required**\n
+ /// @brief **optional**\n
/// This event's parental rating code.
void SetParentalRatingCode(const std::string& parentalRatingCode)
{
@@ -354,7 +379,37 @@ public:
}
/// @brief To get with @ref SetParentalRatingCode changed values.
- std::string GetParentalRatingCode() const { return m_cStructure->strParentalRatingCode; }
+ std::string GetParentalRatingCode() const
+ {
+ return m_cStructure->strParentalRatingCode ? m_cStructure->strParentalRatingCode : "";
+ }
+
+ /// @brief **optional**\n
+ /// This event's parental rating icon.
+ void SetParentalRatingIcon(const std::string& parentalRatingIcon)
+ {
+ ReallocAndCopyString(&m_cStructure->strParentalRatingIcon, parentalRatingIcon.c_str());
+ }
+
+ /// @brief To get with @ref SetParentalRatingIcon changed values.
+ std::string GetParentalRatingIcon() const
+ {
+ return m_cStructure->strParentalRatingIcon ? m_cStructure->strParentalRatingIcon : "";
+ }
+
+ /// @brief **optional**\n
+ /// The event's parental rating source.
+ void SetParentalRatingSource(const std::string& parentalRatingSource)
+ {
+ //m_parentalRatingSource = parentalRatingSource;
+ ReallocAndCopyString(&m_cStructure->strParentalRatingSource, parentalRatingSource.c_str());
+ }
+
+ /// @brief To get with @ref SetParentalRatingSource changed values.
+ std::string GetParentalRatingSource() const
+ {
+ return m_cStructure->strParentalRatingSource ? m_cStructure->strParentalRatingSource : "";
+ }
/// @brief **optional**\n
/// Star rating.
@@ -395,7 +450,10 @@ public:
}
/// @brief To get with @ref SetEpisodeName changed values.
- std::string GetEpisodeName() const { return m_cStructure->strEpisodeName; }
+ std::string GetEpisodeName() const
+ {
+ return m_cStructure->strEpisodeName ? m_cStructure->strEpisodeName : "";
+ }
/// @brief **optional**\n
/// Bit field of independent flags associated with the EPG entry.
@@ -419,7 +477,10 @@ public:
}
/// @brief To get with @ref SetSeriesLink changed values.
- std::string GetSeriesLink() const { return m_cStructure->strSeriesLink; }
+ std::string GetSeriesLink() const
+ {
+ return m_cStructure->strSeriesLink ? m_cStructure->strSeriesLink : "";
+ }
///@}
@@ -436,6 +497,8 @@ public:
target->strIconPath = AllocAndCopyString(source->strIconPath);
target->strGenreDescription = AllocAndCopyString(source->strGenreDescription);
target->strParentalRatingCode = AllocAndCopyString(source->strParentalRatingCode);
+ target->strParentalRatingIcon = AllocAndCopyString(source->strParentalRatingIcon);
+ target->strParentalRatingSource = AllocAndCopyString(source->strParentalRatingSource);
target->strEpisodeName = AllocAndCopyString(source->strEpisodeName);
target->strSeriesLink = AllocAndCopyString(source->strSeriesLink);
target->strFirstAired = AllocAndCopyString(source->strFirstAired);
@@ -454,6 +517,8 @@ public:
FreeString(target->strIconPath);
FreeString(target->strGenreDescription);
FreeString(target->strParentalRatingCode);
+ FreeString(target->strParentalRatingIcon);
+ FreeString(target->strParentalRatingSource);
FreeString(target->strEpisodeName);
FreeString(target->strSeriesLink);
FreeString(target->strFirstAired);
diff --git a/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/pvr/General.h b/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/pvr/General.h
index dfb7cfa1ac..cfd072791e 100644
--- a/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/pvr/General.h
+++ b/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/pvr/General.h
@@ -23,8 +23,8 @@ namespace addon
//==============================================================================
/// @defgroup cpp_kodi_addon_pvr_Defs_PVRTypeIntValue class PVRTypeIntValue
/// @ingroup cpp_kodi_addon_pvr_Defs_General
-/// @brief **PVR add-on type value**\n
-/// Representation of a <b>`<int, std::string>`</b> event related value.
+/// @brief **PVR add-on int type value**\n
+/// Representation of a <b>`<int, std::string>`</b> value.
///
/// ----------------------------------------------------------------------------
///
@@ -55,8 +55,6 @@ public:
///@{
/// @brief Default class constructor.
- ///
- /// @note Values must be set afterwards.
PVRTypeIntValue() = default;
/// @brief Class constructor with integrated value set.
@@ -82,7 +80,10 @@ public:
}
/// @brief To get with the description text of the value.
- std::string GetDescription() const { return m_cStructure->strDescription; }
+ std::string GetDescription() const
+ {
+ return m_cStructure->strDescription ? m_cStructure->strDescription : "";
+ }
///@}
static PVR_ATTRIBUTE_INT_VALUE* AllocAndCopyData(const std::vector<PVRTypeIntValue>& source)
@@ -91,7 +92,7 @@ public:
for (unsigned int i = 0; i < source.size(); ++i)
{
values[i].iValue = source[i].GetCStructure()->iValue;
- AllocResources(source[i].GetCStructure(), &values[i]);
+ AllocResources(source[i].GetCStructure(), &values[i]); // handles strDescription
}
return values;
}
@@ -103,7 +104,7 @@ public:
for (unsigned int i = 0; i < size; ++i)
{
values[i].iValue = source[i].iValue;
- AllocResources(&source[i], &values[i]);
+ AllocResources(&source[i], &values[i]); // handles strDescription
}
return values;
}
@@ -147,6 +148,913 @@ private:
//------------------------------------------------------------------------------
//==============================================================================
+/// @defgroup cpp_kodi_addon_pvr_Defs_PVRTypeStringValue class PVRTypeStringValue
+/// @ingroup cpp_kodi_addon_pvr_Defs_General
+/// @brief **PVR add-on string type value**\n
+/// Representation of a <b>`<std::string, std::string>`</b> value.
+///
+/// ----------------------------------------------------------------------------
+///
+/// @copydetails cpp_kodi_addon_pvr_Defs_PVRTypeStringValue_Help
+///
+///@{
+class PVRTypeStringValue : public DynamicCStructHdl<PVRTypeStringValue, PVR_ATTRIBUTE_STRING_VALUE>
+{
+ friend class CInstancePVRClient;
+
+public:
+ /*! \cond PRIVATE */
+ PVRTypeStringValue(const PVRTypeStringValue& data) : DynamicCStructHdl(data) {}
+ /*! \endcond */
+
+ /// @defgroup cpp_kodi_addon_pvr_Defs_PVRTypeStringValue_Help Value Help
+ /// @ingroup cpp_kodi_addon_pvr_Defs_PVRTypeStringValue
+ ///
+ /// <b>The following table contains values that can be set with @ref cpp_kodi_addon_pvr_Defs_PVRTypeStringValue :</b>
+ /// | Name | Type | Set call | Get call
+ /// |------|------|----------|----------
+ /// | **Value** | `std::string` | @ref PVRTypeStringValue::SetValue "SetValue" | @ref PVRTypeStringValue::GetValue "GetValue"
+ /// | **Description** | `std::string` | @ref PVRTypeStringValue::SetDescription "SetDescription" | @ref PVRTypeStringValue::GetDescription "GetDescription"
+ ///
+ /// @remark Further can there be used his class constructor to set values.
+
+ /// @addtogroup cpp_kodi_addon_pvr_Defs_PVRTypeStringValue
+ ///@{
+
+ /// @brief Default class constructor.
+ PVRTypeStringValue() = default;
+
+ /// @brief Class constructor with integrated value set.
+ ///
+ /// @param[in] value Type identification value
+ /// @param[in] description Type description text
+ PVRTypeStringValue(const std::string& value, const std::string& description)
+ {
+ SetValue(value);
+ SetDescription(description);
+ }
+
+ /// @brief To set with the identification value.
+ void SetValue(const std::string& value)
+ {
+ ReallocAndCopyString(&m_cStructure->strValue, value.c_str());
+ }
+
+ /// @brief To get with the identification value.
+ std::string GetValue() const { return m_cStructure->strValue ? m_cStructure->strValue : ""; }
+
+ /// @brief To set with the description text of the value.
+ void SetDescription(const std::string& description)
+ {
+ ReallocAndCopyString(&m_cStructure->strDescription, description.c_str());
+ }
+
+ /// @brief To get with the description text of the value.
+ std::string GetDescription() const
+ {
+ return m_cStructure->strDescription ? m_cStructure->strDescription : "";
+ }
+ ///@}
+
+ static PVR_ATTRIBUTE_STRING_VALUE* AllocAndCopyData(const std::vector<PVRTypeStringValue>& source)
+ {
+ PVR_ATTRIBUTE_STRING_VALUE* values = new PVR_ATTRIBUTE_STRING_VALUE[source.size()]{};
+ for (unsigned int i = 0; i < source.size(); ++i)
+ AllocResources(source[i].GetCStructure(), &values[i]); // handles strValue, strDescription
+ return values;
+ }
+
+ static PVR_ATTRIBUTE_STRING_VALUE* AllocAndCopyData(const PVR_ATTRIBUTE_STRING_VALUE* source,
+ unsigned int size)
+ {
+ PVR_ATTRIBUTE_STRING_VALUE* values = new PVR_ATTRIBUTE_STRING_VALUE[size]{};
+ for (unsigned int i = 0; i < size; ++i)
+ AllocResources(&source[i], &values[i]); // handles strValue, strDescription
+ return values;
+ }
+
+ static void AllocResources(const PVR_ATTRIBUTE_STRING_VALUE* source,
+ PVR_ATTRIBUTE_STRING_VALUE* target)
+ {
+ target->strValue = AllocAndCopyString(source->strValue);
+ target->strDescription = AllocAndCopyString(source->strDescription);
+ }
+
+ static void FreeResources(PVR_ATTRIBUTE_STRING_VALUE* target)
+ {
+ FreeString(target->strValue);
+ target->strValue = nullptr;
+ FreeString(target->strDescription);
+ target->strDescription = nullptr;
+ }
+
+ static void FreeResources(PVR_ATTRIBUTE_STRING_VALUE* values, unsigned int size)
+ {
+ for (unsigned int i = 0; i < size; ++i)
+ FreeResources(&values[i]);
+ delete[] values;
+ }
+
+ static void ReallocAndCopyData(PVR_ATTRIBUTE_STRING_VALUE** source,
+ unsigned int* size,
+ const std::vector<PVRTypeStringValue>& values)
+ {
+ FreeResources(*source, *size);
+ *source = nullptr;
+ *size = values.size();
+ if (*size)
+ *source = AllocAndCopyData(values);
+ }
+
+private:
+ PVRTypeStringValue(const PVR_ATTRIBUTE_STRING_VALUE* data) : DynamicCStructHdl(data) {}
+ PVRTypeStringValue(PVR_ATTRIBUTE_STRING_VALUE* data) : DynamicCStructHdl(data) {}
+};
+///@}
+//------------------------------------------------------------------------------
+
+//==============================================================================
+/// @defgroup cpp_kodi_addon_pvr_Defs_PVRIntSettingDefinition class PVRIntSettingDefinition
+/// @ingroup cpp_kodi_addon_pvr_Defs_General
+/// @brief **PVR add-on integer setting definition**\n
+/// Representation of an integer setting definition.
+///
+/// ----------------------------------------------------------------------------
+///
+/// @copydetails cpp_kodi_addon_pvr_Defs_PVRIntSettingDefinition_Help
+///
+///@{
+class PVRIntSettingDefinition
+ : public DynamicCStructHdl<PVRIntSettingDefinition, PVR_INT_SETTING_DEFINITION>
+{
+ friend class CInstancePVRClient;
+
+public:
+ /*! \cond PRIVATE */
+ PVRIntSettingDefinition() { m_cStructure->iStep = 1; }
+ PVRIntSettingDefinition(const PVRIntSettingDefinition& def) : DynamicCStructHdl(def) {}
+ /*! \endcond */
+
+ /// @defgroup cpp_kodi_addon_pvr_Defs_General_PVRIntSettingDefinition_Help Value Help
+ /// @ingroup cpp_kodi_addon_pvr_Defs_General_PVRIntSettingDefinition
+ /// ----------------------------------------------------------------------------
+ ///
+ /// <b>The following table contains values that can be set with @ref cpp_kodi_addon_pvr_Defs_General_PVRIntSettingDefinition :</b>
+ /// | Name | Type | Set call | Get call | Usage
+ /// |------|------|----------|----------|-----------
+ /// | **Values** | @ref cpp_kodi_addon_pvr_Defs_PVRTypeIntValue "PVRTypeIntValue" | @ref PVRIntSettingDefinition::SetValues "SetValues" | @ref PVRIntSettingDefinition:GetValues "GetValues" | *optional*
+ /// | **Default value** | `int`| @ref PVRIntSettingDefinition::SetDefaultValue "SetDefaultValue" | @ref PVRIntSettingDefinition::GetDefaultValue "GetDefaultValue" | *optional*
+ /// | **Min value** | `int`| @ref PVRIntSettingDefinition::SetMinValue "SetMinValue" | @ref PVRIntSettingDefinition::GetMinValue "GetMinValue" | *optional*
+ /// | **Step** | `int`| @ref PVRIntSettingDefinition::SetStep "SetStep" | @ref PVRIntSettingDefinition::GetStep "GetStep" | *optional*
+ /// | **Max value** | `int`| @ref PVRIntSettingDefinition::SetMaxValue "SetMaxValue" | @ref PVRIntSettingDefinition::GetMaxValue "GetMaxValue" | *optional*
+ ///
+
+ /// @addtogroup cpp_kodi_addon_pvr_Defs_PVRIntSettingDefinition
+ ///@{
+
+ /// @brief Class constructor with integrated values.
+ ///
+ /// @param[in] settingValues possible setting values
+ /// @param[in] defaultValue default setting value
+ /// @param[in] minValue minimim setting value
+ /// @param[in] step amount to change values from min to max
+ /// @param[in] maxValue maximum setting value
+ PVRIntSettingDefinition(const std::vector<PVRTypeIntValue>& settingValues,
+ int defaultValue,
+ int minValue,
+ int step,
+ int maxValue)
+ {
+ SetValues(settingValues);
+ SetDefaultValue(defaultValue);
+ SetMinValue(minValue);
+ SetStep(step);
+ SetMaxValue(maxValue);
+ }
+
+ /// @brief **optional**\n
+ /// value definitions.
+ ///
+ /// Array containing the possible settings values. If left blank, any int value is accepted.
+ ///
+ /// @param[in] values List of possible values
+ /// @param[in] defaultValue [opt] The default value in list, can also be
+ /// set by @ref SetDefaultValue()
+ ///
+ /// --------------------------------------------------------------------------
+ ///
+ /// @copydetails cpp_kodi_addon_pvr_Defs_PVRTypeIntValue_Help
+ void SetValues(const std::vector<PVRTypeIntValue>& values, int defaultValue = -1)
+ {
+ PVRTypeIntValue::ReallocAndCopyData(&m_cStructure->values, &m_cStructure->iValuesSize, values);
+ if (defaultValue != -1)
+ m_cStructure->iDefaultValue = defaultValue;
+ }
+
+ /// @brief To get with @ref SetValues changed values.
+ std::vector<PVRTypeIntValue> GetValues() const
+ {
+ std::vector<PVRTypeIntValue> ret;
+ for (unsigned int i = 0; i < m_cStructure->iValuesSize; ++i)
+ ret.emplace_back(m_cStructure->values[i].iValue, m_cStructure->values[i].strDescription);
+ return ret;
+ }
+
+ /// @brief **optional**\n
+ /// The default value for this setting.
+ void SetDefaultValue(int defaultValue) { m_cStructure->iDefaultValue = defaultValue; }
+
+ /// @brief To get with @ref SetDefaultValue changed values.
+ int GetDefaultValue() const { return m_cStructure->iDefaultValue; }
+
+ /// @brief **optional**\n
+ /// The minimum value for this setting.
+ void SetMinValue(int minValue) { m_cStructure->iMinValue = minValue; }
+
+ /// @brief To get with @ref SetMinValue changed values.
+ int GetMinValue() const { return m_cStructure->iMinValue; }
+
+ /// @brief **optional**\n
+ /// The amount for increasing the values for this setting from min to max.
+ void SetStep(int step) { m_cStructure->iStep = step; }
+
+ /// @brief To get with @ref SetStep changed values.
+ int GetStep() const { return m_cStructure->iStep; }
+
+ /// @brief **optional**\n
+ /// The maximum value for this setting.
+ void SetMaxValue(int maxValue) { m_cStructure->iMaxValue = maxValue; }
+
+ /// @brief To get with @ref SetMaxValue changed values.
+ int GetMaxValue() const { return m_cStructure->iMaxValue; }
+ ///@}
+
+ static PVR_INT_SETTING_DEFINITION* AllocAndCopyData(const PVRIntSettingDefinition& source)
+ {
+ PVR_INT_SETTING_DEFINITION* def = new PVR_INT_SETTING_DEFINITION{};
+ AllocResources(source.GetCStructure(), def); // handles values, iValuesSize
+ def->iDefaultValue = source.GetCStructure()->iDefaultValue;
+ def->iMinValue = source.GetCStructure()->iMinValue;
+ def->iStep = source.GetCStructure()->iStep;
+ def->iMaxValue = source.GetCStructure()->iMaxValue;
+ return def;
+ }
+
+ static PVR_INT_SETTING_DEFINITION* AllocAndCopyData(PVR_INT_SETTING_DEFINITION* source)
+ {
+ PVR_INT_SETTING_DEFINITION* def = new PVR_INT_SETTING_DEFINITION{};
+ AllocResources(source, def); // handles values, iValuesSize
+ def->iDefaultValue = source->iDefaultValue;
+ def->iMinValue = source->iMinValue;
+ def->iStep = source->iStep;
+ def->iMaxValue = source->iMaxValue;
+ return def;
+ }
+
+ static void AllocResources(const PVR_INT_SETTING_DEFINITION* source,
+ PVR_INT_SETTING_DEFINITION* target)
+ {
+ target->values = PVRTypeIntValue::AllocAndCopyData(source->values, source->iValuesSize);
+ target->iValuesSize = source->iValuesSize;
+ }
+
+ static void FreeResources(PVR_INT_SETTING_DEFINITION* target)
+ {
+ PVRTypeIntValue::FreeResources(target->values, target->iValuesSize);
+ target->values = nullptr;
+ target->iValuesSize = 0;
+ }
+
+ static void ReallocAndCopyData(PVR_INT_SETTING_DEFINITION** source,
+ const PVRIntSettingDefinition& def)
+ {
+ if (*source)
+ FreeResources(*source);
+ *source = AllocAndCopyData(def);
+ }
+
+private:
+ PVRIntSettingDefinition(const PVR_INT_SETTING_DEFINITION* def) : DynamicCStructHdl(def) {}
+ PVRIntSettingDefinition(PVR_INT_SETTING_DEFINITION* def) : DynamicCStructHdl(def) {}
+};
+///@}
+//------------------------------------------------------------------------------
+
+//==============================================================================
+/// @defgroup cpp_kodi_addon_pvr_Defs_PVRStringSettingDefinition class PVRStringSettingDefinition
+/// @ingroup cpp_kodi_addon_pvr_Defs_General
+/// @brief **PVR add-on string setting definition**\n
+/// Representation of a string setting definition.
+///
+/// ----------------------------------------------------------------------------
+///
+/// @copydetails cpp_kodi_addon_pvr_Defs_PVRStringSettingDefinition_Help
+///
+///@{
+class PVRStringSettingDefinition
+ : public DynamicCStructHdl<PVRStringSettingDefinition, PVR_STRING_SETTING_DEFINITION>
+{
+ friend class CInstancePVRClient;
+
+public:
+ /*! \cond PRIVATE */
+ PVRStringSettingDefinition() { m_cStructure->bAllowEmptyValue = true; }
+ PVRStringSettingDefinition(const PVRStringSettingDefinition& def) : DynamicCStructHdl(def) {}
+ /*! \endcond */
+
+ /// @defgroup cpp_kodi_addon_pvr_Defs_General_PVRStringSettingDefinition_Help Value Help
+ /// @ingroup cpp_kodi_addon_pvr_Defs_General_PVRStringSettingDefinition
+ /// ----------------------------------------------------------------------------
+ ///
+ /// <b>The following table contains values that can be set with @ref cpp_kodi_addon_pvr_Defs_General_PVRStringSettingDefinition :</b>
+ /// | Name | Type | Set call | Get call | Usage
+ /// |------|------|----------|----------|-----------
+ /// | **Values** | @ref cpp_kodi_addon_pvr_Defs_PVRTypeStringValue "PVRTypeStringValue" | @ref PVRStringSettingDefinition::SetValues "SetValues" | @ref PVRStringSettingDefinition:GetValues "GetValues" | *optional*
+ /// | **Default value** | `std::string`| @ref PVRStringSettingDefinition::SetDefaultValue "SetDefaultValue" | @ref PVRStringSettingDefinition::GetDefaultValue "GetDefaultValue" | *optional*
+ /// | **Allow empty value** | `bool`| @ref PVRStringSettingDefinition::SetAllowEmptyValue "SetAllowEmptyValue" | @ref PVRStringSettingDefinition::GetetAllowEmptyValue "GetetAllowEmptyValue" | *optional*
+ ///
+
+ /// @addtogroup cpp_kodi_addon_pvr_Defs_PVRStringSettingDefinition
+ ///@{
+
+ /// @brief Class constructor with integrated values.
+ ///
+ /// @param[in] settingValues possible setting values
+ /// @param[in] defaultValue default setting value
+ /// @param[in] allowEmptyValues allow empty values flag
+ PVRStringSettingDefinition(const std::vector<PVRTypeStringValue>& settingValues,
+ const std::string& defaultValue,
+ bool allowEmptyValue)
+ {
+ SetValues(settingValues);
+ SetDefaultValue(defaultValue);
+ SetAllowEmptyValue(allowEmptyValue);
+ }
+
+ /// @brief **optional**\n
+ /// value definitions.
+ ///
+ /// Array containing the possible settings values. If left blank, any string value is accepted.
+ ///
+ /// @param[in] values List of possible values
+ /// @param[in] defaultValue [opt] The default value in list, can also be
+ /// set by @ref SetDefaultValue()
+ ///
+ /// --------------------------------------------------------------------------
+ ///
+ /// @copydetails cpp_kodi_addon_pvr_Defs_PVRTypeStringValue_Help
+ void SetValues(const std::vector<PVRTypeStringValue>& values,
+ const std::string& defaultValue = "")
+ {
+ PVRTypeStringValue::ReallocAndCopyData(&m_cStructure->values, &m_cStructure->iValuesSize,
+ values);
+ ReallocAndCopyString(&m_cStructure->strDefaultValue, defaultValue.c_str());
+ }
+
+ /// @brief To get with @ref SetValues changed values.
+ std::vector<PVRTypeStringValue> GetValues() const
+ {
+ std::vector<PVRTypeStringValue> ret;
+ for (unsigned int i = 0; i < m_cStructure->iValuesSize; ++i)
+ ret.emplace_back(m_cStructure->values[i].strValue, m_cStructure->values[i].strDescription);
+ return ret;
+ }
+
+ /// @brief **optional**\n
+ /// The default value for this setting.
+ void SetDefaultValue(const std::string& defaultValue)
+ {
+ ReallocAndCopyString(&m_cStructure->strDefaultValue, defaultValue.c_str());
+ }
+
+ /// @brief To get with @ref SetDefaultValue changed values.
+ std::string GetDefaultValue() const
+ {
+ return m_cStructure->strDefaultValue ? m_cStructure->strDefaultValue : "";
+ }
+
+ /// @brief **optional**\n
+ /// The allow empty values flag for this setting.
+ void SetAllowEmptyValue(bool allowEmptyValue)
+ {
+ m_cStructure->bAllowEmptyValue = allowEmptyValue;
+ }
+
+ /// @brief To get with @ref SetAllowEmptyValue changed values.
+ bool GetAllowEmptyValue() const { return m_cStructure->bAllowEmptyValue; }
+ ///@}
+
+ static PVR_STRING_SETTING_DEFINITION* AllocAndCopyData(const PVRStringSettingDefinition& source)
+ {
+ PVR_STRING_SETTING_DEFINITION* def = new PVR_STRING_SETTING_DEFINITION{};
+ AllocResources(source.GetCStructure(), def); // handles strDefaultValue, values, iValuesSize
+ def->bAllowEmptyValue = source.GetCStructure()->bAllowEmptyValue;
+ return def;
+ }
+
+ static PVR_STRING_SETTING_DEFINITION* AllocAndCopyData(PVR_STRING_SETTING_DEFINITION* source)
+ {
+ PVR_STRING_SETTING_DEFINITION* def = new PVR_STRING_SETTING_DEFINITION{};
+ AllocResources(source, def); // handles strDefaultValue, values, iValuesSize
+ def->bAllowEmptyValue = source->bAllowEmptyValue;
+ return def;
+ }
+
+ static void AllocResources(const PVR_STRING_SETTING_DEFINITION* source,
+ PVR_STRING_SETTING_DEFINITION* target)
+ {
+ target->strDefaultValue = AllocAndCopyString(source->strDefaultValue);
+ target->values = PVRTypeStringValue::AllocAndCopyData(source->values, source->iValuesSize);
+ target->iValuesSize = source->iValuesSize;
+ }
+
+ static void FreeResources(PVR_STRING_SETTING_DEFINITION* target)
+ {
+ PVRTypeStringValue::FreeResources(target->values, target->iValuesSize);
+ target->values = nullptr;
+ target->iValuesSize = 0;
+
+ FreeString(target->strDefaultValue);
+ target->strDefaultValue = nullptr;
+ }
+
+ static void ReallocAndCopyData(PVR_STRING_SETTING_DEFINITION** source,
+ const PVRStringSettingDefinition& def)
+ {
+ if (*source)
+ FreeResources(*source);
+ *source = AllocAndCopyData(def);
+ }
+
+private:
+ PVRStringSettingDefinition(const PVR_STRING_SETTING_DEFINITION* def) : DynamicCStructHdl(def) {}
+ PVRStringSettingDefinition(PVR_STRING_SETTING_DEFINITION* def) : DynamicCStructHdl(def) {}
+};
+///@}
+//------------------------------------------------------------------------------
+
+//==============================================================================
+/// @defgroup cpp_kodi_addon_pvr_Defs_PVRSettingDefinition class PVRSettingDefinition
+/// @ingroup cpp_kodi_addon_pvr_Defs_General
+/// @brief **PVR add-on setting definition**\n
+/// Representation of a setting definition.
+///
+/// ----------------------------------------------------------------------------
+///
+/// @copydetails cpp_kodi_addon_pvr_Defs_PVRSettingDefinition_Help
+///
+///@{
+class PVRSettingDefinition : public DynamicCStructHdl<PVRSettingDefinition, PVR_SETTING_DEFINITION>
+{
+ friend class CInstancePVRClient;
+
+public:
+ /*! \cond PRIVATE */
+ PVRSettingDefinition() = default;
+ PVRSettingDefinition(const PVRSettingDefinition& type) : DynamicCStructHdl(type) {}
+ /*! \endcond */
+
+ /// @defgroup cpp_kodi_addon_pvr_Defs_General_PVRSettingDefinition_Help Value Help
+ /// @ingroup cpp_kodi_addon_pvr_Defs_General_PVRSettingDefinition
+ /// ----------------------------------------------------------------------------
+ ///
+ /// <b>The following table contains values that can be set with @ref cpp_kodi_addon_pvr_Defs_General_PVRSettingDefinition :</b>
+ /// | Name | Type | Set call | Get call | Usage
+ /// |------|------|----------|----------|-----------
+ /// | **Identifier** | `unsigned int` | @ref PVRSettingDefinition::SetId "SetId" | @ref PVRSettingDefinition::GetId "GetId" | *required to set*
+ /// | **Name** | `std::string` | @ref PVRSettingDefinition::SetName "SetName" | @ref PVRSettingDefinition::GetName "GetName" | *required to set*
+ /// | **Type** | @ref PVR_SETTING_TYPE | @ref PVRSettingDefinition::SetType "SetType" | @ref PVRSettingDefinition:GetType "GetType" | *required to set*
+ /// | | | | | |
+ /// | **Read-only conditions** | `uint64_t`| @ref PVRSettingDefinition::SetReadonlyConditions "SetReadonlyConditions" | @ref PVRSettingDefinition::GetReadonlyConditions "GetReadonlyConditions" | *optional*
+ /// | | | | | |
+ /// | **Int Definition** | @ref cpp_kodi_addon_pvr_Defs_PVRIntSettingDefinition "PVRIntSettingDefinition" | @ref PVRSettingDefinition::SetIntDefinition "SetIntDefinition" | @ref PVRSettingDefinition:GetIntDefinition "GetIntDefinition" | *optional*
+ /// | **String Definition** | @ref cpp_kodi_addon_pvr_Defs_PVRStringSettingDefinition "PVRStringSettingDefinition" | @ref PVRSettingDefinition::SetStringValues "SetStringDefinition" | @ref PVRSettingDefinition:GetStringDefinition "GetStringDefinition" | *optional*
+ ///
+
+ /// @addtogroup cpp_kodi_addon_pvr_Defs_PVRSettingDefinition
+ ///@{
+
+ /// @brief Class constructor with integrated values.
+ ///
+ /// @param[in] settingDefId Setting definition identification value
+ /// @param[in] settingDefName Setting definition name
+ /// @param[in] readonlyConditions readonly conditions value
+ /// @param[in] settingDef int setting definition
+ PVRSettingDefinition(unsigned int settingDefId,
+ const std::string& settingDefName,
+ uint64_t readonlyConditions,
+ const PVRIntSettingDefinition& settingDef)
+ {
+ SetId(settingDefId);
+ SetName(settingDefName);
+ SetType(PVR_SETTING_TYPE::INTEGER);
+ SetReadonlyConditions(readonlyConditions);
+ SetIntDefinition(settingDef);
+ }
+
+ /// @brief Class constructor with integrated values.
+ ///
+ /// @param[in] settingDefId Setting definition identification value
+ /// @param[in] settingDefName Setting definition name
+ /// @param[in] readonlyConditions readonly conditions value
+ /// @param[in] settingDef string setting definition
+ PVRSettingDefinition(unsigned int settingDefId,
+ const std::string& settingDefName,
+ uint64_t readonlyConditions,
+ const PVRStringSettingDefinition& settingDef)
+ {
+ SetId(settingDefId);
+ SetName(settingDefName);
+ SetType(PVR_SETTING_TYPE::STRING);
+ SetReadonlyConditions(readonlyConditions);
+ SetStringDefinition(settingDef);
+ }
+
+ /// @brief Class constructor with integrated values.
+ ///
+ /// @param[in] settingDefId Setting definition identification value
+ /// @param[in] settingDefName Setting definition name
+ /// @param[in] eType Setting type
+ /// @param[in] readonlyConditions readonly conditions value
+ /// @param[in] intSettingDef int setting definition
+ /// @param[in] stringSettingDef string setting definition
+ PVRSettingDefinition(unsigned int settingDefId,
+ const std::string& settingDefName,
+ PVR_SETTING_TYPE eType,
+ uint64_t readonlyConditions,
+ const PVRIntSettingDefinition& intSettingDef,
+ const PVRStringSettingDefinition& stringSettingDef)
+ {
+ SetId(settingDefId);
+ SetName(settingDefName);
+ SetType(eType);
+ SetReadonlyConditions(readonlyConditions);
+ SetIntDefinition(intSettingDef);
+ SetStringDefinition(stringSettingDef);
+ }
+
+ /// @brief **required**\n
+ /// This setting definition's identifier.
+ void SetId(unsigned int defId) { m_cStructure->iId = defId; }
+
+ /// @brief To get with @ref SetId changed values.
+ unsigned int GetId() const { return m_cStructure->iId; }
+
+ /// @brief **required**\n
+ /// A short localized string with the name of the setting.
+ void SetName(const std::string& name)
+ {
+ ReallocAndCopyString(&m_cStructure->strName, name.c_str());
+ }
+
+ /// @brief To get with @ref SetName changed values.
+ std::string GetName() const { return m_cStructure->strName ? m_cStructure->strName : ""; }
+
+ /// @brief **required**\n
+ /// This setting definition's identifier.
+ void SetType(PVR_SETTING_TYPE eType) { m_cStructure->eType = eType; }
+
+ /// @brief To get with @ref SetType changed values.
+ PVR_SETTING_TYPE GetType() const { return m_cStructure->eType; }
+
+ /// @brief **optional**\n
+ /// The read-only conditions value for this setting.
+ /// @ref cpp_kodi_addon_pvr_Defs_General_PVR_SETTING_READONLY_CONDITION "PVR_SETTING_READONLY_CONDITION_*" enum values
+ void SetReadonlyConditions(uint64_t conditions)
+ {
+ m_cStructure->iReadonlyConditions = conditions;
+ }
+
+ /// @brief To get with @ref SetReadonlyConditions changed values.
+ uint64_t GetReadonlyConditions() const { return m_cStructure->iReadonlyConditions; }
+
+ //----------------------------------------------------------------------------
+
+ /// @brief **optional**\n
+ /// @param[in] def integer setting definition
+ ///
+ /// --------------------------------------------------------------------------
+ ///
+ /// @copydetails cpp_kodi_addon_pvr_Defs_PVRIntSettingDefinition_Help
+ void SetIntDefinition(const PVRIntSettingDefinition& def)
+ {
+ PVRIntSettingDefinition::ReallocAndCopyData(&m_cStructure->intSettingDefinition, def);
+ }
+
+ /// @brief To get with @ref SetIntDefinition changed values.
+ PVRIntSettingDefinition GetIntDefinition() const
+ {
+ PVRIntSettingDefinition ret;
+ if (m_cStructure->intSettingDefinition)
+ {
+ std::vector<PVRTypeIntValue> settingValues;
+ settingValues.reserve(m_cStructure->intSettingDefinition->iValuesSize);
+ for (unsigned int i = 0; i < m_cStructure->intSettingDefinition->iValuesSize; ++i)
+ {
+ settingValues.emplace_back(m_cStructure->intSettingDefinition->values[i].iValue,
+ m_cStructure->intSettingDefinition->values[i].strDescription);
+ }
+ ret.SetValues(std::move(settingValues));
+ ret.SetDefaultValue(m_cStructure->intSettingDefinition->iDefaultValue);
+ ret.SetMinValue(m_cStructure->intSettingDefinition->iMinValue);
+ ret.SetStep(m_cStructure->intSettingDefinition->iStep);
+ ret.SetMaxValue(m_cStructure->intSettingDefinition->iMaxValue);
+ }
+ return ret;
+ }
+
+ //----------------------------------------------------------------------------
+
+ /// @brief **optional**\n
+ /// @param[in] def string setting definition
+ ///
+ /// --------------------------------------------------------------------------
+ ///
+ /// @copydetails cpp_kodi_addon_pvr_Defs_PVRStringSettingDefinition_Help
+ void SetStringDefinition(const PVRStringSettingDefinition& def)
+ {
+ PVRStringSettingDefinition::ReallocAndCopyData(&m_cStructure->stringSettingDefinition, def);
+ }
+
+ /// @brief To get with @ref SetStringDefinition changed values.
+ PVRStringSettingDefinition GetStringDefinition() const
+ {
+ PVRStringSettingDefinition ret;
+ if (m_cStructure->stringSettingDefinition)
+ {
+ std::vector<PVRTypeStringValue> settingValues;
+ settingValues.reserve(m_cStructure->stringSettingDefinition->iValuesSize);
+ for (unsigned int i = 0; i < m_cStructure->stringSettingDefinition->iValuesSize; ++i)
+ {
+ settingValues.emplace_back(m_cStructure->stringSettingDefinition->values[i].strValue,
+ m_cStructure->stringSettingDefinition->values[i].strDescription);
+ }
+ ret.SetValues(std::move(settingValues));
+ ret.SetDefaultValue(m_cStructure->stringSettingDefinition->strDefaultValue);
+ }
+ return ret;
+ }
+ ///@}
+
+ static PVR_SETTING_DEFINITION** AllocAndCopyData(const std::vector<PVRSettingDefinition>& source)
+ {
+ PVR_SETTING_DEFINITION** defs = new PVR_SETTING_DEFINITION* [source.size()] {};
+ for (unsigned int i = 0; i < source.size(); ++i)
+ {
+ defs[i] = new PVR_SETTING_DEFINITION{};
+ defs[i]->iId = source[i].GetCStructure()->iId;
+ defs[i]->eType = source[i].GetCStructure()->eType;
+ defs[i]->iReadonlyConditions = source[i].GetCStructure()->iReadonlyConditions;
+ AllocResources(source[i].GetCStructure(),
+ defs[i]); // handles strName, intSettingDefinition, stringSettingDefinition
+ }
+ return defs;
+ }
+
+ static PVR_SETTING_DEFINITION** AllocAndCopyData(PVR_SETTING_DEFINITION** source,
+ unsigned int size)
+ {
+ PVR_SETTING_DEFINITION** defs = new PVR_SETTING_DEFINITION* [size] {};
+ for (unsigned int i = 0; i < size; ++i)
+ {
+ defs[i] = new PVR_SETTING_DEFINITION{};
+ defs[i]->iId = source[i]->iId;
+ defs[i]->eType = source[i]->eType;
+ defs[i]->iReadonlyConditions = source[i]->iReadonlyConditions;
+ AllocResources(source[i],
+ defs[i]); // handles strName, intSettingDefinition, stringSettingDefinition
+ }
+ return defs;
+ }
+
+ static void AllocResources(const PVR_SETTING_DEFINITION* source, PVR_SETTING_DEFINITION* target)
+ {
+ target->strName = AllocAndCopyString(source->strName);
+ if (source->intSettingDefinition)
+ target->intSettingDefinition =
+ PVRIntSettingDefinition::AllocAndCopyData(source->intSettingDefinition);
+ if (source->stringSettingDefinition)
+ target->stringSettingDefinition =
+ PVRStringSettingDefinition::AllocAndCopyData(source->stringSettingDefinition);
+ }
+
+ static void FreeResources(PVR_SETTING_DEFINITION* target)
+ {
+ FreeString(target->strName);
+ target->strName = nullptr;
+
+ if (target->intSettingDefinition)
+ {
+ PVRIntSettingDefinition::FreeResources(target->intSettingDefinition);
+ target->intSettingDefinition = nullptr;
+ }
+
+ if (target->stringSettingDefinition)
+ {
+ PVRStringSettingDefinition::FreeResources(target->stringSettingDefinition);
+ target->stringSettingDefinition = nullptr;
+ }
+ }
+
+ static void FreeResources(PVR_SETTING_DEFINITION** defs, unsigned int size)
+ {
+ for (unsigned int i = 0; i < size; ++i)
+ {
+ FreeResources(defs[i]);
+ delete defs[i];
+ }
+ delete[] defs;
+ }
+
+ static void ReallocAndCopyData(PVR_SETTING_DEFINITION*** source,
+ unsigned int* size,
+ const std::vector<PVRSettingDefinition>& defs)
+ {
+ FreeResources(*source, *size);
+ *source = nullptr;
+ *size = defs.size();
+ if (*size)
+ *source = AllocAndCopyData(defs);
+ }
+
+private:
+ PVRSettingDefinition(const PVR_SETTING_DEFINITION* def) : DynamicCStructHdl(def) {}
+ PVRSettingDefinition(PVR_SETTING_DEFINITION* def) : DynamicCStructHdl(def) {}
+};
+///@}
+//------------------------------------------------------------------------------
+
+//==============================================================================
+/// @defgroup cpp_kodi_addon_pvr_Defs_General_PVRSettingKeyValuePair class PVRSettingKeyValuePair
+/// @ingroup cpp_kodi_addon_pvr_Defs_General
+/// @brief **Key-value pair of two ints**\n
+/// To hold a pair of two ints.
+///
+/// ----------------------------------------------------------------------------
+///
+/// @copydetails cpp_kodi_addon_pvr_Defs_General_PVRSettingKeyValuePair_Help
+///
+///@{
+class PVRSettingKeyValuePair
+ : public DynamicCStructHdl<PVRSettingKeyValuePair, PVR_SETTING_KEY_VALUE_PAIR>
+{
+ friend class CInstancePVRClient;
+
+public:
+ /*! \cond PRIVATE */
+ PVRSettingKeyValuePair(const PVRSettingKeyValuePair& pair) : DynamicCStructHdl(pair) {}
+ /*! \endcond */
+
+ /// @defgroup cpp_kodi_addon_pvr_Defs_General_PVRSettingKeyValuePair_Help Value Help
+ /// @ingroup cpp_kodi_addon_pvr_Defs_General_PVRSettingKeyValuePair
+ ///
+ /// <b>The following table contains values that can be set with @ref cpp_kodi_addon_pvr_Defs_General_PVRSettingKeyValuePair :</b>
+ /// | Name | Type | Set call | Get call
+ /// |------|------|----------|----------
+ /// | **Key** | `unsigned int` | @ref PVRSettingKeyValuePair::SetKey "SetKey" | @ref PVRSettingKeyValuePair::GetKey "GetKey"
+ /// | **Type** | @ref PVR_SETTING_TYPE | @ref PVRSettingKeyValuePair::SetType "SetType" | @ref PVRSettingKeyValuePair:GetType "GetType" | *required to set*
+ /// | **Int Value** | `int` | @ref PVRSettingKeyValuePair::SetIntValue "SetIntValue" | @ref PVRSettingKeyValuePair::GetIntValue "GetIntValue"
+ /// | **String Value** | `std::string` | @ref PVRSettingKeyValuePair::SetStringValue "SetStringValue" | @ref PVRSettingKeyValuePair::GetStringValue "GetStringValue"
+
+ /// @addtogroup cpp_kodi_addon_pvr_Defs_General_PVRSettingKeyValuePair
+ ///@{
+
+ /// @brief Default class constructor.
+ PVRSettingKeyValuePair() = default;
+
+ /// @brief Class constructor with integrated value set.
+ ///
+ /// @param[in] key The key
+ /// @param[in] value The value
+ PVRSettingKeyValuePair(unsigned int key, int value)
+ {
+ SetKey(key);
+ SetType(PVR_SETTING_TYPE::INTEGER);
+ SetIntValue(value);
+ }
+
+ /// @brief Class constructor with integrated value set.
+ ///
+ /// @param[in] key The key
+ /// @param[in] value The value
+ PVRSettingKeyValuePair(unsigned int key, const std::string& value)
+ {
+ SetKey(key);
+ SetType(PVR_SETTING_TYPE::STRING);
+ SetStringValue(value);
+ }
+
+ /// @brief Class constructor with integrated value set.
+ ///
+ /// @param[in] key The key
+ /// @param[in] eType The type
+ /// @param[in] intValue The int value
+ /// @param[in] stringValue The string value
+ PVRSettingKeyValuePair(unsigned int key,
+ PVR_SETTING_TYPE eType,
+ int intValue,
+ const std::string& stringValue)
+ {
+ SetKey(key);
+ SetType(eType);
+ SetIntValue(intValue);
+ SetStringValue(stringValue);
+ }
+
+ /// @brief To set with the key.
+ void SetKey(unsigned int key) { m_cStructure->iKey = key; }
+
+ /// @brief To get with the key.
+ unsigned GetKey() const { return m_cStructure->iKey; }
+
+ /// @brief **required**\n
+ /// This key value pair's type.
+ void SetType(PVR_SETTING_TYPE eType) { m_cStructure->eType = eType; }
+
+ /// @brief To get with @ref SetType changed values.
+ PVR_SETTING_TYPE GetType() const { return m_cStructure->eType; }
+
+ /// @brief To set with the value.
+ void SetIntValue(int value) { m_cStructure->iValue = value; }
+
+ /// @brief To get with the value.
+ int GetIntValue() const { return m_cStructure->iValue; }
+
+ /// @brief To set with the value.
+ void SetStringValue(const std::string& value)
+ {
+ ReallocAndCopyString(&m_cStructure->strValue, value.c_str());
+ }
+
+ /// @brief To get with the value.
+ std::string GetStringValue() const
+ {
+ return m_cStructure->strValue ? m_cStructure->strValue : "";
+ }
+ ///@}
+
+ static PVR_SETTING_KEY_VALUE_PAIR* AllocAndCopyData(
+ const std::vector<PVRSettingKeyValuePair>& values)
+ {
+ PVR_SETTING_KEY_VALUE_PAIR* pairs = new PVR_SETTING_KEY_VALUE_PAIR[values.size()]{};
+ for (unsigned int i = 0; i < values.size(); ++i)
+ {
+ pairs[i].iKey = values[i].GetCStructure()->iKey;
+ pairs[i].eType = values[i].GetCStructure()->eType;
+ pairs[i].iValue = values[i].GetCStructure()->iValue;
+ AllocResources(values[i].GetCStructure(), &pairs[i]); // handles strValue
+ }
+ return pairs;
+ }
+
+ static PVR_SETTING_KEY_VALUE_PAIR* AllocAndCopyData(const PVR_SETTING_KEY_VALUE_PAIR* source,
+ unsigned int size)
+ {
+ PVR_SETTING_KEY_VALUE_PAIR* pairs = new PVR_SETTING_KEY_VALUE_PAIR[size]{};
+ for (unsigned int i = 0; i < size; ++i)
+ {
+ pairs[i].iKey = source[i].iKey;
+ pairs[i].eType = source[i].eType;
+ pairs[i].iValue = source[i].iValue;
+ AllocResources(&source[i], &pairs[i]); // handles strValue
+ }
+ return pairs;
+ }
+
+ static void AllocResources(const PVR_SETTING_KEY_VALUE_PAIR* source,
+ PVR_SETTING_KEY_VALUE_PAIR* target)
+ {
+ target->strValue = AllocAndCopyString(source->strValue);
+ }
+
+ static void FreeResources(PVR_SETTING_KEY_VALUE_PAIR* target) { FreeString(target->strValue); }
+
+ static void FreeResources(PVR_SETTING_KEY_VALUE_PAIR* pairs, unsigned int size)
+ {
+ for (unsigned int i = 0; i < size; ++i)
+ FreeResources(&pairs[i]);
+ delete[] pairs;
+ }
+
+ static void ReallocAndCopyData(PVR_SETTING_KEY_VALUE_PAIR** source,
+ unsigned int* size,
+ const std::vector<PVRSettingKeyValuePair>& values)
+ {
+ FreeResources(*source, *size);
+ *source = nullptr;
+ *size = values.size();
+ if (*size)
+ *source = AllocAndCopyData(values);
+ }
+
+private:
+ PVRSettingKeyValuePair(const PVR_SETTING_KEY_VALUE_PAIR* pair) : DynamicCStructHdl(pair) {}
+ PVRSettingKeyValuePair(PVR_SETTING_KEY_VALUE_PAIR* pair) : DynamicCStructHdl(pair) {}
+};
+///@}
+//------------------------------------------------------------------------------
+
+//==============================================================================
/// @defgroup cpp_kodi_addon_pvr_Defs_PVRCapabilities class PVRCapabilities
/// @ingroup cpp_kodi_addon_pvr_Defs_General
/// @brief **PVR add-on capabilities**\n
@@ -171,7 +1079,7 @@ class PVRCapabilities : public DynamicCStructHdl<PVRCapabilities, PVR_ADDON_CAPA
public:
/*! \cond PRIVATE */
- PVRCapabilities() { memset(m_cStructure, 0, sizeof(PVR_ADDON_CAPABILITIES)); }
+ PVRCapabilities() = default;
PVRCapabilities(const PVRCapabilities& capabilities) : DynamicCStructHdl(capabilities) {}
/*! \endcond */
@@ -496,6 +1404,7 @@ private:
/// ...
///
/// PVR_ERROR CMyPVRInstance::GetChannelStreamProperties(const kodi::addon::PVRChannel& channel,
+/// PVR_SOURCE source,
/// std::vector<kodi::addon::PVRStreamProperty>& properties)
/// {
/// ...
@@ -512,6 +1421,7 @@ private:
/// ...
///
/// PVR_ERROR CMyPVRInstance::GetChannelStreamProperties(const kodi::addon::PVRChannel& channel,
+/// PVR_SOURCE source,
/// std::vector<kodi::addon::PVRStreamProperty>& properties)
/// {
/// ...
@@ -550,8 +1460,6 @@ public:
///@{
/// @brief Default class constructor.
- ///
- /// @note Values must be set afterwards.
PVRStreamProperty() = default;
/// @brief Class constructor with integrated value set.
@@ -571,7 +1479,7 @@ public:
}
/// @brief To get with the identification name.
- std::string GetName() const { return m_cStructure->strName; }
+ std::string GetName() const { return m_cStructure->strName ? m_cStructure->strName : ""; }
/// @brief To set with the used property value.
void SetValue(const std::string& value)
@@ -580,7 +1488,7 @@ public:
}
/// @brief To get with the used property value.
- std::string GetValue() const { return m_cStructure->strValue; }
+ std::string GetValue() const { return m_cStructure->strValue ? m_cStructure->strValue : ""; }
///@}
static void AllocResources(const PVR_NAMED_VALUE* source, PVR_NAMED_VALUE* target)
diff --git a/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/pvr/Providers.h b/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/pvr/Providers.h
index a76f44ca9f..508eb2d294 100644
--- a/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/pvr/Providers.h
+++ b/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/pvr/Providers.h
@@ -42,7 +42,7 @@ class PVRProvider : public DynamicCStructHdl<PVRProvider, PVR_PROVIDER>
public:
/*! \cond PRIVATE */
- PVRProvider() { memset(m_cStructure, 0, sizeof(PVR_PROVIDER)); }
+ PVRProvider() = default;
PVRProvider(const PVRProvider& provider) : DynamicCStructHdl(provider) {}
/*! \endcond */
@@ -78,7 +78,7 @@ public:
}
/// @brief To get with @ref SetName changed values.
- std::string GetName() const { return m_cStructure->strName; }
+ std::string GetName() const { return m_cStructure->strName ? m_cStructure->strName : ""; }
/// @brief **optional**\n
/// Provider type.
@@ -107,7 +107,10 @@ public:
}
/// @brief To get with @ref SetIconPath changed values.
- std::string GetIconPath() const { return m_cStructure->strIconPath; }
+ std::string GetIconPath() const
+ {
+ return m_cStructure->strIconPath ? m_cStructure->strIconPath : "";
+ }
///@}
/// @brief **optional**\n
@@ -124,7 +127,10 @@ public:
/// @brief To get with @ref SetCountries changed values.
std::vector<std::string> GetCountries() const
{
- return tools::StringUtils::Split(m_cStructure->strCountries, PROVIDER_STRING_TOKEN_SEPARATOR);
+ if (m_cStructure->strCountries)
+ return tools::StringUtils::Split(m_cStructure->strCountries, PROVIDER_STRING_TOKEN_SEPARATOR);
+ else
+ return {};
}
///@}
@@ -142,7 +148,10 @@ public:
/// @brief To get with @ref SetLanguages changed values.
std::vector<std::string> GetLanguages() const
{
- return tools::StringUtils::Split(m_cStructure->strLanguages, PROVIDER_STRING_TOKEN_SEPARATOR);
+ if (m_cStructure->strLanguages)
+ return tools::StringUtils::Split(m_cStructure->strLanguages, PROVIDER_STRING_TOKEN_SEPARATOR);
+ else
+ return {};
}
///@}
diff --git a/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/pvr/Recordings.h b/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/pvr/Recordings.h
index 9e27c513f7..1fd8616691 100644
--- a/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/pvr/Recordings.h
+++ b/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/pvr/Recordings.h
@@ -45,7 +45,7 @@ public:
{
m_cStructure->iSeriesNumber = PVR_RECORDING_INVALID_SERIES_EPISODE;
m_cStructure->iEpisodeNumber = PVR_RECORDING_INVALID_SERIES_EPISODE;
- m_cStructure->recordingTime = 0;
+ m_cStructure->iEpisodePartNumber = PVR_RECORDING_INVALID_SERIES_EPISODE;
m_cStructure->iDuration = PVR_RECORDING_VALUE_NOT_AVAILABLE;
m_cStructure->iPriority = PVR_RECORDING_VALUE_NOT_AVAILABLE;
m_cStructure->iLifetime = PVR_RECORDING_VALUE_NOT_AVAILABLE;
@@ -53,12 +53,10 @@ public:
m_cStructure->iGenreSubType = PVR_RECORDING_VALUE_NOT_AVAILABLE;
m_cStructure->iPlayCount = PVR_RECORDING_VALUE_NOT_AVAILABLE;
m_cStructure->iLastPlayedPosition = PVR_RECORDING_VALUE_NOT_AVAILABLE;
- m_cStructure->bIsDeleted = false;
- m_cStructure->iEpgEventId = 0;
m_cStructure->iChannelUid = PVR_RECORDING_VALUE_NOT_AVAILABLE;
m_cStructure->channelType = PVR_RECORDING_CHANNEL_TYPE_UNKNOWN;
- m_cStructure->iFlags = 0;
m_cStructure->sizeInBytes = PVR_RECORDING_VALUE_NOT_AVAILABLE;
+ m_cStructure->iClientProviderUid = PVR_PROVIDER_INVALID_UID;
}
PVRRecording(const PVRRecording& recording) : DynamicCStructHdl(recording) {}
/*! \endcond */
@@ -74,6 +72,7 @@ public:
/// | **Episode name** | `std::string` | @ref PVRRecording::SetEpisodeName "SetEpisodeName" | @ref PVRRecording::GetEpisodeName "GetEpisodeName" | *optional*
/// | **Series number** | `int` | @ref PVRRecording::SetSeriesNumber "SetSeriesNumber" | @ref PVRRecording::GetSeriesNumber "GetSeriesNumber" | *optional*
/// | **Episode number** | `int` | @ref PVRRecording::SetEpisodeNumber "SetEpisodeNumber" | @ref PVRRecording::GetEpisodeNumber "GetEpisodeNumber" | *optional*
+ /// | **Episode part number** | `int` | @ref PVRRecording::SetEpisodePartNumber "SetEpisodePartNumber" | @ref PVRRecording::GetEpisodePartNumber "GetEpisodePartNumber" | *optional*
/// | **Year** | `int` | @ref PVRRecording::SetYear "SetYear" | @ref PVRRecording::GetYear "GetYear" | *optional*
/// | **Directory** | `std::string` | @ref PVRRecording::SetDirectory "SetDirectory" | @ref PVRRecording::GetDirectory "GetDirectory" | *optional*
/// | **Plot outline** | `std::string` | @ref PVRRecording::SetPlotOutline "SetPlotOutline" | @ref PVRRecording::GetPlotOutline "GetPlotOutline" | *optional*
@@ -100,6 +99,10 @@ public:
/// | **Size in bytes** | `std::string` | @ref PVRRecording::SetSizeInBytes "SetSizeInBytes" | @ref PVRRecording::GetSizeInBytes "GetSizeInBytes" | *optional*
/// | **Client provider unique identifier** | `int` | @ref PVRChannel::SetClientProviderUid "SetClientProviderUid" | @ref PVRTimer::GetClientProviderUid "GetClientProviderUid" | *optional*
/// | **Provider name** | `std::string` | @ref PVRChannel::SetProviderName "SetProviderlName" | @ref PVRChannel::GetProviderName "GetProviderName" | *optional*
+ /// | **Parental rating age** | `unsigned int` | @ref PVRRecording::SetParentalRating "SetParentalRating" | @ref PVRRecording::GetParentalRating "GetParentalRating" | *optional*
+ /// | **Parental rating code** | `std::string` | @ref PVRRecording::SetParentalRatingCode "SetParentalRatingCode" | @ref PVRRecording::GetParentalRatingCode "GetParentalRatingCode" | *optional*
+ /// | **Parental rating icon** | `std::string` | @ref PVRRecording::SetParentalRatingIcon "SetParentalRatingIcon" | @ref PVRRecording::GetParentalRatingIcon "GetParentalRatingIcon" | *optional*
+ /// | **Parental rating source** | `std::string` | @ref PVRRecording::SetParentalRatingSource "SetParentalRatingSource" | @ref PVRRecording::GetParentalRatingSource "GetParentalRatingSource" | *optional*
/// @addtogroup cpp_kodi_addon_pvr_Defs_Recording_PVRRecording
///@{
@@ -112,7 +115,10 @@ public:
}
/// @brief To get with @ref SetRecordingId changed values.
- std::string GetRecordingId() const { return m_cStructure->strRecordingId; }
+ std::string GetRecordingId() const
+ {
+ return m_cStructure->strRecordingId ? m_cStructure->strRecordingId : "";
+ }
/// @brief **required**\n
/// The title of this recording.
@@ -122,7 +128,7 @@ public:
}
/// @brief To get with @ref SetTitle changed values.
- std::string GetTitle() const { return m_cStructure->strTitle; }
+ std::string GetTitle() const { return m_cStructure->strTitle ? m_cStructure->strTitle : ""; }
/// @brief **optional**\n
/// Episode name (also known as subtitle).
@@ -132,7 +138,10 @@ public:
}
/// @brief To get with @ref SetEpisodeName changed values.
- std::string GetEpisodeName() const { return m_cStructure->strEpisodeName; }
+ std::string GetEpisodeName() const
+ {
+ return m_cStructure->strEpisodeName ? m_cStructure->strEpisodeName : "";
+ }
/// @brief **optional**\n
/// Series number (usually called season).
@@ -153,6 +162,16 @@ public:
int GetEpisodeNumber() const { return m_cStructure->iEpisodeNumber; }
/// @brief **optional**\n
+ /// Episode part number.
+ void SetEpisodePartNumber(int episodePartNumber)
+ {
+ m_cStructure->iEpisodePartNumber = episodePartNumber;
+ }
+
+ /// @brief To get with @ref SetEpisodePartNumber changed values.
+ int GetEpisodePartNumber() const { return m_cStructure->iEpisodePartNumber; }
+
+ /// @brief **optional**\n
/// Year of first release (use to identify a specific movie re-make) / first
/// airing for TV shows.
///
@@ -171,7 +190,10 @@ public:
}
/// @brief To get with @ref SetDirectory changed values.
- std::string GetDirectory() const { return m_cStructure->strDirectory; }
+ std::string GetDirectory() const
+ {
+ return m_cStructure->strDirectory ? m_cStructure->strDirectory : "";
+ }
/// @brief **optional**\n
/// Plot outline name.
@@ -181,7 +203,10 @@ public:
}
/// @brief To get with @ref SetPlotOutline changed values.
- std::string GetPlotOutline() const { return m_cStructure->strPlotOutline; }
+ std::string GetPlotOutline() const
+ {
+ return m_cStructure->strPlotOutline ? m_cStructure->strPlotOutline : "";
+ }
/// @brief **optional**\n
/// Plot name.
@@ -191,7 +216,7 @@ public:
}
/// @brief To get with @ref SetPlot changed values.
- std::string GetPlot() const { return m_cStructure->strPlot; }
+ std::string GetPlot() const { return m_cStructure->strPlot ? m_cStructure->strPlot : ""; }
/// @brief **optional**\n
/// Channel name.
@@ -201,7 +226,10 @@ public:
}
/// @brief To get with @ref SetChannelName changed values.
- std::string GetChannelName() const { return m_cStructure->strChannelName; }
+ std::string GetChannelName() const
+ {
+ return m_cStructure->strChannelName ? m_cStructure->strChannelName : "";
+ }
/// @brief **optional**\n
/// Channel logo (icon) path.
@@ -211,7 +239,10 @@ public:
}
/// @brief To get with @ref SetIconPath changed values.
- std::string GetIconPath() const { return m_cStructure->strIconPath; }
+ std::string GetIconPath() const
+ {
+ return m_cStructure->strIconPath ? m_cStructure->strIconPath : "";
+ }
/// @brief **optional**\n
/// Thumbnail path.
@@ -221,7 +252,10 @@ public:
}
/// @brief To get with @ref SetThumbnailPath changed values.
- std::string GetThumbnailPath() const { return m_cStructure->strThumbnailPath; }
+ std::string GetThumbnailPath() const
+ {
+ return m_cStructure->strThumbnailPath ? m_cStructure->strThumbnailPath : "";
+ }
/// @brief **optional**\n
/// Fanart path.
@@ -231,7 +265,10 @@ public:
}
/// @brief To get with @ref SetFanartPath changed values.
- std::string GetFanartPath() const { return m_cStructure->strFanartPath; }
+ std::string GetFanartPath() const
+ {
+ return m_cStructure->strFanartPath ? m_cStructure->strFanartPath : "";
+ }
/// @brief **optional**\n
/// Start time of the recording.
@@ -354,7 +391,10 @@ public:
}
/// @brief To get with @ref SetGenreDescription changed values.
- std::string GetGenreDescription() const { return m_cStructure->strGenreDescription; }
+ std::string GetGenreDescription() const
+ {
+ return m_cStructure->strGenreDescription ? m_cStructure->strGenreDescription : "";
+ }
/// @brief **optional**\n
/// Play count of this recording on the client.
@@ -436,7 +476,10 @@ public:
}
/// @brief To get with @ref SetFirstAired changed values
- std::string GetFirstAired() const { return m_cStructure->strFirstAired; }
+ std::string GetFirstAired() const
+ {
+ return m_cStructure->strFirstAired ? m_cStructure->strFirstAired : "";
+ }
/// @brief **optional**\n
/// Bit field of independent flags associated with the recording.
@@ -481,7 +524,59 @@ public:
}
/// @brief To get with @ref SetProviderName changed values.
- std::string GetProviderName() const { return m_cStructure->strProviderName; }
+ std::string GetProviderName() const
+ {
+ return m_cStructure->strProviderName ? m_cStructure->strProviderName : "";
+ }
+
+ /// @brief **optional**\n
+ /// Age rating for the recording.
+ void SetParentalRating(unsigned int iParentalRating)
+ {
+ m_cStructure->iParentalRating = iParentalRating;
+ }
+
+ /// @brief To get with @ref SetParentalRating changed values
+ unsigned int GetParentalRating() const { return m_cStructure->iParentalRating; }
+
+ /// @brief **optional**\n
+ /// Parental rating code for this recording.
+ void SetParentalRatingCode(const std::string& ratingCode)
+ {
+ ReallocAndCopyString(&m_cStructure->strParentalRatingCode, ratingCode.c_str());
+ }
+
+ /// @brief To get with @ref SetParentalRatingCode changed values.
+ std::string GetParentalRatingCode() const
+ {
+ return m_cStructure->strParentalRatingCode ? m_cStructure->strParentalRatingCode : "";
+ }
+
+ /// @brief **optional**\n
+ /// Parental rating icon for this recording.
+ void SetParentalRatingIcon(const std::string& ratingIcon)
+ {
+ ReallocAndCopyString(&m_cStructure->strParentalRatingIcon, ratingIcon.c_str());
+ }
+
+ /// @brief To get with @ref SetParentalRatingIcon changed values.
+ std::string GetParentalRatingIcon() const
+ {
+ return m_cStructure->strParentalRatingIcon ? m_cStructure->strParentalRatingIcon : "";
+ }
+
+ /// @brief **optional**\n
+ /// Parental rating source for this recording.
+ void SetParentalRatingSource(const std::string& ratingSource)
+ {
+ ReallocAndCopyString(&m_cStructure->strParentalRatingSource, ratingSource.c_str());
+ }
+
+ /// @brief To get with @ref SetParentalRatingSource changed values.
+ std::string GetParentalRatingSource() const
+ {
+ return m_cStructure->strParentalRatingSource ? m_cStructure->strParentalRatingSource : "";
+ }
static void AllocResources(const PVR_RECORDING* source, PVR_RECORDING* target)
{
@@ -498,6 +593,9 @@ public:
target->strFanartPath = AllocAndCopyString(source->strFanartPath);
target->strFirstAired = AllocAndCopyString(source->strFirstAired);
target->strProviderName = AllocAndCopyString(source->strProviderName);
+ target->strParentalRatingCode = AllocAndCopyString(source->strParentalRatingCode);
+ target->strParentalRatingIcon = AllocAndCopyString(source->strParentalRatingIcon);
+ target->strParentalRatingSource = AllocAndCopyString(source->strParentalRatingSource);
}
static void FreeResources(PVR_RECORDING* target)
@@ -515,6 +613,9 @@ public:
FreeString(target->strFanartPath);
FreeString(target->strFirstAired);
FreeString(target->strProviderName);
+ FreeString(target->strParentalRatingCode);
+ FreeString(target->strParentalRatingIcon);
+ FreeString(target->strParentalRatingSource);
}
private:
diff --git a/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/pvr/Timers.h b/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/pvr/Timers.h
index 2dcf8029fa..9df97ee685 100644
--- a/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/pvr/Timers.h
+++ b/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/pvr/Timers.h
@@ -43,26 +43,13 @@ public:
/*! \cond PRIVATE */
PVRTimer()
{
- m_cStructure->iClientIndex = 0;
m_cStructure->state = PVR_TIMER_STATE_NEW;
m_cStructure->iTimerType = PVR_TIMER_TYPE_NONE;
- m_cStructure->iParentClientIndex = 0;
m_cStructure->iClientChannelUid = PVR_TIMER_VALUE_NOT_AVAILABLE;
- m_cStructure->startTime = 0;
- m_cStructure->endTime = 0;
- m_cStructure->bStartAnyTime = false;
- m_cStructure->bEndAnyTime = false;
- m_cStructure->bFullTextEpgSearch = false;
m_cStructure->iPriority = PVR_TIMER_VALUE_NOT_AVAILABLE;
m_cStructure->iLifetime = PVR_TIMER_VALUE_NOT_AVAILABLE;
m_cStructure->iMaxRecordings = PVR_TIMER_VALUE_NOT_AVAILABLE;
- m_cStructure->iRecordingGroup = 0;
- m_cStructure->firstDay = 0;
m_cStructure->iWeekdays = PVR_WEEKDAY_NONE;
- m_cStructure->iPreventDuplicateEpisodes = 0;
- m_cStructure->iEpgUid = 0;
- m_cStructure->iMarginStart = 0;
- m_cStructure->iMarginEnd = 0;
m_cStructure->iGenreType = PVR_TIMER_VALUE_NOT_AVAILABLE;
m_cStructure->iGenreSubType = PVR_TIMER_VALUE_NOT_AVAILABLE;
}
@@ -101,6 +88,7 @@ public:
/// | **Genre type** | `int` | @ref PVRTimer::SetGenreType "SetGenreType" | @ref PVRTimer::GetGenreType "GetGenreType" | *optional*
/// | **Genre sub type** | `int` | @ref PVRTimer::SetGenreSubType "SetGenreSubType" | @ref PVRTimer::GetGenreSubType "GetGenreSubType" | *optional*
/// | **Series link** | `std::string` | @ref PVRTimer::SetSeriesLink "SetSeriesLink" | @ref PVRTimer::GetSeriesLink "GetSeriesLink" | *optional*
+ /// | **Custom properties** | @ref cpp_kodi_addon_pvr_Defs_PVRSettingKeyValuePair "PVRSettingKeyValuePair" | @ref PVRTimer::SetCustomProperties "SetCustomProperties" | @ref PVRTimer::GetCustomProperties "GetCustomProperties" | *optional*
/// @addtogroup cpp_kodi_addon_pvr_Defs_Timer_PVRTimer
///@{
@@ -171,7 +159,7 @@ public:
}
/// @brief To get with @ref SetTitle changed values.
- std::string GetTitle() const { return m_cStructure->strTitle; }
+ std::string GetTitle() const { return m_cStructure->strTitle ? m_cStructure->strTitle : ""; }
/// @brief **optional**\n
/// For timers scheduled by a repeating timer.
@@ -245,7 +233,10 @@ public:
}
/// @brief To get with @ref SetEPGSearchString changed values
- std::string GetEPGSearchString() const { return m_cStructure->strEpgSearchString; }
+ std::string GetEPGSearchString() const
+ {
+ return m_cStructure->strEpgSearchString ? m_cStructure->strEpgSearchString : "";
+ }
/// @brief **optional**\n
/// Indicates, whether @ref SetEPGSearchString() is to match against the epg
@@ -266,7 +257,10 @@ public:
}
/// @brief To get with @ref SetDirectory changed values.
- std::string GetDirectory() const { return m_cStructure->strDirectory; }
+ std::string GetDirectory() const
+ {
+ return m_cStructure->strDirectory ? m_cStructure->strDirectory : "";
+ }
/// @brief **optional**\n
/// The summary for this timer.
@@ -276,7 +270,10 @@ public:
}
/// @brief To get with @ref SetDirectory changed values.
- std::string GetSummary() const { return m_cStructure->strSummary; }
+ std::string GetSummary() const
+ {
+ return m_cStructure->strSummary ? m_cStructure->strSummary : "";
+ }
/// @brief **optional**\n
/// The priority of this timer.
@@ -461,7 +458,43 @@ public:
}
/// @brief To get with @ref SetSeriesLink changed values.
- std::string GetSeriesLink() const { return m_cStructure->strSeriesLink; }
+ std::string GetSeriesLink() const
+ {
+ return m_cStructure->strSeriesLink ? m_cStructure->strSeriesLink : "";
+ }
+
+ //----------------------------------------------------------------------------
+
+ /// @brief **optional**\n
+ /// Array containing the custom properties.
+ ///
+ /// @param[in] properties List of properties.
+ ///
+ /// --------------------------------------------------------------------------
+ ///
+ /// @copydetails cpp_kodi_addon_pvr_Defs_General_PVRSettingKeyValuePair_Help
+ void SetCustomProperties(const std::vector<PVRSettingKeyValuePair>& properties)
+ {
+ PVRSettingKeyValuePair::ReallocAndCopyData(&m_cStructure->customProps,
+ &m_cStructure->iCustomPropsSize, properties);
+ }
+
+ /// @brief To get with @ref SetCustomProperties changed values
+ std::vector<PVRSettingKeyValuePair> GetCustomProperties() const
+ {
+ std::vector<PVRSettingKeyValuePair> ret;
+ if (m_cStructure->iCustomPropsSize)
+ {
+ ret.reserve(m_cStructure->iCustomPropsSize);
+ for (unsigned int i = 0; i < m_cStructure->iCustomPropsSize; ++i)
+ {
+ ret.emplace_back(m_cStructure->customProps[i].iKey, m_cStructure->customProps[i].eType,
+ m_cStructure->customProps[i].iValue,
+ m_cStructure->customProps[i].strValue);
+ }
+ }
+ return ret;
+ }
///@}
static void AllocResources(const PVR_TIMER* source, PVR_TIMER* target)
@@ -471,6 +504,10 @@ public:
target->strDirectory = AllocAndCopyString(source->strDirectory);
target->strSummary = AllocAndCopyString(source->strSummary);
target->strSeriesLink = AllocAndCopyString(source->strSeriesLink);
+
+ target->customProps =
+ PVRSettingKeyValuePair::AllocAndCopyData(source->customProps, source->iCustomPropsSize);
+ target->iCustomPropsSize = source->iCustomPropsSize;
}
static void FreeResources(PVR_TIMER* target)
@@ -480,6 +517,10 @@ public:
FreeString(target->strDirectory);
FreeString(target->strSummary);
FreeString(target->strSeriesLink);
+
+ PVRSettingKeyValuePair::FreeResources(target->customProps, target->iCustomPropsSize);
+ target->customProps = nullptr;
+ target->iCustomPropsSize = 0;
}
private:
@@ -550,7 +591,6 @@ public:
/*! \cond PRIVATE */
PVRTimerType()
{
- memset(m_cStructure, 0, sizeof(PVR_TIMER_TYPE));
m_cStructure->iPrioritiesDefault = -1;
m_cStructure->iLifetimesDefault = -1;
m_cStructure->iPreventDuplicateEpisodesDefault = -1;
@@ -585,6 +625,8 @@ public:
/// | | | | | |
/// | **Max recordings selection** | @ref cpp_kodi_addon_pvr_Defs_PVRTypeIntValue "PVRTypeIntValue" | @ref PVRTimerType::SetMaxRecordings "SetMaxRecordings" | @ref PVRTimerType::GetMaxRecordings "GetMaxRecordings" | *optional*
/// | **Max recordings default** | `int`| @ref PVRTimerType::SetMaxRecordingsDefault "SetMaxRecordingsDefault" | @ref PVRTimerType::GetMaxRecordingsDefault "GetMaxRecordingsDefault" | *optional*
+ /// | | | | | |
+ /// | **Custom setting definitions**| @ref cpp_kodi_addon_pvr_Defs_PVRSettingDefinition "PVRSettingDefinition" | @ref PVRTimerType::SetCustomSettingDefinitions "SetCustomSettingDefinitions" | @ref PVRTimerType::GetCustomSettingDefinitions "GetCustomSettingDefinitions" | *optional*
///
/// @addtogroup cpp_kodi_addon_pvr_Defs_Timer_PVRTimerType
@@ -628,7 +670,10 @@ public:
}
/// @brief To get with @ref SetDescription changed values.
- std::string GetDescription() const { return m_cStructure->strDescription; }
+ std::string GetDescription() const
+ {
+ return m_cStructure->strDescription ? m_cStructure->strDescription : "";
+ }
//----------------------------------------------------------------------------
@@ -821,7 +866,7 @@ public:
/// @brief **optional**\n
/// Array containing the possible values of @ref PVRTimer::SetMaxRecordings().
///
- /// @param[in] maxRecordings List of lifetimes values
+ /// @param[in] maxRecordings List of max recordings values
/// @param[in] maxRecordingsDefault [opt] The default value in list, can also be
/// set by @ref SetMaxRecordingsDefault()
///
@@ -858,6 +903,78 @@ public:
/// @brief To get with @ref SetMaxRecordingsDefault changed values
int GetMaxRecordingsDefault() const { return m_cStructure->iMaxRecordingsDefault; }
+
+ //----------------------------------------------------------------------------
+
+ /// @brief **optional**\n
+ /// Array containing the possible custom integer setting definitions.
+ ///
+ /// @param[in] defs List of integer setting definitions.
+ ///
+ /// --------------------------------------------------------------------------
+ ///
+ /// @copydetails cpp_kodi_addon_pvr_Defs_General_PVRIntSettingDefinition_Help
+ void SetCustomSettingDefinitions(const std::vector<PVRSettingDefinition>& defs)
+ {
+ PVRSettingDefinition::ReallocAndCopyData(&m_cStructure->customSettingDefs,
+ &m_cStructure->iCustomSettingDefsSize, defs);
+ }
+
+ /// @brief To get with @ref SetCustomSettingDefinitions changed values
+ std::vector<PVRSettingDefinition> GetCustomSettingDefinitions() const
+ {
+ std::vector<PVRSettingDefinition> ret;
+ if (m_cStructure->iCustomSettingDefsSize)
+ {
+ ret.reserve(m_cStructure->iCustomSettingDefsSize);
+ for (unsigned int i = 0; i < m_cStructure->iCustomSettingDefsSize; ++i)
+ {
+ const PVR_SETTING_DEFINITION* def{m_cStructure->customSettingDefs[i]};
+
+ PVRIntSettingDefinition intDef;
+ if (def->intSettingDefinition)
+ {
+ const PVR_INT_SETTING_DEFINITION* intSettingDef{def->intSettingDefinition};
+
+ std::vector<PVRTypeIntValue> intValues;
+ if (intSettingDef->iValuesSize)
+ {
+ intValues.reserve(intSettingDef->iValuesSize);
+ for (unsigned int j = 0; j < intSettingDef->iValuesSize; ++j)
+ {
+ intValues.emplace_back(intSettingDef->values[j].iValue,
+ intSettingDef->values[j].strDescription);
+ }
+ }
+ intDef = {intValues, intSettingDef->iDefaultValue, intSettingDef->iMinValue,
+ intSettingDef->iStep, intSettingDef->iMaxValue};
+ }
+
+ PVRStringSettingDefinition stringDef;
+ if (def->stringSettingDefinition)
+ {
+ const PVR_STRING_SETTING_DEFINITION* stringSettingDef{def->stringSettingDefinition};
+
+ std::vector<PVRTypeStringValue> stringValues;
+ if (stringSettingDef->iValuesSize)
+ {
+ stringValues.reserve(stringSettingDef->iValuesSize);
+ for (unsigned int j = 0; j < stringSettingDef->iValuesSize; ++j)
+ {
+ stringValues.emplace_back(stringSettingDef->values[j].strValue,
+ stringSettingDef->values[j].strDescription);
+ }
+ }
+ stringDef = {stringValues, stringSettingDef->strDefaultValue,
+ stringSettingDef->bAllowEmptyValue};
+ }
+
+ ret.emplace_back(def->iId, def->strName, def->eType, def->iReadonlyConditions, intDef,
+ stringDef);
+ }
+ }
+ return ret;
+ }
///@}
static void AllocResources(const PVR_TIMER_TYPE* source, PVR_TIMER_TYPE* target)
@@ -893,6 +1010,12 @@ public:
target->maxRecordings =
PVRTypeIntValue::AllocAndCopyData(source->maxRecordings, source->iMaxRecordingsSize);
}
+
+ if (target->iCustomSettingDefsSize)
+ {
+ target->customSettingDefs = PVRSettingDefinition::AllocAndCopyData(
+ source->customSettingDefs, source->iCustomSettingDefsSize);
+ }
}
static void FreeResources(PVR_TIMER_TYPE* target)
@@ -920,6 +1043,10 @@ public:
PVRTypeIntValue::FreeResources(target->maxRecordings, target->iMaxRecordingsSize);
target->maxRecordings = nullptr;
target->iMaxRecordingsSize = 0;
+
+ PVRSettingDefinition::FreeResources(target->customSettingDefs, target->iCustomSettingDefsSize);
+ target->customSettingDefs = nullptr;
+ target->iCustomSettingDefsSize = 0;
}
private:
diff --git a/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr.h b/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr.h
index d32df12c19..e123ff414c 100644
--- a/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr.h
+++ b/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr.h
@@ -163,6 +163,7 @@ extern "C"
enum PVR_ERROR(__cdecl* GetChannels)(const struct AddonInstance_PVR*, PVR_HANDLE, bool);
enum PVR_ERROR(__cdecl* GetChannelStreamProperties)(const struct AddonInstance_PVR*,
const struct PVR_CHANNEL*,
+ enum PVR_SOURCE,
struct PVR_NAMED_VALUE***,
unsigned int*);
enum PVR_ERROR(__cdecl* GetSignalStatus)(const struct AddonInstance_PVR*,
@@ -305,6 +306,7 @@ extern "C"
// Stream demux interface functions
enum PVR_ERROR(__cdecl* GetStreamProperties)(const struct AddonInstance_PVR*,
struct PVR_STREAM_PROPERTIES*);
+ enum PVR_ERROR(__cdecl* StreamClosed)(const struct AddonInstance_PVR*);
struct DEMUX_PACKET*(__cdecl* DemuxRead)(const struct AddonInstance_PVR*);
void(__cdecl* DemuxReset)(const struct AddonInstance_PVR*);
void(__cdecl* DemuxAbort)(const struct AddonInstance_PVR*);
diff --git a/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_defines.h b/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_defines.h
index 3c64e9dd7f..3ece119195 100644
--- a/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_defines.h
+++ b/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_defines.h
@@ -20,7 +20,7 @@ extern "C"
#endif /* __cplusplus */
/*!
- * @brief "C" Representation of a general attribute integer value.
+ * @brief "C" Representation of an integer value, including a description.
*/
typedef struct PVR_ATTRIBUTE_INT_VALUE
{
@@ -29,6 +29,15 @@ extern "C"
} PVR_ATTRIBUTE_INT_VALUE;
/*!
+ * @brief "C" Representation of a string value, including a description
+ */
+ typedef struct PVR_ATTRIBUTE_STRING_VALUE
+ {
+ const char* strValue;
+ const char* strDescription;
+ } PVR_ATTRIBUTE_STRING_VALUE;
+
+ /*!
* @brief "C" Representation of a named value.
*/
typedef struct PVR_NAMED_VALUE
diff --git a/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_epg.h b/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_epg.h
index 415a59342c..d41d017a93 100644
--- a/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_epg.h
+++ b/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_epg.h
@@ -639,8 +639,10 @@ extern "C"
int iGenreSubType;
const char* strGenreDescription;
const char* strFirstAired;
- int iParentalRating;
+ unsigned int iParentalRating;
const char* strParentalRatingCode;
+ const char* strParentalRatingIcon;
+ const char* strParentalRatingSource;
int iStarRating;
int iSeriesNumber;
int iEpisodeNumber;
diff --git a/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_general.h b/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_general.h
index a0dff42428..4541f7ffe8 100644
--- a/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_general.h
+++ b/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_general.h
@@ -13,6 +13,7 @@
#include "pvr_defines.h"
#include <stdbool.h>
+#include <stdint.h>
//¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
// "C" Definitions group 1 - General PVR
@@ -113,6 +114,77 @@ extern "C"
//----------------------------------------------------------------------------
//============================================================================
+ /// @defgroup cpp_kodi_addon_pvr_Defs_General_PVR_SOURCE enum PVR_SOURCE
+ /// @ingroup cpp_kodi_addon_pvr_Defs_General
+ /// @brief **PVR add-on playback source**\n
+ /// Used in call to GetChannelStreamProperties() to indicate where the playback
+ /// call initiated.
+ ///
+ /// - @ref kodi::addon::CInstancePVRClient::GetChannelStreamProperties()
+ ///
+ ///@{
+ typedef enum PVR_SOURCE
+ {
+ /// @brief __0__ : Regular live playback
+ DEFAULT = 0,
+
+ /// @brief __1__ : From EPG, but playing back as live
+ PVR_SOURCE_EPG_AS_LIVE = 1,
+ } PVR_SOURCE;
+ ///@}
+ //----------------------------------------------------------------------------
+
+ //============================================================================
+ /// @defgroup cpp_kodi_addon_pvr_Defs_General_PVR_SETTING_READONLY_CONDITION enum PVR_SETTING_READONLY_CONDITION
+ /// @ingroup cpp_kodi_addon_pvr_Defs_General
+ /// @brief **Read-only conditions for settings**\n
+ /// To define read-only conditions for settings.
+ ///
+ ///@{
+ typedef enum PVR_SETTING_READONLY_CONDITION
+ {
+ /// @brief __0000 0000 0000 0000 0000 0000 0000 0000__ :\n Empty value.
+ PVR_SETTING_READONLY_CONDITION_NONE = 0,
+
+ /// @brief __0000 0000 0000 0000 0000 0000 0000 0001__ :\n Readonly, if associated timer is
+ /// disabled (PVR_TIMER_STATE_DISABLED. Applicable to timer settings only).
+ PVR_SETTING_READONLY_CONDITION_TIMER_DISABLED = (1 << 0),
+
+ /// @brief __0000 0000 0000 0000 0000 0000 0000 0010__ :\n Readonly, if associated timer is
+ /// scheduled (PVR_TIMER_STATE_SCHEDULED. Applicable to timer settings only).
+ PVR_SETTING_READONLY_CONDITION_TIMER_SCHEDULED = (1 << 1),
+
+ /// @brief __0000 0000 0000 0000 0000 0000 0000 0100__ :\n Readonly, if associated timer is
+ /// currently recording (PVR_TIMER_STATE_RECORDING. Applicable to timer settings only).
+ PVR_SETTING_READONLY_CONDITION_TIMER_RECORDING = (1 << 2),
+
+ /// @brief __0000 0000 0000 0000 0000 0000 0000 1000__ :\n Readonly, if associated timer is
+ /// currently recording (PVR_TIMER_STATE_COMPLETED. Applicable to timer settings only).
+ PVR_SETTING_READONLY_CONDITION_TIMER_COMPLETED = (1 << 3),
+
+ } PVR_SETTING_READONLY_CONDITION;
+ ///@}
+ //----------------------------------------------------------------------------
+
+ //============================================================================
+ /// @defgroup cpp_kodi_addon_pvr_Defs_General_PVR_SETTING_TYPE enum PVR_SETTING_TYPE
+ /// @ingroup cpp_kodi_addon_pvr_Defs_General
+ /// @brief **PVR setting type**\n
+ ///
+ ///@{
+ typedef enum PVR_SETTING_TYPE
+ {
+ /// @brief __0__ : Integer
+ INTEGER = 0,
+
+ /// @brief __1__ : String
+ STRING = 1,
+
+ } PVR_SETTING_TYPE;
+ ///@}
+ //----------------------------------------------------------------------------
+
+ //============================================================================
/// @defgroup cpp_kodi_addon_pvr_Defs_General_PVR_STREAM_PROPERTY definition PVR_STREAM_PROPERTY
/// @ingroup cpp_kodi_addon_pvr_Defs_General_Inputstream
/// @brief **PVR related stream property values**\n
@@ -134,6 +206,7 @@ extern "C"
/// ...
///
/// PVR_ERROR CMyPVRInstance::GetChannelStreamProperties(const kodi::addon::PVRChannel& channel,
+ /// PVR_SOURCE source,
/// std::vector<PVRStreamProperty>& properties)
/// {
/// ...
@@ -193,6 +266,7 @@ extern "C"
///
/// // On PVR instance of addon
/// PVR_ERROR CMyPVRInstance::GetChannelStreamProperties(const kodi::addon::PVRChannel& channel,
+ /// PVR_SOURCE source,
/// std::vector<PVRStreamProperty>& properties)
/// {
/// ...
@@ -250,6 +324,14 @@ extern "C"
///
#define PVR_STREAM_PROPERTY_EPGPLAYBACKASLIVE "epgplaybackaslive"
+ /// @brief <b>"true"</b> to denote that if the stream is from a channel but should
+ /// be played as an EPG tag.
+ ///
+ /// It should be played as an epg/catchup stream. Otherwise if it's a channel it will
+ /// played as live stream.
+ ///
+#define PVR_STREAM_PROPERTY_LIVEPLAYBACKASEPG "liveplaybackasepg"
+
/// @brief Special value for @ref PVR_STREAM_PROPERTY_INPUTSTREAM to use
/// ffmpeg to directly play a stream URL.
#define PVR_STREAM_PROPERTY_VALUE_INPUTSTREAMFFMPEG STREAM_PROPERTY_VALUE_INPUTSTREAMFFMPEG
@@ -293,6 +375,75 @@ extern "C"
struct PVR_ATTRIBUTE_INT_VALUE* recordingsLifetimeValues;
} PVR_ADDON_CAPABILITIES;
+ /*!
+ * @brief "C" Representation of an integer setting definition.
+ *
+ * Structure used to interface in "C" between Kodi and Addon.
+ *
+ * See @ref cpp_kodi_addon_pvr_Defs_PVRIntSettingDefinition "kodi::addon::PVRIntSettingDefinition"
+ * for description of values.
+ */
+ typedef struct PVR_INT_SETTING_DEFINITION
+ {
+ unsigned int iValuesSize;
+ struct PVR_ATTRIBUTE_INT_VALUE* values;
+ int iDefaultValue;
+ int iMinValue;
+ int iStep;
+ int iMaxValue;
+ } PVR_INT_SETTING_DEFINITION;
+
+ /*!
+ * @brief "C" Representation of a string setting definition.
+ *
+ * Structure used to interface in "C" between Kodi and Addon.
+ *
+ * See @ref cpp_kodi_addon_pvr_Defs_PVRStringSettingDefinition "kodi::addon::PVRStringSettingDefinition"
+ * for description of values.
+ */
+ typedef struct PVR_STRING_SETTING_DEFINITION
+ {
+ unsigned int iValuesSize;
+ struct PVR_ATTRIBUTE_STRING_VALUE* values;
+ const char* strDefaultValue;
+ bool bAllowEmptyValue;
+ } PVR_STRING_SETTING_DEFINITION;
+
+ /*!
+ * @brief "C" Representation of a setting definition.
+ *
+ * Structure used to interface in "C" between Kodi and Addon.
+ *
+ * See @ref cpp_kodi_addon_pvr_Defs_PVRSettingDefinition "kodi::addon::PVRSettingDefinition"
+ * for description of values.
+ */
+ typedef struct PVR_SETTING_DEFINITION
+ {
+ unsigned int iId;
+ const char* strName;
+ enum PVR_SETTING_TYPE eType;
+ uint64_t iReadonlyConditions;
+ struct PVR_INT_SETTING_DEFINITION* intSettingDefinition;
+ struct PVR_STRING_SETTING_DEFINITION* stringSettingDefinition;
+ } PVR_SETTING_DEFINITION;
+
+ /*!
+ * @brief "C" Representation of a key-value pair, either {int,int} or {int,string}, depending on
+ * the type set.
+ *
+ * Structure used to interface in "C" between Kodi and Addon.
+ *
+ * See @ref cpp_kodi_addon_pvr_Defs_PVRSettingKeyValuePair "kodi::addon::PVRSettingKeyValuePair" for description
+ * of values.
+ */
+ typedef struct PVR_SETTING_KEY_VALUE_PAIR
+ {
+ unsigned int iKey;
+ enum PVR_SETTING_TYPE eType;
+ int iValue;
+ const char* strValue;
+ } PVR_SETTING_KEY_VALUE_PAIR;
+
#ifdef __cplusplus
}
#endif /* __cplusplus */
diff --git a/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_recordings.h b/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_recordings.h
index 8c57f33e2a..8b0e19c5b3 100644
--- a/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_recordings.h
+++ b/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_recordings.h
@@ -61,12 +61,13 @@ extern "C"
//============================================================================
/// @ingroup cpp_kodi_addon_pvr_Defs_Recording_PVRRecording
/// @brief Special @ref kodi::addon::PVRRecording::SetSeriesNumber() and
- /// @ref kodi::addon::PVRRecording::SetEpisodeNumber() value to indicate it is
- /// not to be used.
+ /// @ref kodi::addon::PVRRecording::SetEpisodeNumber() and
+ /// @ref kodi::addon::PVRRecording::SetEpisodePartNumber() value to indicate
+ /// it is not to be used.
///
/// Used if recording has no valid season and/or episode info.
///
-#define PVR_RECORDING_INVALID_SERIES_EPISODE EPG_TAG_INVALID_SERIES_EPISODE
+#define PVR_RECORDING_INVALID_SERIES_EPISODE -1
//----------------------------------------------------------------------------
//============================================================================
@@ -113,6 +114,7 @@ extern "C"
const char* strEpisodeName;
int iSeriesNumber;
int iEpisodeNumber;
+ int iEpisodePartNumber;
int iYear;
const char* strDirectory;
const char* strPlotOutline;
@@ -139,6 +141,10 @@ extern "C"
int64_t sizeInBytes;
int iClientProviderUid;
const char* strProviderName;
+ unsigned int iParentalRating;
+ const char* strParentalRatingCode;
+ const char* strParentalRatingIcon;
+ const char* strParentalRatingSource;
} PVR_RECORDING;
#ifdef __cplusplus
diff --git a/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_timers.h b/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_timers.h
index 8af03939ff..668a18529c 100644
--- a/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_timers.h
+++ b/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_timers.h
@@ -298,7 +298,7 @@ extern "C"
/// @brief __1__ : The timer is scheduled for recording.
PVR_TIMER_STATE_SCHEDULED = 1,
- /// @brief __2__ : The timer is currently recordings.
+ /// @brief __2__ : The timer is currently recording.
PVR_TIMER_STATE_RECORDING = 2,
/// @brief __3__ : The recording completed successfully.
@@ -310,19 +310,17 @@ extern "C"
/// @brief __5__ : The timer was scheduled, but was canceled.
PVR_TIMER_STATE_CANCELLED = 5,
- /// @brief __6__ : The scheduled timer conflicts with another one, but will be
- /// recorded.
+ /// @brief __6__ : The scheduled timer conflicts with another one, but will be recorded.
PVR_TIMER_STATE_CONFLICT_OK = 6,
- /// @brief __7__ : The scheduled timer conflicts with another one and won't be
- /// recorded.
+ /// @brief __7__ : The scheduled timer conflicts with another one and won't be recorded.
PVR_TIMER_STATE_CONFLICT_NOK = 7,
/// @brief __8__ : The timer is scheduled, but can't be recorded for some reason.
PVR_TIMER_STATE_ERROR = 8,
- /// @brief __9__ : The timer was disabled by the user, can be enabled via setting
- /// the state to @ref PVR_TIMER_STATE_SCHEDULED.
+ /// @brief __9__ : The timer was disabled by the user, can be enabled via setting the state to
+ /// @ref PVR_TIMER_STATE_SCHEDULED.
PVR_TIMER_STATE_DISABLED = 9,
} PVR_TIMER_STATE;
///@}
@@ -365,10 +363,12 @@ extern "C"
int iGenreType;
int iGenreSubType;
const char* strSeriesLink;
+ unsigned int iCustomPropsSize;
+ struct PVR_SETTING_KEY_VALUE_PAIR* customProps;
} PVR_TIMER;
/*!
- * @brief "C" PVR add-on timer event type.
+ * @brief "C" PVR add-on timer type.
*
* Structure used to interface in "C" between Kodi and Addon.
*
@@ -400,6 +400,9 @@ extern "C"
unsigned int iMaxRecordingsSize;
struct PVR_ATTRIBUTE_INT_VALUE* maxRecordings;
int iMaxRecordingsDefault;
+
+ unsigned int iCustomSettingDefsSize;
+ struct PVR_SETTING_DEFINITION** customSettingDefs;
} PVR_TIMER_TYPE;
#ifdef __cplusplus
diff --git a/xbmc/addons/kodi-dev-kit/include/kodi/versions.h b/xbmc/addons/kodi-dev-kit/include/kodi/versions.h
index 4b8a49e9b7..c4c45d6f95 100644
--- a/xbmc/addons/kodi-dev-kit/include/kodi/versions.h
+++ b/xbmc/addons/kodi-dev-kit/include/kodi/versions.h
@@ -130,8 +130,8 @@
#define ADDON_INSTANCE_VERSION_PERIPHERAL_DEPENDS "addon-instance/Peripheral.h" \
"addon-instance/PeripheralUtils.h"
-#define ADDON_INSTANCE_VERSION_PVR "8.4.0"
-#define ADDON_INSTANCE_VERSION_PVR_MIN "8.4.0"
+#define ADDON_INSTANCE_VERSION_PVR "9.1.0"
+#define ADDON_INSTANCE_VERSION_PVR_MIN "9.0.0"
#define ADDON_INSTANCE_VERSION_PVR_XML_ID "kodi.binary.instance.pvr"
#define ADDON_INSTANCE_VERSION_PVR_DEPENDS "c-api/addon-instance/pvr.h" \
"c-api/addon-instance/pvr/pvr_channel_groups.h" \
diff --git a/xbmc/cdrip/CDDARipJob.h b/xbmc/cdrip/CDDARipJob.h
index 11d92eb976..be98321b91 100644
--- a/xbmc/cdrip/CDDARipJob.h
+++ b/xbmc/cdrip/CDDARipJob.h
@@ -52,7 +52,7 @@ public:
const char* GetType() const override { return "cdrip"; }
bool operator==(const CJob* job) const override;
bool DoWork() override;
- std::string GetOutput() const { return m_output; }
+ const std::string& GetOutput() const { return m_output; }
protected:
/*!
diff --git a/xbmc/cores/AudioEngine/CMakeLists.txt b/xbmc/cores/AudioEngine/CMakeLists.txt
index fba64eda89..d3a5acc973 100644
--- a/xbmc/cores/AudioEngine/CMakeLists.txt
+++ b/xbmc/cores/AudioEngine/CMakeLists.txt
@@ -105,17 +105,19 @@ endif()
if(CORE_SYSTEM_NAME MATCHES windows)
list(APPEND SOURCES Sinks/AESinkWASAPI.cpp
- Sinks/windows/AESinkFactoryWin.cpp)
+ Sinks/AESinkXAudio.cpp
+ Sinks/windows/AESinkFactoryWin.cpp
+ Sinks/windows/AESinkFactoryWinRT.cpp)
list(APPEND HEADERS Sinks/AESinkWASAPI.h
+ Sinks/AESinkXAudio.h
Sinks/windows/AESinkFactoryWin.h)
+
if(CORE_SYSTEM_NAME STREQUAL windowsstore)
- list(APPEND SOURCES Sinks/AESinkXAudio.cpp
- Sinks/windows/AESinkFactoryWin10.cpp)
- list(APPEND SOURCES Sinks/AESinkXAudio.h)
- elseif(CORE_SYSTEM_NAME STREQUAL windows)
+ list(APPEND SOURCES Sinks/windows/AESinkFactoryWin10.cpp)
+ else()
list(APPEND SOURCES Sinks/AESinkDirectSound.cpp
Sinks/windows/AESinkFactoryWin32.cpp)
- list(APPEND SOURCES Sinks/AESinkDirectSound.h)
+ list(APPEND HEADERS Sinks/AESinkDirectSound.h)
endif()
endif()
diff --git a/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAE.cpp b/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAE.cpp
index e7ae140bec..d68de6c6f6 100644
--- a/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAE.cpp
+++ b/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAE.cpp
@@ -1369,7 +1369,8 @@ void CActiveAE::Configure(AEAudioFormat *desiredFmt)
(*it)->m_inputBuffers->m_format, outputFormat, m_settings.resampleQuality);
(*it)->m_processingBuffers->ForceResampler((*it)->m_forceResampler);
- (*it)->m_processingBuffers->Create(MAX_CACHE_LEVEL*1000, false, m_settings.stereoupmix, m_settings.normalizelevels);
+ (*it)->m_processingBuffers->Create(MAX_CACHE_LEVEL * 1000, false, m_settings.stereoupmix,
+ m_settings.normalizelevels, m_settings.mixSubLevel);
}
if (m_mode == MODE_TRANSCODE || m_streams.size() > 1)
(*it)->m_processingBuffers->FillBuffer();
@@ -1641,7 +1642,9 @@ void CActiveAE::ChangeResamplers()
std::list<CActiveAEStream*>::iterator it;
for(it=m_streams.begin(); it!=m_streams.end(); ++it)
{
- (*it)->m_processingBuffers->ConfigureResampler(m_settings.normalizelevels, m_settings.stereoupmix, m_settings.resampleQuality);
+ (*it)->m_processingBuffers->ConfigureResampler(
+ m_settings.normalizelevels, m_settings.stereoupmix, m_settings.resampleQuality,
+ m_settings.mixSubLevel);
}
}
@@ -2666,6 +2669,7 @@ void CActiveAE::LoadSettings()
m_settings.atempoThreshold = settings->GetInt(CSettings::SETTING_AUDIOOUTPUT_ATEMPOTHRESHOLD) / 100.0;
m_settings.streamNoise = settings->GetBool(CSettings::SETTING_AUDIOOUTPUT_STREAMNOISE);
m_settings.silenceTimeoutMinutes = settings->GetInt(CSettings::SETTING_AUDIOOUTPUT_STREAMSILENCE);
+ m_settings.mixSubLevel = settings->GetInt(CSettings::SETTING_AUDIOOUTPUT_MIXSUBLEVEL) / 100.0;
}
void CActiveAE::ValidateOutputDevices(bool saveChanges)
@@ -3310,13 +3314,9 @@ bool CActiveAE::ResampleSound(CActiveAESound *sound)
std::unique_ptr<IAEResample> resampler =
CAEResampleFactory::Create(AERESAMPLEFACTORY_QUICK_RESAMPLE);
- resampler->Init(dst_config, orig_config,
- false,
- true,
- M_SQRT1_2,
- outChannels.Count() > 0 ? &outChannels : nullptr,
- m_settings.resampleQuality,
- false);
+ resampler->Init(dst_config, orig_config, false, true, M_SQRT1_2,
+ outChannels.Count() > 0 ? &outChannels : nullptr, m_settings.resampleQuality,
+ false, 0.0f);
dst_samples = resampler->CalcDstSampleCount(sound->GetSound(true)->nb_samples,
m_internalFormat.m_sampleRate,
diff --git a/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAE.h b/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAE.h
index 7c43d2037c..cf74ee52b0 100644
--- a/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAE.h
+++ b/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAE.h
@@ -63,6 +63,7 @@ struct AudioSettings
double atempoThreshold;
bool streamNoise;
int silenceTimeoutMinutes;
+ float mixSubLevel;
};
class CActiveAEControlProtocol : public Protocol
diff --git a/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAEBuffer.cpp b/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAEBuffer.cpp
index 8e4d957cea..5ab47edda4 100644
--- a/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAEBuffer.cpp
+++ b/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAEBuffer.cpp
@@ -144,12 +144,14 @@ CActiveAEBufferPoolResample::~CActiveAEBufferPoolResample()
Flush();
}
-bool CActiveAEBufferPoolResample::Create(unsigned int totaltime, bool remap, bool upmix, bool normalize)
+bool CActiveAEBufferPoolResample::Create(
+ unsigned int totaltime, bool remap, bool upmix, bool normalize, float sublevel)
{
CActiveAEBufferPool::Create(totaltime);
m_remap = remap;
m_stereoUpmix = upmix;
+ m_mixSubLevel = sublevel;
m_normalize = true;
if ((m_format.m_channelLayout.Count() < m_inputFormat.m_channelLayout.Count() && !normalize))
@@ -184,13 +186,9 @@ void CActiveAEBufferPoolResample::ChangeResampler()
srcConfig.bits_per_sample = CAEUtil::DataFormatToUsedBits(m_inputFormat.m_dataFormat);
srcConfig.dither_bits = CAEUtil::DataFormatToDitherBits(m_inputFormat.m_dataFormat);
- m_resampler->Init(dstConfig, srcConfig,
- m_stereoUpmix,
- m_normalize,
- m_centerMixLevel,
- m_remap ? &m_format.m_channelLayout : nullptr,
- m_resampleQuality,
- m_forceResampler);
+ m_resampler->Init(dstConfig, srcConfig, m_stereoUpmix, m_normalize, m_centerMixLevel,
+ m_remap ? &m_format.m_channelLayout : nullptr, m_resampleQuality,
+ m_forceResampler, m_mixSubLevel);
m_changeResampler = false;
}
@@ -350,7 +348,10 @@ bool CActiveAEBufferPoolResample::ResampleBuffers(int64_t timestamp)
return busy;
}
-void CActiveAEBufferPoolResample::ConfigureResampler(bool normalizelevels, bool stereoupmix, AEQuality quality)
+void CActiveAEBufferPoolResample::ConfigureResampler(bool normalizelevels,
+ bool stereoupmix,
+ AEQuality quality,
+ float sublevel)
{
bool normalize = true;
if ((m_format.m_channelLayout.Count() < m_inputFormat.m_channelLayout.Count()) && !normalizelevels)
diff --git a/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAEBuffer.h b/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAEBuffer.h
index 70e51bbd6f..9feff2c558 100644
--- a/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAEBuffer.h
+++ b/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAEBuffer.h
@@ -78,9 +78,13 @@ public:
CActiveAEBufferPoolResample(const AEAudioFormat& inputFormat, const AEAudioFormat& outputFormat, AEQuality quality);
~CActiveAEBufferPoolResample() override;
using CActiveAEBufferPool::Create;
- bool Create(unsigned int totaltime, bool remap, bool upmix, bool normalize = true);
+ bool Create(
+ unsigned int totaltime, bool remap, bool upmix, bool normalize = true, float sublevel = 0.0);
bool ResampleBuffers(int64_t timestamp = 0);
- void ConfigureResampler(bool normalizelevels, bool stereoupmix, AEQuality quality);
+ void ConfigureResampler(bool normalizelevels,
+ bool stereoupmix,
+ AEQuality quality,
+ float sublevel);
float GetDelay();
void Flush();
void SetDrain(bool drain);
@@ -107,6 +111,7 @@ protected:
double m_centerMixLevel = M_SQRT1_2;
bool m_fillPackets = false;
bool m_normalize = true;
+ float m_mixSubLevel = 0.0f;
bool m_changeResampler = false;
bool m_forceResampler = false;
AEQuality m_resampleQuality;
diff --git a/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAEResampleFFMPEG.cpp b/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAEResampleFFMPEG.cpp
index e897cbd3ea..70e946a116 100644
--- a/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAEResampleFFMPEG.cpp
+++ b/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAEResampleFFMPEG.cpp
@@ -29,8 +29,15 @@ CActiveAEResampleFFMPEG::~CActiveAEResampleFFMPEG()
swr_free(&m_pContext);
}
-bool CActiveAEResampleFFMPEG::Init(SampleConfig dstConfig, SampleConfig srcConfig, bool upmix, bool normalize, double centerMix,
- CAEChannelInfo *remapLayout, AEQuality quality, bool force_resample)
+bool CActiveAEResampleFFMPEG::Init(SampleConfig dstConfig,
+ SampleConfig srcConfig,
+ bool upmix,
+ bool normalize,
+ double centerMix,
+ CAEChannelInfo* remapLayout,
+ AEQuality quality,
+ bool force_resample,
+ float sublevel)
{
m_dst_chan_layout = dstConfig.channel_layout;
m_dst_channels = dstConfig.channels;
@@ -78,6 +85,9 @@ bool CActiveAEResampleFFMPEG::Init(SampleConfig dstConfig, SampleConfig srcConfi
return false;
}
+ if (sublevel > 0.0f)
+ av_opt_set_double(m_pContext, "lfe_mix_level", static_cast<double>(sublevel), 0);
+
if(quality == AE_QUALITY_HIGH)
{
av_opt_set_double(m_pContext, "cutoff", 1.0, 0);
diff --git a/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAEResampleFFMPEG.h b/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAEResampleFFMPEG.h
index 5fbce373ad..b6a9a03376 100644
--- a/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAEResampleFFMPEG.h
+++ b/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAEResampleFFMPEG.h
@@ -27,8 +27,15 @@ public:
const char *GetName() override { return "ActiveAEResampleFFMPEG"; }
CActiveAEResampleFFMPEG();
~CActiveAEResampleFFMPEG() override;
- bool Init(SampleConfig dstConfig, SampleConfig srcConfig, bool upmix, bool normalize, double centerMix,
- CAEChannelInfo *remapLayout, AEQuality quality, bool force_resample) override;
+ bool Init(SampleConfig dstConfig,
+ SampleConfig srcConfig,
+ bool upmix,
+ bool normalize,
+ double centerMix,
+ CAEChannelInfo* remapLayout,
+ AEQuality quality,
+ bool force_resample,
+ float sublevel) override;
int Resample(uint8_t **dst_buffer, int dst_samples, uint8_t **src_buffer, int src_samples, double ratio) override;
int64_t GetDelay(int64_t base) override;
int GetBufferedSamples() override;
diff --git a/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAESettings.cpp b/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAESettings.cpp
index 5f9a1fbc75..b7b20ecf3f 100644
--- a/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAESettings.cpp
+++ b/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAESettings.cpp
@@ -52,6 +52,7 @@ CActiveAESettings::CActiveAESettings(CActiveAE &ae) : m_audioEngine(ae)
settingSet.insert(CSettings::SETTING_AUDIOOUTPUT_PASSTHROUGHDEVICE);
settingSet.insert(CSettings::SETTING_AUDIOOUTPUT_STREAMSILENCE);
settingSet.insert(CSettings::SETTING_AUDIOOUTPUT_STREAMNOISE);
+ settingSet.insert(CSettings::SETTING_AUDIOOUTPUT_MIXSUBLEVEL);
settingSet.insert(CSettings::SETTING_AUDIOOUTPUT_MAINTAINORIGINALVOLUME);
settingSet.insert(CSettings::SETTING_AUDIOOUTPUT_DTSHDCOREFALLBACK);
settings->GetSettingsManager()->RegisterCallback(this, settingSet);
diff --git a/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAESink.cpp b/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAESink.cpp
index 700abfcee5..f0cce1885b 100644
--- a/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAESink.cpp
+++ b/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAESink.cpp
@@ -1192,8 +1192,8 @@ void CActiveAESink::GenerateNoise()
srcConfig.bits_per_sample = CAEUtil::DataFormatToUsedBits(m_sinkFormat.m_dataFormat);
srcConfig.dither_bits = CAEUtil::DataFormatToDitherBits(m_sinkFormat.m_dataFormat);
- resampler->Init(dstConfig, srcConfig,
- false, false, M_SQRT1_2, nullptr, AE_QUALITY_UNKNOWN, false);
+ resampler->Init(dstConfig, srcConfig, false, false, M_SQRT1_2, nullptr, AE_QUALITY_UNKNOWN, false,
+ 0.0);
resampler->Resample(m_sampleOfSilence.pkt->data, m_sampleOfSilence.pkt->max_nb_samples,
(uint8_t**)&noise, m_sampleOfSilence.pkt->max_nb_samples, 1.0);
diff --git a/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAEStream.cpp b/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAEStream.cpp
index bdf65d60ae..bf00b58840 100644
--- a/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAEStream.cpp
+++ b/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAEStream.cpp
@@ -143,13 +143,9 @@ void CActiveAEStream::InitRemapper()
srcConfig.bits_per_sample = CAEUtil::DataFormatToUsedBits(m_format.m_dataFormat);
srcConfig.dither_bits = CAEUtil::DataFormatToDitherBits(m_format.m_dataFormat);
- m_remapper->Init(dstConfig, srcConfig,
- false,
- false,
- M_SQRT1_2,
- &remapLayout,
+ m_remapper->Init(dstConfig, srcConfig, false, false, M_SQRT1_2, &remapLayout,
AE_QUALITY_LOW, // not used for remapping
- false);
+ false, 0.0f);
// extra sound packet, we can't resample to the same buffer
m_remapBuffer =
@@ -602,9 +598,10 @@ bool CActiveAEStreamBuffers::HasInputLevel(int level)
return false;
}
-bool CActiveAEStreamBuffers::Create(unsigned int totaltime, bool remap, bool upmix, bool normalize)
+bool CActiveAEStreamBuffers::Create(
+ unsigned int totaltime, bool remap, bool upmix, bool normalize, float sublevel)
{
- if (!m_resampleBuffers->Create(totaltime, remap, upmix, normalize))
+ if (!m_resampleBuffers->Create(totaltime, remap, upmix, normalize, sublevel))
return false;
if (!m_atempoBuffers->Create(totaltime))
@@ -654,9 +651,12 @@ bool CActiveAEStreamBuffers::ProcessBuffers()
return busy;
}
-void CActiveAEStreamBuffers::ConfigureResampler(bool normalizelevels, bool stereoupmix, AEQuality quality)
+void CActiveAEStreamBuffers::ConfigureResampler(bool normalizelevels,
+ bool stereoupmix,
+ AEQuality quality,
+ float sublevel)
{
- m_resampleBuffers->ConfigureResampler(normalizelevels, stereoupmix, quality);
+ m_resampleBuffers->ConfigureResampler(normalizelevels, stereoupmix, quality, sublevel);
}
float CActiveAEStreamBuffers::GetDelay()
diff --git a/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAEStream.h b/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAEStream.h
index 56b9e51f36..e29109fc26 100644
--- a/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAEStream.h
+++ b/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAEStream.h
@@ -96,10 +96,14 @@ class CActiveAEStreamBuffers
public:
CActiveAEStreamBuffers(const AEAudioFormat& inputFormat, const AEAudioFormat& outputFormat, AEQuality quality);
virtual ~CActiveAEStreamBuffers();
- bool Create(unsigned int totaltime, bool remap, bool upmix, bool normalize = true);
+ bool Create(
+ unsigned int totaltime, bool remap, bool upmix, bool normalize = true, float sublevel = 0.0f);
void SetExtraData(int profile, enum AVMatrixEncoding matrix_encoding, enum AVAudioServiceType audio_service_type);
bool ProcessBuffers();
- void ConfigureResampler(bool normalizelevels, bool stereoupmix, AEQuality quality);
+ void ConfigureResampler(bool normalizelevels,
+ bool stereoupmix,
+ AEQuality quality,
+ float sublevel);
bool HasInputLevel(int level);
float GetDelay();
void Flush();
diff --git a/xbmc/cores/AudioEngine/Interfaces/AEResample.h b/xbmc/cores/AudioEngine/Interfaces/AEResample.h
index 8b27b32b86..620ce56195 100644
--- a/xbmc/cores/AudioEngine/Interfaces/AEResample.h
+++ b/xbmc/cores/AudioEngine/Interfaces/AEResample.h
@@ -20,8 +20,15 @@ public:
virtual const char *GetName() = 0;
IAEResample() = default;
virtual ~IAEResample() = default;
- virtual bool Init(SampleConfig dstConfig, SampleConfig srcConfig, bool upmix, bool normalize, double centerMix,
- CAEChannelInfo *remapLayout, AEQuality quality, bool force_resample) = 0;
+ virtual bool Init(SampleConfig dstConfig,
+ SampleConfig srcConfig,
+ bool upmix,
+ bool normalize,
+ double centerMix,
+ CAEChannelInfo* remapLayout,
+ AEQuality quality,
+ bool force_resample,
+ float sublevel) = 0;
virtual int Resample(uint8_t **dst_buffer, int dst_samples, uint8_t **src_buffer, int src_samples, double ratio) = 0;
virtual int64_t GetDelay(int64_t base) = 0;
virtual int GetBufferedSamples() = 0;
diff --git a/xbmc/cores/AudioEngine/Sinks/AESinkAUDIOTRACK.cpp b/xbmc/cores/AudioEngine/Sinks/AESinkAUDIOTRACK.cpp
index e0a42c62a4..149bc7a268 100644
--- a/xbmc/cores/AudioEngine/Sinks/AESinkAUDIOTRACK.cpp
+++ b/xbmc/cores/AudioEngine/Sinks/AESinkAUDIOTRACK.cpp
@@ -329,12 +329,19 @@ bool CAESinkAUDIOTRACK::Initialize(AEAudioFormat &format, std::string &device)
m_encoding = AEStreamFormatToATFormat(m_format.m_streamInfo.m_type);
m_format.m_channelLayout = AE_CH_LAYOUT_2_0;
- if (m_format.m_streamInfo.m_type == CAEStreamInfo::STREAM_TYPE_DTSHD_MA ||
- m_format.m_streamInfo.m_type == CAEStreamInfo::STREAM_TYPE_TRUEHD)
+ if (m_format.m_streamInfo.m_type == CAEStreamInfo::STREAM_TYPE_DTSHD_MA)
{
+ // we keep the 48 khz sample rate, reason: Androids packer only packs DTS Core
+ // even if we ask for DTS-HD-MA it seems.
m_format.m_channelLayout = AE_CH_LAYOUT_7_1;
}
+ if (m_format.m_streamInfo.m_type == CAEStreamInfo::STREAM_TYPE_TRUEHD)
+ {
+ m_format.m_channelLayout = AE_CH_LAYOUT_7_1;
+ m_sink_sampleRate = 192000;
+ }
+
// EAC3 needs real samplerate not the modulation
if (m_format.m_streamInfo.m_type == CAEStreamInfo::STREAM_TYPE_EAC3)
m_sink_sampleRate = m_format.m_streamInfo.m_sampleRate;
@@ -1073,6 +1080,8 @@ void CAESinkAUDIOTRACK::UpdateAvailablePassthroughCapabilities(bool isRaw)
m_info.m_wantsIECPassthrough = false;
m_info.m_dataFormats.push_back(AE_FMT_RAW);
m_info.m_streamTypes.clear();
+ bool supports_192khz = m_sink_sampleRates.find(192000) != m_sink_sampleRates.end();
+
if (isRaw)
{
bool canDoAC3 = false;
@@ -1126,9 +1135,9 @@ void CAESinkAUDIOTRACK::UpdateAvailablePassthroughCapabilities(bool isRaw)
m_info.m_streamTypes.push_back(CAEStreamInfo::STREAM_TYPE_DTSHD_MA);
}
}
- if (CJNIAudioFormat::ENCODING_DOLBY_TRUEHD != -1)
+ if (CJNIAudioFormat::ENCODING_DOLBY_TRUEHD != -1 && supports_192khz)
{
- if (VerifySinkConfiguration(48000, AEChannelMapToAUDIOTRACKChannelMask(AE_CH_LAYOUT_7_1),
+ if (VerifySinkConfiguration(192000, AEChannelMapToAUDIOTRACKChannelMask(AE_CH_LAYOUT_7_1),
CJNIAudioFormat::ENCODING_DOLBY_TRUEHD, true))
{
CLog::Log(LOGDEBUG, "Firmware implements TrueHD RAW");
@@ -1146,7 +1155,6 @@ void CAESinkAUDIOTRACK::UpdateAvailablePassthroughCapabilities(bool isRaw)
CJNIAudioFormat::ENCODING_IEC61937);
if (supports_iec)
{
- bool supports_192khz = m_sink_sampleRates.find(192000) != m_sink_sampleRates.end();
m_info.m_wantsIECPassthrough = true;
m_info.m_streamTypes.clear();
m_info.m_dataFormats.push_back(AE_FMT_RAW);
diff --git a/xbmc/cores/AudioEngine/Sinks/AESinkWASAPI.cpp b/xbmc/cores/AudioEngine/Sinks/AESinkWASAPI.cpp
index 5877e9f319..02357c123d 100644
--- a/xbmc/cores/AudioEngine/Sinks/AESinkWASAPI.cpp
+++ b/xbmc/cores/AudioEngine/Sinks/AESinkWASAPI.cpp
@@ -254,8 +254,7 @@ unsigned int CAESinkWASAPI::AddPackets(uint8_t **data, unsigned int frames, unsi
return 0;
HRESULT hr;
- BYTE *buf;
- DWORD flags = 0;
+ BYTE* buf;
#ifndef _DEBUG
LARGE_INTEGER timerStart;
@@ -293,9 +292,8 @@ unsigned int CAESinkWASAPI::AddPackets(uint8_t **data, unsigned int frames, unsi
return INT_MAX;
}
- memset(buf, 0, NumFramesRequested * m_format.m_frameSize); //fill buffer with silence
-
- hr = m_pRenderClient->ReleaseBuffer(NumFramesRequested, flags); //pass back to audio driver
+ hr = m_pRenderClient->ReleaseBuffer(NumFramesRequested,
+ AUDCLNT_BUFFERFLAGS_SILENT); //pass back to audio driver
if (FAILED(hr))
{
#ifdef _DEBUG
@@ -359,7 +357,7 @@ unsigned int CAESinkWASAPI::AddPackets(uint8_t **data, unsigned int frames, unsi
NumFramesRequested * m_format.m_frameSize);
m_bufferPtr = 0;
- hr = m_pRenderClient->ReleaseBuffer(NumFramesRequested, flags); //pass back to audio driver
+ hr = m_pRenderClient->ReleaseBuffer(NumFramesRequested, 0); //pass back to audio driver
if (FAILED(hr))
{
#ifdef _DEBUG
@@ -396,7 +394,10 @@ void CAESinkWASAPI::EnumerateDevicesEx(AEDeviceInfoList &deviceInfoList, bool fo
deviceInfo.m_channels.Reset();
deviceInfo.m_dataFormats.clear();
deviceInfo.m_sampleRates.clear();
+ deviceInfo.m_streamTypes.clear();
deviceChannels.Reset();
+ add192 = false;
+ add48 = false;
for (unsigned int c = 0; c < WASAPI_SPEAKER_COUNT; c++)
{
@@ -592,8 +593,23 @@ void CAESinkWASAPI::EnumerateDevicesEx(AEDeviceInfoList &deviceInfoList, bool fo
wfxex.dwChannelMask = KSAUDIO_SPEAKER_STEREO;
wfxex.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
wfxex.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
- wfxex.Format.wBitsPerSample = 16;
- wfxex.Samples.wValidBitsPerSample = 16;
+
+ // 16 bits is most widely supported and likely to have the widest range of sample rates
+ if (deviceInfo.m_dataFormats.empty() ||
+ std::find(deviceInfo.m_dataFormats.cbegin(), deviceInfo.m_dataFormats.cend(),
+ AE_FMT_S16NE) != deviceInfo.m_dataFormats.cend())
+ {
+ wfxex.Format.wBitsPerSample = 16;
+ wfxex.Samples.wValidBitsPerSample = 16;
+ }
+ else
+ {
+ const AEDataFormat fmt = deviceInfo.m_dataFormats.front();
+ wfxex.Format.wBitsPerSample = CAEUtil::DataFormatToBits(fmt);
+ wfxex.Samples.wValidBitsPerSample =
+ (fmt == AE_FMT_S24NE4MSB ? 24 : wfxex.Format.wBitsPerSample);
+ }
+
wfxex.Format.nChannels = 2;
wfxex.Format.nBlockAlign = wfxex.Format.nChannels * (wfxex.Format.wBitsPerSample >> 3);
wfxex.Format.nAvgBytesPerSec = wfxex.Format.nSamplesPerSec * wfxex.Format.nBlockAlign;
@@ -745,17 +761,17 @@ bool CAESinkWASAPI::InitializeExclusive(AEAudioFormat &format)
else if (format.m_dataFormat == AE_FMT_RAW) //No sense in trying other formats for passthrough.
return false;
- CLog::Log(LOGWARNING,
- "AESinkWASAPI: IsFormatSupported failed ({}) - trying to find a compatible format",
- WASAPIErrToStr(hr));
+ CLog::LogF(LOGWARNING,
+ "format {} not supported by the device - trying to find a compatible format",
+ CAEUtil::DataFormatToStr(format.m_dataFormat));
requestedChannels = wfxex.Format.nChannels;
desired_map = CAESinkFactoryWin::SpeakerMaskFromAEChannels(format.m_channelLayout);
/* The requested format is not supported by the device. Find something that works */
- CLog::Log(LOGWARNING,
- "AESinkWASAPI: Input channels are [{}] - Trying to find a matching output layout",
- std::string(format.m_channelLayout));
+ CLog::LogF(LOGWARNING, "Input channels are [{}] - Trying to find a matching output layout",
+ std::string(format.m_channelLayout));
+
for (int layout = -1; layout <= (int)ARRAYSIZE(layoutsList); layout++)
{
// if requested layout is not supported, try standard layouts which contain
@@ -886,6 +902,20 @@ initialize:
format.m_sampleRate = wfxex.Format.nSamplesPerSec; //PCM: Sample rate. RAW: Link speed
format.m_frameSize = (wfxex.Format.wBitsPerSample >> 3) * wfxex.Format.nChannels;
+ ComPtr<IAudioClient2> audioClient2;
+ if (SUCCEEDED(m_pAudioClient.As(&audioClient2)))
+ {
+ AudioClientProperties props = {};
+ props.cbSize = sizeof(props);
+ // ForegroundOnlyMedia/BackgroundCapableMedia replaced in Windows 10 by Movie/Media
+ props.eCategory = CSysInfo::IsWindowsVersionAtLeast(CSysInfo::WindowsVersionWin10)
+ ? AudioCategory_Media
+ : AudioCategory_ForegroundOnlyMedia;
+
+ if (FAILED(hr = audioClient2->SetClientProperties(&props)))
+ CLog::LogF(LOGERROR, "unable to set audio category, {}", WASAPIErrToStr(hr));
+ }
+
REFERENCE_TIME audioSinkBufferDurationMsec, hnsLatency;
audioSinkBufferDurationMsec = (REFERENCE_TIME)500000;
diff --git a/xbmc/cores/AudioEngine/Sinks/AESinkXAudio.cpp b/xbmc/cores/AudioEngine/Sinks/AESinkXAudio.cpp
index 473495c168..792c8ffc6a 100644
--- a/xbmc/cores/AudioEngine/Sinks/AESinkXAudio.cpp
+++ b/xbmc/cores/AudioEngine/Sinks/AESinkXAudio.cpp
@@ -13,34 +13,65 @@
#include "cores/AudioEngine/Sinks/windows/AESinkFactoryWin.h"
#include "cores/AudioEngine/Utils/AEDeviceInfo.h"
#include "cores/AudioEngine/Utils/AEUtil.h"
-#include "utils/StringUtils.h"
-#include "utils/TimeUtils.h"
-#include "utils/XTimeUtils.h"
+#include "utils/SystemInfo.h"
#include "utils/log.h"
-#include "platform/win10/AsyncHelpers.h"
#include "platform/win32/CharsetConverter.h"
#include <algorithm>
#include <stdint.h>
#include <ksmedia.h>
-#include <mfapi.h>
-#include <mmdeviceapi.h>
-#include <mmreg.h>
-#include <wrl/implements.h>
using namespace Microsoft::WRL;
-extern const char *WASAPIErrToStr(HRESULT err);
+namespace
+{
+constexpr int XAUDIO_BUFFERS_IN_QUEUE = 2;
+
+HRESULT KXAudio2Create(IXAudio2** ppXAudio2,
+ UINT32 Flags X2DEFAULT(0),
+ XAUDIO2_PROCESSOR XAudio2Processor X2DEFAULT(XAUDIO2_DEFAULT_PROCESSOR))
+{
+ typedef HRESULT(__stdcall * XAudio2CreateInfoFunc)(_Outptr_ IXAudio2**, UINT32,
+ XAUDIO2_PROCESSOR);
+ static HMODULE dll = NULL;
+ static XAudio2CreateInfoFunc XAudio2CreateFn = nullptr;
+
+ if (dll == NULL)
+ {
+ dll = LoadLibraryEx(L"xaudio2_9redist.dll", NULL, LOAD_LIBRARY_SEARCH_DEFAULT_DIRS);
+
+ if (dll == NULL)
+ {
+ dll = LoadLibraryEx(L"xaudio2_9.dll", NULL, LOAD_LIBRARY_SEARCH_SYSTEM32);
+
+ if (dll == NULL)
+ {
+ // Windows 8 compatibility
+ dll = LoadLibraryEx(L"xaudio2_8.dll", NULL, LOAD_LIBRARY_SEARCH_SYSTEM32);
+
+ if (dll == NULL)
+ return HRESULT_FROM_WIN32(GetLastError());
+ }
+ }
-#define EXIT_ON_FAILURE(hr, reason, ...) \
- if (FAILED(hr)) \
- { \
- CLog::Log(LOGERROR, reason " - {}", __VA_ARGS__, WASAPIErrToStr(hr)); \
- goto failed; \
+ XAudio2CreateFn = (XAudio2CreateInfoFunc)(void*)GetProcAddress(dll, "XAudio2Create");
+ if (!XAudio2CreateFn)
+ {
+ return HRESULT_FROM_WIN32(GetLastError());
+ }
}
-#define XAUDIO_BUFFERS_IN_QUEUE 2
+
+ if (XAudio2CreateFn)
+ return (*XAudio2CreateFn)(ppXAudio2, Flags, XAudio2Processor);
+ else
+ return E_FAIL;
+}
+
+} // namespace
+
+extern const char* WASAPIErrToStr(HRESULT err);
template <class TVoice>
inline void SafeDestroyVoice(TVoice **ppVoice)
@@ -52,36 +83,14 @@ inline void SafeDestroyVoice(TVoice **ppVoice)
}
}
-/// ----------------- CAESinkXAudio ------------------------
-
-CAESinkXAudio::CAESinkXAudio() :
- m_masterVoice(nullptr),
- m_sourceVoice(nullptr),
- m_encodedChannels(0),
- m_encodedSampleRate(0),
- sinkReqFormat(AE_FMT_INVALID),
- sinkRetFormat(AE_FMT_INVALID),
- m_AvgBytesPerSec(0),
- m_dwChunkSize(0),
- m_dwFrameSize(0),
- m_dwBufferLen(0),
- m_sinkFrames(0),
- m_framesInBuffers(0),
- m_running(false),
- m_initialized(false),
- m_isSuspended(false),
- m_isDirty(false),
- m_uiBufferLen(0),
- m_avgTimeWaiting(50)
+CAESinkXAudio::CAESinkXAudio()
{
- m_channelLayout.Reset();
-
- HRESULT hr = XAudio2Create(m_xAudio2.ReleaseAndGetAddressOf(), 0);
+ HRESULT hr = KXAudio2Create(m_xAudio2.ReleaseAndGetAddressOf(), 0);
if (FAILED(hr))
{
- CLog::LogF(LOGERROR, "XAudio initialization failed.");
+ CLog::LogF(LOGERROR, "XAudio initialization failed, error {:X}.", hr);
}
-#ifdef _DEBUG
+#ifdef _DEBUG
else
{
XAUDIO2_DEBUG_CONFIGURATION config = {};
@@ -91,7 +100,10 @@ CAESinkXAudio::CAESinkXAudio() :
config.LogFunctionName = true;
m_xAudio2->SetDebugConfiguration(&config, 0);
}
-#endif // _DEBUG
+#endif // _DEBUG
+
+ // Get performance counter frequency for latency calculations
+ QueryPerformanceFrequency(&m_timerFreq);
}
CAESinkXAudio::~CAESinkXAudio()
@@ -112,6 +124,13 @@ void CAESinkXAudio::Register()
std::unique_ptr<IAESink> CAESinkXAudio::Create(std::string& device, AEAudioFormat& desiredFormat)
{
auto sink = std::make_unique<CAESinkXAudio>();
+
+ if (!sink->m_xAudio2)
+ {
+ CLog::LogF(LOGERROR, "XAudio2 not loaded.");
+ return {};
+ }
+
if (sink->Initialize(desiredFormat, device))
return sink;
@@ -123,32 +142,20 @@ bool CAESinkXAudio::Initialize(AEAudioFormat &format, std::string &device)
if (m_initialized)
return false;
- m_device = device;
- bool bdefault = false;
- HRESULT hr = S_OK;
-
/* Save requested format */
- /* Clear returned format */
- sinkReqFormat = format.m_dataFormat;
- sinkRetFormat = AE_FMT_INVALID;
+ AEDataFormat reqFormat = format.m_dataFormat;
if (!InitializeInternal(device, format))
{
- CLog::Log(LOGINFO, __FUNCTION__": Could not Initialize voices with that format");
- goto failed;
+ CLog::LogF(LOGINFO, "could not Initialize voices with format {}",
+ CAEUtil::DataFormatToStr(reqFormat));
+ CLog::LogF(LOGERROR, "XAudio initialization failed");
+ return false;
}
- format.m_frames = m_uiBufferLen;
- m_format = format;
- sinkRetFormat = format.m_dataFormat;
-
m_initialized = true;
- m_isDirty = false;
-
- return true;
+ m_isDirty = false;
-failed:
- CLog::Log(LOGERROR, __FUNCTION__": XAudio initialization failed.");
return true;
}
@@ -163,12 +170,26 @@ void CAESinkXAudio::Deinitialize()
{
m_sourceVoice->Stop();
m_sourceVoice->FlushSourceBuffers();
+
+ // Stop and FlushSourceBuffers are async, wait for queued buffers to be released by XAudio2.
+ // callbacks don't seem to be called otherwise, with memory leakage.
+ XAUDIO2_VOICE_STATE state{};
+ do
+ {
+ if (WAIT_OBJECT_0 != WaitForSingleObject(m_voiceCallback.mBufferEnd.get(), 500))
+ {
+ CLog::LogF(LOGERROR, "timeout waiting for buffer flush - possible buffer memory leak");
+ break;
+ }
+ m_sourceVoice->GetState(&state, 0);
+ } while (state.BuffersQueued > 0);
+
m_sinkFrames = 0;
m_framesInBuffers = 0;
}
catch (...)
{
- CLog::Log(LOGDEBUG, "{}: Invalidated source voice - Releasing", __FUNCTION__);
+ CLog::LogF(LOGERROR, "invalidated source voice - Releasing");
}
}
m_running = false;
@@ -179,20 +200,8 @@ void CAESinkXAudio::Deinitialize()
m_initialized = false;
}
-/**
- * @brief rescale uint64_t without overflowing on large values
- */
-static uint64_t rescale_u64(uint64_t val, uint64_t num, uint64_t den)
-{
- return ((val / den) * num) + (((val % den) * num) / den);
-}
-
void CAESinkXAudio::GetDelay(AEDelayStatus& status)
{
- HRESULT hr = S_OK;
- uint64_t pos = 0, tick = 0;
- int retries = 0;
-
if (!m_initialized)
{
status.SetDelay(0.0);
@@ -202,8 +211,7 @@ void CAESinkXAudio::GetDelay(AEDelayStatus& status)
XAUDIO2_VOICE_STATE state;
m_sourceVoice->GetState(&state, 0);
- double delay = (double)(m_sinkFrames - state.SamplesPlayed) / m_format.m_sampleRate;
- status.SetDelay(delay);
+ status.SetDelay(static_cast<double>(m_sinkFrames - state.SamplesPlayed) / m_format.m_sampleRate);
return;
}
@@ -212,7 +220,7 @@ double CAESinkXAudio::GetCacheTotal()
if (!m_initialized)
return 0.0;
- return XAUDIO_BUFFERS_IN_QUEUE * m_format.m_frames / (double)m_format.m_sampleRate;
+ return static_cast<double>(XAUDIO_BUFFERS_IN_QUEUE * m_format.m_frames) / m_format.m_sampleRate;
}
double CAESinkXAudio::GetLatency()
@@ -223,7 +231,7 @@ double CAESinkXAudio::GetLatency()
XAUDIO2_PERFORMANCE_DATA perfData;
m_xAudio2->GetPerformanceData(&perfData);
- return perfData.CurrentLatencyInSamples / (double) m_format.m_sampleRate;
+ return static_cast<double>(perfData.CurrentLatencyInSamples) / m_format.m_sampleRate;
}
unsigned int CAESinkXAudio::AddPackets(uint8_t **data, unsigned int frames, unsigned int offset)
@@ -232,25 +240,11 @@ unsigned int CAESinkXAudio::AddPackets(uint8_t **data, unsigned int frames, unsi
return 0;
HRESULT hr = S_OK;
- DWORD flags = 0;
-#ifndef _DEBUG
LARGE_INTEGER timerStart;
LARGE_INTEGER timerStop;
- LARGE_INTEGER timerFreq;
-#endif
- size_t dataLenght = frames * m_format.m_frameSize;
- struct buffer_ctx *ctx = new buffer_ctx;
- ctx->data = new uint8_t[dataLenght];
- ctx->frames = frames;
- ctx->sink = this;
- memcpy(ctx->data, data[0] + offset * m_format.m_frameSize, dataLenght);
-
- XAUDIO2_BUFFER xbuffer = {};
- xbuffer.AudioBytes = dataLenght;
- xbuffer.pAudioData = ctx->data;
- xbuffer.pContext = ctx;
+ XAUDIO2_BUFFER xbuffer = BuildXAudio2Buffer(data, frames, offset);
if (!m_running) //first time called, pre-fill buffer then start voice
{
@@ -259,7 +253,7 @@ unsigned int CAESinkXAudio::AddPackets(uint8_t **data, unsigned int frames, unsi
if (FAILED(hr))
{
CLog::LogF(LOGERROR, "voice submit buffer failed due to {}", WASAPIErrToStr(hr));
- delete ctx;
+ delete xbuffer.pContext;
return 0;
}
hr = m_sourceVoice->Start(0, XAUDIO2_COMMIT_NOW);
@@ -267,7 +261,7 @@ unsigned int CAESinkXAudio::AddPackets(uint8_t **data, unsigned int frames, unsi
{
CLog::LogF(LOGERROR, "voice start failed due to {}", WASAPIErrToStr(hr));
m_isDirty = true; //flag new device or re-init needed
- delete ctx;
+ delete xbuffer.pContext;
return INT_MAX;
}
m_sinkFrames += frames;
@@ -276,15 +270,10 @@ unsigned int CAESinkXAudio::AddPackets(uint8_t **data, unsigned int frames, unsi
return frames;
}
-#ifndef _DEBUG
/* Get clock time for latency checks */
- QueryPerformanceFrequency(&timerFreq);
QueryPerformanceCounter(&timerStart);
-#endif
/* Wait for Audio Driver to tell us it's got a buffer available */
- //XAUDIO2_VOICE_STATE state;
- //while (m_sourceVoice->GetState(&state), state.BuffersQueued >= XAUDIO_BUFFERS_IN_QUEUE)
while (m_format.m_frames * XAUDIO_BUFFERS_IN_QUEUE <= m_framesInBuffers.load())
{
DWORD eventAudioCallback;
@@ -292,7 +281,7 @@ unsigned int CAESinkXAudio::AddPackets(uint8_t **data, unsigned int frames, unsi
if (eventAudioCallback != WAIT_OBJECT_0)
{
CLog::LogF(LOGERROR, "voice buffer timed out");
- delete ctx;
+ delete xbuffer.pContext;
return INT_MAX;
}
}
@@ -300,10 +289,9 @@ unsigned int CAESinkXAudio::AddPackets(uint8_t **data, unsigned int frames, unsi
if (!m_running)
return 0;
-#ifndef _DEBUG
QueryPerformanceCounter(&timerStop);
- LONGLONG timerDiff = timerStop.QuadPart - timerStart.QuadPart;
- double timerElapsed = (double) timerDiff * 1000.0 / (double) timerFreq.QuadPart;
+ const LONGLONG timerDiff = timerStop.QuadPart - timerStart.QuadPart;
+ const double timerElapsed = static_cast<double>(timerDiff) * 1000.0 / m_timerFreq.QuadPart;
m_avgTimeWaiting += (timerElapsed - m_avgTimeWaiting) * 0.5;
if (m_avgTimeWaiting < 3.0)
@@ -311,15 +299,12 @@ unsigned int CAESinkXAudio::AddPackets(uint8_t **data, unsigned int frames, unsi
CLog::LogF(LOGDEBUG, "Possible AQ Loss: Avg. Time Waiting for Audio Driver callback : {}msec",
(int)m_avgTimeWaiting);
}
-#endif
hr = m_sourceVoice->SubmitSourceBuffer(&xbuffer);
if (FAILED(hr))
{
- #ifdef _DEBUG
CLog::LogF(LOGERROR, "submiting buffer failed due to {}", WASAPIErrToStr(hr));
-#endif
- delete ctx;
+ delete xbuffer.pContext;
return INT_MAX;
}
@@ -331,180 +316,62 @@ unsigned int CAESinkXAudio::AddPackets(uint8_t **data, unsigned int frames, unsi
void CAESinkXAudio::EnumerateDevicesEx(AEDeviceInfoList &deviceInfoList, bool force)
{
- HRESULT hr = S_OK, hr2 = S_OK;
+ HRESULT hr = S_OK;
CAEDeviceInfo deviceInfo;
CAEChannelInfo deviceChannels;
WAVEFORMATEXTENSIBLE wfxex = {};
- bool add192 = false;
-
- UINT32 eflags = 0;// XAUDIO2_DEBUG_ENGINE;
-
+ UINT32 eflags = 0; // XAUDIO2_DEBUG_ENGINE;
IXAudio2MasteringVoice* mMasterVoice = nullptr;
IXAudio2SourceVoice* mSourceVoice = nullptr;
Microsoft::WRL::ComPtr<IXAudio2> xaudio2;
- hr = XAudio2Create(xaudio2.ReleaseAndGetAddressOf(), eflags);
+
+ // ForegroundOnlyMedia/BackgroundCapableMedia replaced in Windows 10 by Movie/Media
+ const AUDIO_STREAM_CATEGORY streamCategory{
+ CSysInfo::IsWindowsVersionAtLeast(CSysInfo::WindowsVersionWin10)
+ ? AudioCategory_Media
+ : AudioCategory_ForegroundOnlyMedia};
+
+ hr = KXAudio2Create(xaudio2.ReleaseAndGetAddressOf(), eflags);
if (FAILED(hr))
{
- CLog::Log(LOGDEBUG, __FUNCTION__": Failed to activate XAudio for capability testing.");
- goto failed;
+ CLog::LogF(LOGERROR, "failed to activate XAudio for capability testing ({})",
+ WASAPIErrToStr(hr));
+ return;
}
- for(RendererDetail& details : CAESinkFactoryWin::GetRendererDetails())
+ for (RendererDetail& details : CAESinkFactoryWin::GetRendererDetailsWinRT())
{
deviceInfo.m_channels.Reset();
deviceInfo.m_dataFormats.clear();
deviceInfo.m_sampleRates.clear();
+ deviceChannels.Reset();
- std::wstring deviceId = KODI::PLATFORM::WINDOWS::ToW(details.strDeviceId);
-
- /* Test format DTS-HD-MA */
- wfxex.Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX);
- wfxex.Format.nSamplesPerSec = 192000;
- wfxex.dwChannelMask = KSAUDIO_SPEAKER_7POINT1_SURROUND;
- wfxex.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
- wfxex.SubFormat = KSDATAFORMAT_SUBTYPE_IEC61937_DTS_HD;
- wfxex.Format.wBitsPerSample = 16;
- wfxex.Samples.wValidBitsPerSample = 16;
- wfxex.Format.nChannels = 8;
- wfxex.Format.nBlockAlign = wfxex.Format.nChannels * (wfxex.Format.wBitsPerSample >> 3);
- wfxex.Format.nAvgBytesPerSec = wfxex.Format.nSamplesPerSec * wfxex.Format.nBlockAlign;
-
- hr2 = xaudio2->CreateMasteringVoice(&mMasterVoice, wfxex.Format.nChannels, wfxex.Format.nSamplesPerSec,
- 0, deviceId.c_str(), nullptr, AudioCategory_Media);
- hr = xaudio2->CreateSourceVoice(&mSourceVoice, &wfxex.Format);
-
- if (FAILED(hr))
- {
- CLog::Log(
- LOGINFO, __FUNCTION__ ": stream type \"{}\" on device \"{}\" seems to be not supported.",
- CAEUtil::StreamTypeToStr(CAEStreamInfo::STREAM_TYPE_DTSHD_MA), details.strDescription);
- }
- else
+ for (unsigned int c = 0; c < WASAPI_SPEAKER_COUNT; c++)
{
- deviceInfo.m_streamTypes.push_back(CAEStreamInfo::STREAM_TYPE_DTSHD_MA);
- add192 = true;
+ if (details.uiChannelMask & WASAPIChannelOrder[c])
+ deviceChannels += AEChannelNames[c];
}
- SafeDestroyVoice(&mSourceVoice);
- /* Test format DTS-HD-HR */
- wfxex.Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX);
- wfxex.Format.nSamplesPerSec = 192000;
- wfxex.dwChannelMask = KSAUDIO_SPEAKER_5POINT1;
- wfxex.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
- wfxex.SubFormat = KSDATAFORMAT_SUBTYPE_IEC61937_DTS_HD;
- wfxex.Format.wBitsPerSample = 16;
- wfxex.Samples.wValidBitsPerSample = 16;
- wfxex.Format.nChannels = 2;
- wfxex.Format.nBlockAlign = wfxex.Format.nChannels * (wfxex.Format.wBitsPerSample >> 3);
- wfxex.Format.nAvgBytesPerSec = wfxex.Format.nSamplesPerSec * wfxex.Format.nBlockAlign;
-
- hr2 = xaudio2->CreateMasteringVoice(&mMasterVoice, wfxex.Format.nChannels, wfxex.Format.nSamplesPerSec,
- 0, deviceId.c_str(), nullptr, AudioCategory_Media);
- hr = xaudio2->CreateSourceVoice(&mSourceVoice, &wfxex.Format);
-
- if (FAILED(hr))
- {
- CLog::Log(LOGINFO,
- __FUNCTION__ ": stream type \"{}\" on device \"{}\" seems to be not supported.",
- CAEUtil::StreamTypeToStr(CAEStreamInfo::STREAM_TYPE_DTSHD), details.strDescription);
- }
- else
- {
- deviceInfo.m_streamTypes.push_back(CAEStreamInfo::STREAM_TYPE_DTSHD);
- add192 = true;
- }
- SafeDestroyVoice(&mSourceVoice);
+ const std::wstring deviceId = KODI::PLATFORM::WINDOWS::ToW(details.strDeviceId);
- /* Test format Dolby TrueHD */
- wfxex.SubFormat = KSDATAFORMAT_SUBTYPE_IEC61937_DOLBY_MLP;
- wfxex.Format.nChannels = 8;
- wfxex.dwChannelMask = KSAUDIO_SPEAKER_7POINT1_SURROUND;
-
- hr = xaudio2->CreateSourceVoice(&mSourceVoice, &wfxex.Format);
- if (FAILED(hr))
- {
- CLog::Log(
- LOGINFO, __FUNCTION__ ": stream type \"{}\" on device \"{}\" seems to be not supported.",
- CAEUtil::StreamTypeToStr(CAEStreamInfo::STREAM_TYPE_TRUEHD), details.strDescription);
- }
- else
- {
- deviceInfo.m_streamTypes.push_back(CAEStreamInfo::STREAM_TYPE_TRUEHD);
- add192 = true;
- }
-
- /* Test format Dolby EAC3 */
- wfxex.SubFormat = KSDATAFORMAT_SUBTYPE_IEC61937_DOLBY_DIGITAL_PLUS;
- wfxex.Format.nChannels = 2;
- wfxex.Format.nBlockAlign = wfxex.Format.nChannels * (wfxex.Format.wBitsPerSample >> 3);
- wfxex.Format.nAvgBytesPerSec = wfxex.Format.nSamplesPerSec * wfxex.Format.nBlockAlign;
-
- SafeDestroyVoice(&mSourceVoice);
- SafeDestroyVoice(&mMasterVoice);
- hr2 = xaudio2->CreateMasteringVoice(&mMasterVoice, wfxex.Format.nChannels, wfxex.Format.nSamplesPerSec,
- 0, deviceId.c_str(), nullptr, AudioCategory_Media);
- hr = xaudio2->CreateSourceVoice(&mSourceVoice, &wfxex.Format);
-
- if (FAILED(hr))
- {
- CLog::Log(LOGINFO,
- __FUNCTION__ ": stream type \"{}\" on device \"{}\" seems to be not supported.",
- CAEUtil::StreamTypeToStr(CAEStreamInfo::STREAM_TYPE_EAC3), details.strDescription);
- }
- else
- {
- deviceInfo.m_streamTypes.push_back(CAEStreamInfo::STREAM_TYPE_EAC3);
- add192 = true;
- }
-
- /* Test format DTS */
+ /* Test format for PCM format iteration */
+ wfxex.Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX);
wfxex.Format.nSamplesPerSec = 48000;
- wfxex.dwChannelMask = KSAUDIO_SPEAKER_5POINT1;
- wfxex.SubFormat = KSDATAFORMAT_SUBTYPE_IEC61937_DTS;
- wfxex.Format.nBlockAlign = wfxex.Format.nChannels * (wfxex.Format.wBitsPerSample >> 3);
- wfxex.Format.nAvgBytesPerSec = wfxex.Format.nSamplesPerSec * wfxex.Format.nBlockAlign;
-
- SafeDestroyVoice(&mSourceVoice);
- SafeDestroyVoice(&mMasterVoice);
- hr2 = xaudio2->CreateMasteringVoice(&mMasterVoice, wfxex.Format.nChannels, wfxex.Format.nSamplesPerSec,
- 0, deviceId.c_str(), nullptr, AudioCategory_Media);
- hr = xaudio2->CreateSourceVoice(&mSourceVoice, &wfxex.Format);
- if (FAILED(hr))
- {
- CLog::Log(LOGINFO,
- __FUNCTION__ ": stream type \"{}\" on device \"{}\" seems to be not supported.",
- "STREAM_TYPE_DTS", details.strDescription);
- }
- else
- {
- deviceInfo.m_streamTypes.push_back(CAEStreamInfo::STREAM_TYPE_DTSHD_CORE);
- deviceInfo.m_streamTypes.push_back(CAEStreamInfo::STREAM_TYPE_DTS_2048);
- deviceInfo.m_streamTypes.push_back(CAEStreamInfo::STREAM_TYPE_DTS_1024);
- deviceInfo.m_streamTypes.push_back(CAEStreamInfo::STREAM_TYPE_DTS_512);
- }
- SafeDestroyVoice(&mSourceVoice);
+ wfxex.Format.nChannels = 2;
+ wfxex.dwChannelMask = KSAUDIO_SPEAKER_STEREO;
+ wfxex.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
+ wfxex.SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
- /* Test format Dolby AC3 */
- wfxex.SubFormat = KSDATAFORMAT_SUBTYPE_IEC61937_DOLBY_DIGITAL;
+ hr = xaudio2->CreateMasteringVoice(&mMasterVoice, wfxex.Format.nChannels,
+ wfxex.Format.nSamplesPerSec, 0, deviceId.c_str(), nullptr,
+ streamCategory);
- hr = xaudio2->CreateSourceVoice(&mSourceVoice, &wfxex.Format);
if (FAILED(hr))
{
- CLog::Log(LOGINFO,
- __FUNCTION__ ": stream type \"{}\" on device \"{}\" seems to be not supported.",
- CAEUtil::StreamTypeToStr(CAEStreamInfo::STREAM_TYPE_AC3), details.strDescription);
- }
- else
- {
- deviceInfo.m_streamTypes.push_back(CAEStreamInfo::STREAM_TYPE_AC3);
+ CLog::LogF(LOGERROR, "failed to create mastering voice (:X)", hr);
+ return;
}
- /* Test format for PCM format iteration */
- wfxex.Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX);
- wfxex.dwChannelMask = KSAUDIO_SPEAKER_STEREO;
- wfxex.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
- wfxex.SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
-
for (int p = AE_FMT_FLOAT; p > AE_FMT_INVALID; p--)
{
if (p < AE_FMT_FLOAT)
@@ -546,25 +413,27 @@ void CAESinkXAudio::EnumerateDevicesEx(AEDeviceInfoList &deviceInfoList, bool fo
for (int j = 0; j < WASAPISampleRateCount; j++)
{
+ if (WASAPISampleRates[j] < XAUDIO2_MIN_SAMPLE_RATE ||
+ WASAPISampleRates[j] > XAUDIO2_MAX_SAMPLE_RATE)
+ continue;
+
SafeDestroyVoice(&mSourceVoice);
SafeDestroyVoice(&mMasterVoice);
wfxex.Format.nSamplesPerSec = WASAPISampleRates[j];
wfxex.Format.nAvgBytesPerSec = wfxex.Format.nSamplesPerSec * wfxex.Format.nBlockAlign;
- hr2 = xaudio2->CreateMasteringVoice(&mMasterVoice, wfxex.Format.nChannels, wfxex.Format.nSamplesPerSec,
- 0, deviceId.c_str(), nullptr, AudioCategory_Media);
- hr = xaudio2->CreateSourceVoice(&mSourceVoice, &wfxex.Format);
- if (SUCCEEDED(hr))
- deviceInfo.m_sampleRates.push_back(WASAPISampleRates[j]);
- else if (wfxex.Format.nSamplesPerSec == 192000 && add192)
+ if (SUCCEEDED(xaudio2->CreateMasteringVoice(&mMasterVoice, wfxex.Format.nChannels,
+ wfxex.Format.nSamplesPerSec, 0, deviceId.c_str(),
+ nullptr, streamCategory)))
{
- deviceInfo.m_sampleRates.push_back(WASAPISampleRates[j]);
- CLog::Log(LOGINFO,
- __FUNCTION__ ": sample rate 192khz on device \"{}\" seems to be not supported.",
- details.strDescription);
+ hr = xaudio2->CreateSourceVoice(&mSourceVoice, &wfxex.Format);
+
+ if (SUCCEEDED(hr))
+ deviceInfo.m_sampleRates.push_back(WASAPISampleRates[j]);
}
}
+
SafeDestroyVoice(&mSourceVoice);
SafeDestroyVoice(&mMasterVoice);
@@ -572,7 +441,7 @@ void CAESinkXAudio::EnumerateDevicesEx(AEDeviceInfoList &deviceInfoList, bool fo
deviceInfo.m_displayName = details.strWinDevType.append(details.strDescription);
deviceInfo.m_displayNameExtra = std::string("XAudio: ").append(details.strDescription);
deviceInfo.m_deviceType = details.eDeviceType;
- deviceInfo.m_channels = layoutsByChCount[details.nChannels];
+ deviceInfo.m_channels = deviceChannels;
/* Store the device info */
deviceInfo.m_wantsIECPassthrough = true;
@@ -593,19 +462,11 @@ void CAESinkXAudio::EnumerateDevicesEx(AEDeviceInfoList &deviceInfoList, bool fo
deviceInfoList.push_back(deviceInfo);
}
}
-
-failed:
-
- if (FAILED(hr))
- CLog::Log(LOGERROR, __FUNCTION__ ": Failed to enumerate XAudio endpoint devices ({}).",
- WASAPIErrToStr(hr));
}
-/// ------------------- Private utility functions -----------------------------------
-
bool CAESinkXAudio::InitializeInternal(std::string deviceId, AEAudioFormat &format)
{
- std::wstring device = KODI::PLATFORM::WINDOWS::ToW(deviceId);
+ const std::wstring device = KODI::PLATFORM::WINDOWS::ToW(deviceId);
WAVEFORMATEXTENSIBLE wfxex = {};
if ( format.m_dataFormat <= AE_FMT_FLOAT
@@ -637,36 +498,43 @@ bool CAESinkXAudio::InitializeInternal(std::string deviceId, AEAudioFormat &form
wfxex.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
}
- bool bdefault = StringUtils::EndsWithNoCase(deviceId, std::string("default"));
+ const bool bdefault = deviceId.find("default") != std::string::npos;
HRESULT hr;
IXAudio2MasteringVoice* pMasterVoice = nullptr;
+ const wchar_t* pDevice = device.c_str();
+ // ForegroundOnlyMedia/BackgroundCapableMedia replaced in Windows 10 by Movie/Media
+ const AUDIO_STREAM_CATEGORY streamCategory{
+ CSysInfo::IsWindowsVersionAtLeast(CSysInfo::WindowsVersionWin10)
+ ? AudioCategory_Media
+ : AudioCategory_ForegroundOnlyMedia};
if (!bdefault)
{
- hr = m_xAudio2->CreateMasteringVoice(&pMasterVoice, wfxex.Format.nChannels, wfxex.Format.nSamplesPerSec,
- 0, device.c_str(), nullptr, AudioCategory_Media);
+ hr = m_xAudio2->CreateMasteringVoice(&pMasterVoice, wfxex.Format.nChannels,
+ wfxex.Format.nSamplesPerSec, 0, pDevice, nullptr,
+ streamCategory);
}
if (!pMasterVoice)
{
if (!bdefault)
{
- CLog::Log(LOGINFO,
- __FUNCTION__ ": Could not locate the device named \"{}\" in the list of Xaudio "
- "endpoint devices. Trying the default device...",
- KODI::PLATFORM::WINDOWS::FromW(device));
+ CLog::LogF(LOGINFO,
+ "could not locate the device named \"{}\" in the list of Xaudio endpoint devices. "
+ "Trying the default device...",
+ KODI::PLATFORM::WINDOWS::FromW(device));
}
-
// smartphone issue: providing device ID (even default ID) causes E_NOINTERFACE result
// workaround: device = nullptr will initialize default audio endpoint
- hr = m_xAudio2->CreateMasteringVoice(&pMasterVoice, wfxex.Format.nChannels, wfxex.Format.nSamplesPerSec,
- 0, 0, nullptr, AudioCategory_Media);
+ pDevice = nullptr;
+ hr = m_xAudio2->CreateMasteringVoice(&pMasterVoice, wfxex.Format.nChannels,
+ wfxex.Format.nSamplesPerSec, 0, pDevice, nullptr,
+ streamCategory);
if (FAILED(hr) || !pMasterVoice)
{
- CLog::Log(LOGINFO,
- __FUNCTION__ ": Could not retrieve the default XAudio audio endpoint ({}).",
- WASAPIErrToStr(hr));
+ CLog::LogF(LOGINFO, "Could not retrieve the default XAudio audio endpoint ({}).",
+ WASAPIErrToStr(hr));
return false;
}
}
@@ -680,7 +548,7 @@ bool CAESinkXAudio::InitializeInternal(std::string deviceId, AEAudioFormat &form
hr = m_xAudio2->CreateSourceVoice(&m_sourceVoice, &wfxex.Format, 0, XAUDIO2_DEFAULT_FREQ_RATIO, &m_voiceCallback);
if (SUCCEEDED(hr))
{
- CLog::Log(LOGINFO, __FUNCTION__": Format is Supported - will attempt to Initialize");
+ CLog::LogF(LOGINFO, "Format is Supported - will attempt to Initialize");
goto initialize;
}
@@ -688,9 +556,9 @@ bool CAESinkXAudio::InitializeInternal(std::string deviceId, AEAudioFormat &form
return false;
if (CServiceBroker::GetLogging().CanLogComponent(LOGAUDIO))
- CLog::Log(LOGDEBUG,
- __FUNCTION__ ": CreateSourceVoice failed ({}) - trying to find a compatible format",
- WASAPIErrToStr(hr));
+ CLog::LogFC(LOGDEBUG, LOGAUDIO,
+ "CreateSourceVoice failed ({}) - trying to find a compatible format",
+ WASAPIErrToStr(hr));
requestedChannels = wfxex.Format.nChannels;
@@ -725,11 +593,19 @@ bool CAESinkXAudio::InitializeInternal(std::string deviceId, AEAudioFormat &form
for (int i = 0 ; i < WASAPISampleRateCount; i++)
{
+ if (WASAPISampleRates[j] < XAUDIO2_MIN_SAMPLE_RATE ||
+ WASAPISampleRates[j] > XAUDIO2_MAX_SAMPLE_RATE)
+ continue;
+
+ SafeDestroyVoice(&m_sourceVoice);
+ SafeDestroyVoice(&m_masterVoice);
+
wfxex.Format.nSamplesPerSec = WASAPISampleRates[i];
wfxex.Format.nAvgBytesPerSec = wfxex.Format.nSamplesPerSec * wfxex.Format.nBlockAlign;
- hr = m_xAudio2->CreateMasteringVoice(&m_masterVoice, wfxex.Format.nChannels, wfxex.Format.nSamplesPerSec,
- 0, device.c_str(), nullptr, AudioCategory_Media);
+ hr = m_xAudio2->CreateMasteringVoice(&m_masterVoice, wfxex.Format.nChannels,
+ wfxex.Format.nSamplesPerSec, 0, pDevice, nullptr,
+ streamCategory);
if (SUCCEEDED(hr))
{
hr = m_xAudio2->CreateSourceVoice(&m_sourceVoice, &wfxex.Format, 0, XAUDIO2_DEFAULT_FREQ_RATIO, &m_voiceCallback);
@@ -745,19 +621,30 @@ bool CAESinkXAudio::InitializeInternal(std::string deviceId, AEAudioFormat &form
}
if (FAILED(hr))
- CLog::Log(LOGERROR, __FUNCTION__ ": creating voices failed ({})", WASAPIErrToStr(hr));
+ CLog::LogF(LOGERROR, "creating voices failed ({})", WASAPIErrToStr(hr));
}
if (closestMatch >= 0)
{
+ // Closest match may be different from the last successful sample rate tested
+ SafeDestroyVoice(&m_sourceVoice);
+ SafeDestroyVoice(&m_masterVoice);
+
wfxex.Format.nSamplesPerSec = WASAPISampleRates[closestMatch];
wfxex.Format.nAvgBytesPerSec = wfxex.Format.nSamplesPerSec * wfxex.Format.nBlockAlign;
- goto initialize;
+
+ if (SUCCEEDED(m_xAudio2->CreateMasteringVoice(&m_masterVoice, wfxex.Format.nChannels,
+ wfxex.Format.nSamplesPerSec, 0, pDevice,
+ nullptr, streamCategory)) &&
+ SUCCEEDED(m_xAudio2->CreateSourceVoice(&m_sourceVoice, &wfxex.Format, 0,
+ XAUDIO2_DEFAULT_FREQ_RATIO, &m_voiceCallback)))
+ goto initialize;
}
}
}
- CLog::Log(LOGERROR, __FUNCTION__": Unable to locate a supported output format for the device. Check the speaker settings in the control panel.");
+ CLog::LogF(LOGERROR, "unable to locate a supported output format for the device. Check the "
+ "speaker settings in the control panel.");
/* We couldn't find anything supported. This should never happen */
/* unless the user set the wrong speaker setting in the control panel */
@@ -765,13 +652,9 @@ bool CAESinkXAudio::InitializeInternal(std::string deviceId, AEAudioFormat &form
initialize:
- CAESinkFactoryWin::AEChannelsFromSpeakerMask(m_channelLayout, wfxex.dwChannelMask);
- format.m_channelLayout = m_channelLayout;
-
- /* When the stream is raw, the values in the format structure are set to the link */
- /* parameters, so store the encoded stream values here for the IsCompatible function */
- m_encodedChannels = wfxex.Format.nChannels;
- m_encodedSampleRate = (format.m_dataFormat == AE_FMT_RAW) ? format.m_streamInfo.m_sampleRate : format.m_sampleRate;
+ CAEChannelInfo channelLayout;
+ CAESinkFactoryWin::AEChannelsFromSpeakerMask(channelLayout, wfxex.dwChannelMask);
+ format.m_channelLayout = channelLayout;
/* Set up returned sink format for engine */
if (format.m_dataFormat != AE_FMT_RAW)
@@ -800,7 +683,7 @@ initialize:
hr = m_sourceVoice->Start(0, XAUDIO2_COMMIT_NOW);
if (FAILED(hr))
{
- CLog::Log(LOGERROR, __FUNCTION__ ": Voice start failed : {}", WASAPIErrToStr(hr));
+ CLog::LogF(LOGERROR, "Voice start failed : {}", WASAPIErrToStr(hr));
CLog::Log(LOGDEBUG, " Sample Rate : {}", wfxex.Format.nSamplesPerSec);
CLog::Log(LOGDEBUG, " Sample Format : {}", CAEUtil::DataFormatToStr(format.m_dataFormat));
CLog::Log(LOGDEBUG, " Bits Per Sample : {}", wfxex.Format.wBitsPerSample);
@@ -819,22 +702,36 @@ initialize:
m_xAudio2->GetPerformanceData(&perfData);
if (!perfData.TotalSourceVoiceCount)
{
- CLog::Log(LOGERROR, __FUNCTION__ ": GetPerformanceData Failed : {}", WASAPIErrToStr(hr));
+ CLog::LogF(LOGERROR, "GetPerformanceData Failed : {}", WASAPIErrToStr(hr));
return false;
}
- m_uiBufferLen = (int)(format.m_sampleRate * 0.02);
- m_dwFrameSize = wfxex.Format.nBlockAlign;
- m_dwChunkSize = m_dwFrameSize * m_uiBufferLen;
- m_dwBufferLen = m_dwChunkSize * 4;
- m_AvgBytesPerSec = wfxex.Format.nAvgBytesPerSec;
+ format.m_frames = static_cast<int>(format.m_sampleRate * 0.02); // 20 ms chunks
+
+ m_format = format;
- CLog::Log(LOGINFO, __FUNCTION__ ": XAudio Sink Initialized using: {}, {}, {}",
- CAEUtil::DataFormatToStr(format.m_dataFormat), wfxex.Format.nSamplesPerSec,
- wfxex.Format.nChannels);
+ CLog::LogF(LOGINFO, "XAudio Sink Initialized using: {}, {}, {}",
+ CAEUtil::DataFormatToStr(format.m_dataFormat), wfxex.Format.nSamplesPerSec,
+ wfxex.Format.nChannels);
m_sourceVoice->Stop();
+ CLog::LogF(LOGDEBUG, "Initializing XAudio with the following parameters:");
+ CLog::Log(LOGDEBUG, " Audio Device : {}", KODI::PLATFORM::WINDOWS::FromW(device));
+ CLog::Log(LOGDEBUG, " Sample Rate : {}", wfxex.Format.nSamplesPerSec);
+ CLog::Log(LOGDEBUG, " Sample Format : {}", CAEUtil::DataFormatToStr(format.m_dataFormat));
+ CLog::Log(LOGDEBUG, " Bits Per Sample : {}", wfxex.Format.wBitsPerSample);
+ CLog::Log(LOGDEBUG, " Valid Bits/Samp : {}", wfxex.Samples.wValidBitsPerSample);
+ CLog::Log(LOGDEBUG, " Channel Count : {}", wfxex.Format.nChannels);
+ CLog::Log(LOGDEBUG, " Block Align : {}", wfxex.Format.nBlockAlign);
+ CLog::Log(LOGDEBUG, " Avg. Bytes Sec : {}", wfxex.Format.nAvgBytesPerSec);
+ CLog::Log(LOGDEBUG, " Samples/Block : {}", wfxex.Samples.wSamplesPerBlock);
+ CLog::Log(LOGDEBUG, " Format cBSize : {}", wfxex.Format.cbSize);
+ CLog::Log(LOGDEBUG, " Channel Layout : {}", ((std::string)format.m_channelLayout));
+ CLog::Log(LOGDEBUG, " Channel Mask : {}", wfxex.dwChannelMask);
+ CLog::Log(LOGDEBUG, " Frames : {}", format.m_frames);
+ CLog::Log(LOGDEBUG, " Frame Size : {}", format.m_frameSize);
+
return true;
}
@@ -843,47 +740,95 @@ void CAESinkXAudio::Drain()
if(!m_sourceVoice)
return;
- AEDelayStatus status;
- GetDelay(status);
-
- KODI::TIME::Sleep(std::chrono::milliseconds(static_cast<int>(status.GetDelay() * 500)));
-
if (m_running)
{
try
{
+ // Contrary to MS doc, the voice must play a buffer with end of stream flag for the voice
+ // SamplesPlayed counter to be reset.
+ // Per MS doc, Discontinuity() may not take effect until after the entire buffer queue is
+ // consumed, which wouldn't invoke the callback or reset the voice stats.
+ // Solution: submit a 1 sample buffer with end of stream flag and wait for StreamEnd callback
+ // The StreamEnd event is manual reset so that it cannot be missed even if raised before this
+ // code starts waiting for it
+
+ AddEndOfStreamPacket();
+
+ constexpr uint32_t waitSafety = 100; // extra ms wait in case of scheduler issue
+ DWORD waitRc =
+ WaitForSingleObject(m_voiceCallback.m_StreamEndEvent,
+ m_framesInBuffers * 1000 / m_format.m_sampleRate + waitSafety);
+
+ if (waitRc != WAIT_OBJECT_0)
+ {
+ if (waitRc == WAIT_FAILED)
+ {
+ //! @todo use FormatMessage for a human readable error message
+ CLog::LogF(LOGERROR,
+ "error WAIT_FAILED while waiting for queued buffers to drain. GetLastError:{}",
+ GetLastError());
+ }
+ else
+ {
+ CLog::LogF(LOGERROR, "error {} while waiting for queued buffers to drain.", waitRc);
+ }
+ }
+
m_sourceVoice->Stop();
- m_sourceVoice->FlushSourceBuffers();
+ ResetEvent(m_voiceCallback.m_StreamEndEvent);
+
m_sinkFrames = 0;
m_framesInBuffers = 0;
}
catch (...)
{
- CLog::Log(LOGDEBUG, "{}: Invalidated source voice - Releasing", __FUNCTION__);
+ CLog::LogF(LOGERROR, "invalidated source voice - Releasing");
}
}
m_running = false;
}
-bool CAESinkXAudio::IsUSBDevice()
+bool CAESinkXAudio::AddEndOfStreamPacket()
{
-#if 0 // TODO
- IPropertyStore *pProperty = nullptr;
- PROPVARIANT varName;
- PropVariantInit(&varName);
- bool ret = false;
-
- HRESULT hr = m_pDevice->OpenPropertyStore(STGM_READ, &pProperty);
- if (!SUCCEEDED(hr))
- return ret;
- hr = pProperty->GetValue(PKEY_Device_EnumeratorName, &varName);
-
- std::string str = localWideToUtf(varName.pwszVal);
- StringUtils::ToUpper(str);
- ret = (str == "USB");
- PropVariantClear(&varName);
- if (pProperty)
- pProperty->Release();
-#endif
- return false;
+ constexpr unsigned int frames{1};
+
+ XAUDIO2_BUFFER xbuffer = BuildXAudio2Buffer(nullptr, frames, 0);
+ xbuffer.Flags = XAUDIO2_END_OF_STREAM;
+
+ HRESULT hr = m_sourceVoice->SubmitSourceBuffer(&xbuffer);
+
+ if (hr != S_OK)
+ {
+ CLog::LogF(LOGERROR, "SubmitSourceBuffer failed due to {:X}", hr);
+ delete xbuffer.pContext;
+ return false;
+ }
+
+ m_sinkFrames += frames;
+ m_framesInBuffers += frames;
+ return true;
+}
+
+XAUDIO2_BUFFER CAESinkXAudio::BuildXAudio2Buffer(uint8_t** data,
+ unsigned int frames,
+ unsigned int offset)
+{
+ const unsigned int dataLength{frames * m_format.m_frameSize};
+
+ struct buffer_ctx* ctx = new buffer_ctx;
+ ctx->data = new uint8_t[dataLength];
+ ctx->frames = frames;
+ ctx->sink = this;
+
+ if (data)
+ memcpy(ctx->data, data[0] + offset * m_format.m_frameSize, dataLength);
+ else
+ CAEUtil::GenerateSilence(m_format.m_dataFormat, m_format.m_frameSize, ctx->data, frames);
+
+ XAUDIO2_BUFFER xbuffer{};
+ xbuffer.AudioBytes = dataLength;
+ xbuffer.pAudioData = ctx->data;
+ xbuffer.pContext = ctx;
+
+ return xbuffer;
}
diff --git a/xbmc/cores/AudioEngine/Sinks/AESinkXAudio.h b/xbmc/cores/AudioEngine/Sinks/AESinkXAudio.h
index 6af7872732..1335714d09 100644
--- a/xbmc/cores/AudioEngine/Sinks/AESinkXAudio.h
+++ b/xbmc/cores/AudioEngine/Sinks/AESinkXAudio.h
@@ -13,14 +13,10 @@
#include <stdint.h>
-#include <mmdeviceapi.h>
-#include <ppltasks.h>
-#include <wrl/implements.h>
#include <x3daudio.h>
#include <xapofx.h>
#include <xaudio2.h>
#include <xaudio2fx.h>
-#pragma comment(lib,"xaudio2.lib")
class CAESinkXAudio : public IAESink
{
@@ -66,14 +62,27 @@ private:
mBufferEnd.reset(CreateEventEx(nullptr, nullptr, 0, EVENT_MODIFY_STATE | SYNCHRONIZE));
if (!mBufferEnd)
{
- throw std::exception("CreateEvent");
+ throw std::exception("CreateEventEx BufferEnd");
+ }
+ if (NULL == (m_StreamEndEvent = CreateEventEx(nullptr, nullptr, CREATE_EVENT_MANUAL_RESET,
+ EVENT_MODIFY_STATE | SYNCHRONIZE)))
+ {
+ throw std::exception("CreateEventEx StreamEnd");
}
}
- virtual ~VoiceCallback() { }
+ virtual ~VoiceCallback()
+ {
+ if (m_StreamEndEvent != NULL)
+ CloseHandle(m_StreamEndEvent);
+ }
STDMETHOD_(void, OnVoiceProcessingPassStart) (UINT32) override {}
STDMETHOD_(void, OnVoiceProcessingPassEnd)() override {}
- STDMETHOD_(void, OnStreamEnd)() override {}
+ STDMETHOD_(void, OnStreamEnd)() override
+ {
+ if (m_StreamEndEvent != NULL)
+ SetEvent(m_StreamEndEvent);
+ }
STDMETHOD_(void, OnBufferStart)(void*) override {}
STDMETHOD_(void, OnBufferEnd)(void* context) override
{
@@ -95,37 +104,43 @@ private:
}
};
std::unique_ptr<void, handle_closer> mBufferEnd;
+ HANDLE m_StreamEndEvent{0};
};
- bool InitializeInternal(std::string deviceId, AEAudioFormat &format);
- bool IsUSBDevice();
+ bool InitializeInternal(std::string deviceId, AEAudioFormat& format);
+
+ /*!
+ * \brief Add a 1 frame long buffer with the end of stream flag to the voice.
+ * \return true for success, false for failure
+ */
+ bool AddEndOfStreamPacket();
+ /*!
+ * \brief Create a XAUDIO2_BUFFER with a struct buffer_ctx in pContext member, which must
+ * be deleted either manually or by XAudio2 BufferEnd callbak to avoid memory leaks.
+ * \param data data of the frames to copy. if null, the new buffer will contain silence.
+ * \param frames number of frames
+ * \param offset offset from the start in the data buffer.
+ * \return the new buffer
+ */
+ XAUDIO2_BUFFER BuildXAudio2Buffer(uint8_t** data, unsigned int frames, unsigned int offset);
Microsoft::WRL::ComPtr<IXAudio2> m_xAudio2;
- IXAudio2MasteringVoice* m_masterVoice;
- IXAudio2SourceVoice* m_sourceVoice;
+ IXAudio2MasteringVoice* m_masterVoice{nullptr};
+ IXAudio2SourceVoice* m_sourceVoice{nullptr};
VoiceCallback m_voiceCallback;
AEAudioFormat m_format;
- unsigned int m_encodedChannels;
- unsigned int m_encodedSampleRate;
- CAEChannelInfo m_channelLayout;
- std::string m_device;
-
- enum AEDataFormat sinkReqFormat;
- enum AEDataFormat sinkRetFormat;
-
- unsigned int m_uiBufferLen; /* xaudio endpoint buffer size, in frames */
- unsigned int m_AvgBytesPerSec;
- unsigned int m_dwChunkSize;
- unsigned int m_dwFrameSize;
- unsigned int m_dwBufferLen;
- uint64_t m_sinkFrames;
- std::atomic<uint16_t> m_framesInBuffers;
-
- double m_avgTimeWaiting; /* time between next buffer of data from SoftAE and driver call for data */
-
- bool m_running;
- bool m_initialized;
- bool m_isSuspended; /* sink is in a suspended state - release audio device */
- bool m_isDirty; /* sink output failed - needs re-init or new device */
+
+ uint64_t m_sinkFrames{0};
+ std::atomic<uint16_t> m_framesInBuffers{0};
+
+ // time between next buffer of data from SoftAE and driver call for data
+ double m_avgTimeWaiting{50.0};
+
+ bool m_running{false};
+ bool m_initialized{false};
+ bool m_isSuspended{false}; // sink is in a suspended state - release audio device
+ bool m_isDirty{false}; // sink output failed - needs re-init or new device
+
+ LARGE_INTEGER m_timerFreq{}; // performance counter frequency for latency calculations
};
diff --git a/xbmc/cores/AudioEngine/Sinks/windows/AESinkFactoryWin.h b/xbmc/cores/AudioEngine/Sinks/windows/AESinkFactoryWin.h
index 61db03f5c4..68a8bd4a5c 100644
--- a/xbmc/cores/AudioEngine/Sinks/windows/AESinkFactoryWin.h
+++ b/xbmc/cores/AudioEngine/Sinks/windows/AESinkFactoryWin.h
@@ -197,10 +197,14 @@ class CAESinkFactoryWin
{
public:
/*
- Gets list of audio renderers available on platform
+ Gets list of available audio renderers - using MMDevice
*/
static std::vector<RendererDetail> GetRendererDetails();
/*
+ Gets list of available audio renderers - using WinRT
+ */
+ static std::vector<RendererDetail> GetRendererDetailsWinRT();
+ /*
Gets default device id
*/
static std::string GetDefaultDeviceId();
diff --git a/xbmc/cores/AudioEngine/Sinks/windows/AESinkFactoryWin10.cpp b/xbmc/cores/AudioEngine/Sinks/windows/AESinkFactoryWin10.cpp
index 23d79e5a9d..ea3eccf56f 100644
--- a/xbmc/cores/AudioEngine/Sinks/windows/AESinkFactoryWin10.cpp
+++ b/xbmc/cores/AudioEngine/Sinks/windows/AESinkFactoryWin10.cpp
@@ -6,114 +6,21 @@
* See LICENSES/README.md for more information.
*/
#include "AESinkFactoryWin.h"
-#include "utils/log.h"
-#include "platform/win10/AsyncHelpers.h"
#include "platform/win32/CharsetConverter.h"
#include <mmdeviceapi.h>
#include <mmreg.h>
#include <winrt/Windows.Devices.Enumeration.h>
-#include <winrt/Windows.Media.Devices.Core.h>
#include <winrt/Windows.Media.Devices.h>
using namespace winrt::Windows::Devices::Enumeration;
using namespace winrt::Windows::Media::Devices;
-using namespace winrt::Windows::Media::Devices::Core;
using namespace Microsoft::WRL;
-static winrt::hstring PKEY_Device_FriendlyName = L"System.ItemNameDisplay";
-static winrt::hstring PKEY_AudioEndpoint_FormFactor = L"{1da5d803-d492-4edd-8c23-e0c0ffee7f0e} 0";
-static winrt::hstring PKEY_AudioEndpoint_ControlPanelPageProvider = L"{1da5d803-d492-4edd-8c23-e0c0ffee7f0e} 1";
-static winrt::hstring PKEY_AudioEndpoint_Association = L"{1da5d803-d492-4edd-8c23-e0c0ffee7f0e} 2";
-static winrt::hstring PKEY_AudioEndpoint_PhysicalSpeakers = L"{1da5d803-d492-4edd-8c23-e0c0ffee7f0e} 3";
-static winrt::hstring PKEY_AudioEndpoint_GUID = L"{1da5d803-d492-4edd-8c23-e0c0ffee7f0e} 4";
-static winrt::hstring PKEY_AudioEndpoint_Disable_SysFx = L"{1da5d803-d492-4edd-8c23-e0c0ffee7f0e} 5";
-static winrt::hstring PKEY_AudioEndpoint_FullRangeSpeakers = L"{1da5d803-d492-4edd-8c23-e0c0ffee7f0e} 6";
-static winrt::hstring PKEY_AudioEndpoint_Supports_EventDriven_Mode = L"{1da5d803-d492-4edd-8c23-e0c0ffee7f0e} 7";
-static winrt::hstring PKEY_AudioEndpoint_JackSubType = L"{1da5d803-d492-4edd-8c23-e0c0ffee7f0e} 8";
-static winrt::hstring PKEY_AudioEndpoint_Default_VolumeInDb = L"{1da5d803-d492-4edd-8c23-e0c0ffee7f0e} 9";
-static winrt::hstring PKEY_AudioEngine_DeviceFormat = L"{f19f064d-082c-4e27-bc73-6882a1bb8e4c} 0";
-static winrt::hstring PKEY_Device_EnumeratorName = L"{a45c254e-df1c-4efd-8020-67d146a850e0} 24";
-
std::vector<RendererDetail> CAESinkFactoryWin::GetRendererDetails()
{
- std::vector<RendererDetail> list;
- try
- {
- // Get the string identifier of the audio renderer
- auto defaultId = MediaDevice::GetDefaultAudioRenderId(AudioDeviceRole::Default);
- auto audioSelector = MediaDevice::GetAudioRenderSelector();
-
- // Add custom properties to the query
- DeviceInformationCollection devInfocollection = Wait(DeviceInformation::FindAllAsync(audioSelector,
- {
- PKEY_AudioEndpoint_FormFactor,
- PKEY_AudioEndpoint_GUID,
- PKEY_AudioEndpoint_PhysicalSpeakers,
- PKEY_AudioEngine_DeviceFormat,
- PKEY_Device_EnumeratorName
- }));
- if (devInfocollection == nullptr || devInfocollection.Size() == 0)
- goto failed;
-
- for (unsigned int i = 0; i < devInfocollection.Size(); i++)
- {
- RendererDetail details;
-
- DeviceInformation devInfo = devInfocollection.GetAt(i);
- if (devInfo.Properties().Size() == 0)
- goto failed;
-
- winrt::IInspectable propObj = nullptr;
-
- propObj = devInfo.Properties().Lookup(PKEY_AudioEndpoint_FormFactor);
- if (!propObj)
- goto failed;
-
- details.strWinDevType = winEndpoints[propObj.as<winrt::IPropertyValue>().GetUInt32()].winEndpointType;
- details.eDeviceType = winEndpoints[propObj.as<winrt::IPropertyValue>().GetUInt32()].aeDeviceType;
-
- unsigned long ulChannelMask = 0;
- unsigned int nChannels = 0;
-
- propObj = devInfo.Properties().Lookup(PKEY_AudioEngine_DeviceFormat);
- if (propObj)
- {
- winrt::com_array<uint8_t> com_arr;
- propObj.as<winrt::IPropertyValue>().GetUInt8Array(com_arr);
-
- WAVEFORMATEXTENSIBLE* smpwfxex = (WAVEFORMATEXTENSIBLE*)com_arr.data();
- nChannels = std::max(std::min(smpwfxex->Format.nChannels, (WORD)8), (WORD)2);
- ulChannelMask = smpwfxex->dwChannelMask;
- }
- else
- {
- // suppose stereo
- nChannels = 2;
- ulChannelMask = 3;
- }
-
- propObj = devInfo.Properties().Lookup(PKEY_AudioEndpoint_PhysicalSpeakers);
- details.uiChannelMask = propObj ? propObj.as<winrt::IPropertyValue>().GetUInt32() : ulChannelMask;
- details.nChannels = nChannels;
-
- details.strDescription = KODI::PLATFORM::WINDOWS::FromW(devInfo.Name().c_str());
- details.strDeviceId = KODI::PLATFORM::WINDOWS::FromW(devInfo.Id().c_str());
-
- details.bDefault = (devInfo.Id() == defaultId);
-
- list.push_back(details);
- }
- return list;
- }
- catch (...)
- {
- }
-
-failed:
- CLog::Log(LOGERROR, __FUNCTION__": Failed to enumerate audio renderer devices.");
- return list;
+ return GetRendererDetailsWinRT();
}
class CAudioInterfaceActivator : public winrt::implements<CAudioInterfaceActivator, IActivateAudioInterfaceCompletionHandler>
diff --git a/xbmc/cores/AudioEngine/Sinks/windows/AESinkFactoryWin32.cpp b/xbmc/cores/AudioEngine/Sinks/windows/AESinkFactoryWin32.cpp
index 3b6abaae90..d5869c5e2e 100644
--- a/xbmc/cores/AudioEngine/Sinks/windows/AESinkFactoryWin32.cpp
+++ b/xbmc/cores/AudioEngine/Sinks/windows/AESinkFactoryWin32.cpp
@@ -130,14 +130,16 @@ std::vector<RendererDetail> CAESinkFactoryWin::GetRendererDetails()
PropVariantClear(&varName);
hr = pProperty->GetValue(PKEY_Device_EnumeratorName, &varName);
- if (FAILED(hr))
+ if (SUCCEEDED(hr) && varName.pwszVal != nullptr)
{
- CLog::LogF(LOGERROR, "Retrieval of endpoint enumerator name failed.");
- goto failed;
+ details.strDeviceEnumerator = KODI::PLATFORM::WINDOWS::FromW(varName.pwszVal);
+ StringUtils::ToUpper(details.strDeviceEnumerator);
+ }
+ else
+ {
+ CLog::LogF(LOGDEBUG, "Retrieval of endpoint enumerator name failed: {}.",
+ (FAILED(hr)) ? "'GetValue' has failed" : "'varName.pwszVal' is NULL");
}
-
- details.strDeviceEnumerator = KODI::PLATFORM::WINDOWS::FromW(varName.pwszVal);
- StringUtils::ToUpper(details.strDeviceEnumerator);
PropVariantClear(&varName);
if (pDevice->GetId(&pwszID) == S_OK)
diff --git a/xbmc/cores/AudioEngine/Sinks/windows/AESinkFactoryWinRT.cpp b/xbmc/cores/AudioEngine/Sinks/windows/AESinkFactoryWinRT.cpp
new file mode 100644
index 0000000000..d69c64f69a
--- /dev/null
+++ b/xbmc/cores/AudioEngine/Sinks/windows/AESinkFactoryWinRT.cpp
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2024 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "AESinkFactoryWin.h"
+#include "utils/log.h"
+
+#include "platform/win10/AsyncHelpers.h"
+#include "platform/win32/CharsetConverter.h"
+
+#include <winrt/windows.devices.enumeration.h>
+#include <winrt/windows.foundation.collections.h>
+#include <winrt/windows.foundation.h>
+#include <winrt/windows.media.devices.core.h>
+#include <winrt/windows.media.devices.h>
+
+namespace winrt
+{
+using namespace Windows::Foundation;
+}
+
+using namespace winrt::Windows::Devices::Enumeration;
+using namespace winrt::Windows::Media::Devices;
+using namespace winrt::Windows::Media::Devices::Core;
+
+// clang-format off
+static winrt::hstring PKEY_Device_FriendlyName = L"System.ItemNameDisplay";
+static winrt::hstring PKEY_AudioEndpoint_FormFactor = L"{1da5d803-d492-4edd-8c23-e0c0ffee7f0e} 0";
+static winrt::hstring PKEY_AudioEndpoint_ControlPanelPageProvider = L"{1da5d803-d492-4edd-8c23-e0c0ffee7f0e} 1";
+static winrt::hstring PKEY_AudioEndpoint_Association = L"{1da5d803-d492-4edd-8c23-e0c0ffee7f0e} 2";
+static winrt::hstring PKEY_AudioEndpoint_PhysicalSpeakers = L"{1da5d803-d492-4edd-8c23-e0c0ffee7f0e} 3";
+static winrt::hstring PKEY_AudioEndpoint_GUID = L"{1da5d803-d492-4edd-8c23-e0c0ffee7f0e} 4";
+static winrt::hstring PKEY_AudioEndpoint_Disable_SysFx = L"{1da5d803-d492-4edd-8c23-e0c0ffee7f0e} 5";
+static winrt::hstring PKEY_AudioEndpoint_FullRangeSpeakers = L"{1da5d803-d492-4edd-8c23-e0c0ffee7f0e} 6";
+static winrt::hstring PKEY_AudioEndpoint_Supports_EventDriven_Mode = L"{1da5d803-d492-4edd-8c23-e0c0ffee7f0e} 7";
+static winrt::hstring PKEY_AudioEndpoint_JackSubType = L"{1da5d803-d492-4edd-8c23-e0c0ffee7f0e} 8";
+static winrt::hstring PKEY_AudioEndpoint_Default_VolumeInDb = L"{1da5d803-d492-4edd-8c23-e0c0ffee7f0e} 9";
+static winrt::hstring PKEY_AudioEngine_DeviceFormat = L"{f19f064d-082c-4e27-bc73-6882a1bb8e4c} 0";
+static winrt::hstring PKEY_Device_EnumeratorName = L"{a45c254e-df1c-4efd-8020-67d146a850e0} 24";
+// clang-format on
+
+std::vector<RendererDetail> CAESinkFactoryWin::GetRendererDetailsWinRT()
+{
+ std::vector<RendererDetail> list;
+ try
+ {
+ // Get the string identifier of the audio renderer
+ auto defaultId = MediaDevice::GetDefaultAudioRenderId(AudioDeviceRole::Default);
+ auto audioSelector = MediaDevice::GetAudioRenderSelector();
+
+ // Add custom properties to the query
+ DeviceInformationCollection devInfoCollection = Wait(DeviceInformation::FindAllAsync(
+ audioSelector, {PKEY_AudioEndpoint_FormFactor, PKEY_AudioEndpoint_GUID,
+ PKEY_AudioEndpoint_PhysicalSpeakers, PKEY_AudioEngine_DeviceFormat,
+ PKEY_Device_EnumeratorName}));
+
+ if (devInfoCollection == nullptr || devInfoCollection.Size() == 0)
+ goto failed;
+
+ for (const DeviceInformation& devInfo : devInfoCollection)
+ {
+ RendererDetail details;
+
+ if (devInfo.Properties().Size() == 0)
+ goto failed;
+
+ winrt::IInspectable propObj = nullptr;
+
+ propObj = devInfo.Properties().Lookup(PKEY_AudioEndpoint_FormFactor);
+ if (!propObj)
+ goto failed;
+
+ const uint32_t indexFF{propObj.as<winrt::IPropertyValue>().GetUInt32()};
+ details.strWinDevType = winEndpoints[indexFF].winEndpointType;
+ details.eDeviceType = winEndpoints[indexFF].aeDeviceType;
+
+ DWORD ulChannelMask = 0;
+ unsigned int nChannels = 0;
+
+ propObj = devInfo.Properties().Lookup(PKEY_AudioEngine_DeviceFormat);
+ if (propObj)
+ {
+ winrt::com_array<uint8_t> com_arr;
+ propObj.as<winrt::IPropertyValue>().GetUInt8Array(com_arr);
+
+ WAVEFORMATEXTENSIBLE* smpwfxex = (WAVEFORMATEXTENSIBLE*)com_arr.data();
+ nChannels = std::max(std::min(smpwfxex->Format.nChannels, (WORD)8), (WORD)2);
+ ulChannelMask = smpwfxex->dwChannelMask;
+ }
+ else
+ {
+ // suppose stereo
+ nChannels = 2;
+ ulChannelMask = 3;
+ }
+
+ propObj = devInfo.Properties().Lookup(PKEY_AudioEndpoint_PhysicalSpeakers);
+
+ details.uiChannelMask = propObj ? propObj.as<winrt::IPropertyValue>().GetUInt32()
+ : static_cast<unsigned int>(ulChannelMask);
+
+ details.nChannels = nChannels;
+
+ details.strDescription = KODI::PLATFORM::WINDOWS::FromW(devInfo.Name().c_str());
+ details.strDeviceId = KODI::PLATFORM::WINDOWS::FromW(devInfo.Id().c_str());
+
+ details.bDefault = (devInfo.Id() == defaultId);
+
+ list.push_back(details);
+ }
+ return list;
+ }
+ catch (...)
+ {
+ }
+
+failed:
+ CLog::LogF(LOGERROR, "Failed to enumerate audio renderer devices.");
+ return list;
+}
diff --git a/xbmc/cores/AudioEngine/Utils/AEUtil.cpp b/xbmc/cores/AudioEngine/Utils/AEUtil.cpp
index 81a5f1995a..45928621cf 100644
--- a/xbmc/cores/AudioEngine/Utils/AEUtil.cpp
+++ b/xbmc/cores/AudioEngine/Utils/AEUtil.cpp
@@ -607,3 +607,25 @@ int CAEUtil::GetAVChannelIndex(enum AEChannel aechannel, uint64_t layout)
av_channel_layout_uninit(&ch_layout);
return idx;
}
+
+void CAEUtil::GenerateSilence(AEDataFormat format,
+ unsigned int frameSize,
+ void* buffer,
+ unsigned int frames)
+
+{
+ const unsigned int dataLength{frames * frameSize};
+
+ switch (format)
+ {
+ case AE_FMT_U8:
+ case AE_FMT_U8P:
+ memset(buffer, 0x80, dataLength);
+ break;
+
+ default:
+ // binary representation of 0 in all other formats regardless of number of bits per sample
+ // is a concatenation of 0 bytes.
+ memset(buffer, 0, dataLength);
+ }
+}
diff --git a/xbmc/cores/AudioEngine/Utils/AEUtil.h b/xbmc/cores/AudioEngine/Utils/AEUtil.h
index 65a846e044..e47137ab95 100644
--- a/xbmc/cores/AudioEngine/Utils/AEUtil.h
+++ b/xbmc/cores/AudioEngine/Utils/AEUtil.h
@@ -178,4 +178,8 @@ public:
static uint64_t GetAVChannelMask(enum AEChannel aechannel);
static enum AVChannel GetAVChannel(enum AEChannel aechannel);
static int GetAVChannelIndex(enum AEChannel aechannel, uint64_t layout);
+ static void GenerateSilence(AEDataFormat format,
+ unsigned int frameSize,
+ void* buffer,
+ unsigned int frames);
};
diff --git a/xbmc/cores/RetroPlayer/streams/RetroPlayerVideo.h b/xbmc/cores/RetroPlayer/streams/RetroPlayerVideo.h
index 8d153ab5c2..132b71218c 100644
--- a/xbmc/cores/RetroPlayer/streams/RetroPlayerVideo.h
+++ b/xbmc/cores/RetroPlayer/streams/RetroPlayerVideo.h
@@ -11,6 +11,8 @@
#include "IRetroPlayerStream.h"
#include "cores/RetroPlayer/RetroPlayerTypes.h"
+#include <cstdint>
+
extern "C"
{
#include <libavutil/pixfmt.h>
diff --git a/xbmc/cores/VideoPlayer/Buffers/VideoBufferDRMPRIME.h b/xbmc/cores/VideoPlayer/Buffers/VideoBufferDRMPRIME.h
index b83ee8ca68..dca6e82177 100644
--- a/xbmc/cores/VideoPlayer/Buffers/VideoBufferDRMPRIME.h
+++ b/xbmc/cores/VideoPlayer/Buffers/VideoBufferDRMPRIME.h
@@ -54,6 +54,8 @@ public:
virtual const VideoPicture& GetPicture() const { return m_picture; }
virtual uint32_t GetWidth() const { return GetPicture().iWidth; }
virtual uint32_t GetHeight() const { return GetPicture().iHeight; }
+ virtual uint32_t GetXOffset() const { return GetPicture().m_xOffset; }
+ virtual uint32_t GetYOffset() const { return GetPicture().m_yOffset; }
virtual AVDRMFrameDescriptor* GetDescriptor() const = 0;
virtual bool IsValid() const { return true; }
diff --git a/xbmc/cores/VideoPlayer/DVDCodecs/Video/DVDVideoCodec.cpp b/xbmc/cores/VideoPlayer/DVDCodecs/Video/DVDVideoCodec.cpp
index 495d6a25f5..a5468d12a0 100644
--- a/xbmc/cores/VideoPlayer/DVDCodecs/Video/DVDVideoCodec.cpp
+++ b/xbmc/cores/VideoPlayer/DVDCodecs/Video/DVDVideoCodec.cpp
@@ -58,6 +58,8 @@ void VideoPicture::Reset()
iWidth = 0;
iHeight = 0;
+ m_xOffset = 0;
+ m_yOffset = 0;
iDisplayWidth = 0;
iDisplayHeight = 0;
}
diff --git a/xbmc/cores/VideoPlayer/DVDCodecs/Video/DVDVideoCodec.h b/xbmc/cores/VideoPlayer/DVDCodecs/Video/DVDVideoCodec.h
index ca83b1a04f..f3373612e8 100644
--- a/xbmc/cores/VideoPlayer/DVDCodecs/Video/DVDVideoCodec.h
+++ b/xbmc/cores/VideoPlayer/DVDCodecs/Video/DVDVideoCodec.h
@@ -75,6 +75,8 @@ public:
unsigned int iWidth;
unsigned int iHeight;
+ unsigned int m_xOffset{0};
+ unsigned int m_yOffset{0};
unsigned int iDisplayWidth; //< width of the picture without black bars
unsigned int iDisplayHeight; //< height of the picture without black bars
diff --git a/xbmc/cores/VideoPlayer/DVDCodecs/Video/DVDVideoCodecDRMPRIME.cpp b/xbmc/cores/VideoPlayer/DVDCodecs/Video/DVDVideoCodecDRMPRIME.cpp
index eb2943bb8c..0d407043dd 100644
--- a/xbmc/cores/VideoPlayer/DVDCodecs/Video/DVDVideoCodecDRMPRIME.cpp
+++ b/xbmc/cores/VideoPlayer/DVDCodecs/Video/DVDVideoCodecDRMPRIME.cpp
@@ -505,6 +505,8 @@ void CDVDVideoCodecDRMPRIME::SetPictureParams(VideoPicture* pVideoPicture)
{
pVideoPicture->iWidth = m_pFrame->width;
pVideoPicture->iHeight = m_pFrame->height;
+ pVideoPicture->m_xOffset = m_pFrame->crop_left;
+ pVideoPicture->m_yOffset = m_pFrame->crop_top;
double aspect_ratio = 0;
AVRational pixel_aspect = m_pFrame->sample_aspect_ratio;
diff --git a/xbmc/cores/VideoPlayer/DVDInputStreams/dvdnav/ifo_types.h b/xbmc/cores/VideoPlayer/DVDInputStreams/dvdnav/ifo_types.h
index 4191b67922..650838cbea 100644
--- a/xbmc/cores/VideoPlayer/DVDInputStreams/dvdnav/ifo_types.h
+++ b/xbmc/cores/VideoPlayer/DVDInputStreams/dvdnav/ifo_types.h
@@ -29,7 +29,8 @@
#if defined(__GNUC__)
#if __GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ >= 95)
-#if (__GNUC__ > 3 || (__GNUC__ == 3 && __GNUC_MINOR__ >= 4)) && !defined(__clang__)
+#if (__GNUC__ > 3 || (__GNUC__ == 3 && __GNUC_MINOR__ >= 4)) && !defined(__clang__) && \
+ !defined(__arm__) && !defined(__aarch64__)
#define ATTRIBUTE_PACKED __attribute__((packed, gcc_struct))
#else
#define ATTRIBUTE_PACKED __attribute__((packed))
diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/RendererDRMPRIME.cpp b/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/RendererDRMPRIME.cpp
index 66df0e49c7..2ef7f4d521 100644
--- a/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/RendererDRMPRIME.cpp
+++ b/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/RendererDRMPRIME.cpp
@@ -77,7 +77,7 @@ CBaseRenderer* CRendererDRMPRIME::Create(CVideoBuffer* buffer)
if (!plane)
return nullptr;
- if (!plane->SupportsFormatAndModifier(format, modifier))
+ if (!drm->FindVideoPlane(format, modifier))
return nullptr;
return new CRendererDRMPRIME();
diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/VideoLayerBridgeDRMPRIME.cpp b/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/VideoLayerBridgeDRMPRIME.cpp
index 34d1ab6235..33db29b140 100644
--- a/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/VideoLayerBridgeDRMPRIME.cpp
+++ b/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/VideoLayerBridgeDRMPRIME.cpp
@@ -118,9 +118,10 @@ bool CVideoLayerBridgeDRMPRIME::Map(CVideoBufferDRMPRIME* buffer)
flags = DRM_MODE_FB_MODIFIERS;
// add the video frame FB
- ret = drmModeAddFB2WithModifiers(m_DRM->GetFileDescriptor(), buffer->GetWidth(),
- buffer->GetHeight(), layer->format, handles, pitches, offsets,
- modifier, &buffer->m_fb_id, flags);
+ ret = drmModeAddFB2WithModifiers(m_DRM->GetFileDescriptor(),
+ buffer->GetWidth() + buffer->GetXOffset(),
+ buffer->GetHeight() + buffer->GetYOffset(), layer->format,
+ handles, pitches, offsets, modifier, &buffer->m_fb_id, flags);
if (ret < 0)
{
CLog::Log(LOGERROR, "CVideoLayerBridgeDRMPRIME::{} - failed to add fb {}, ret = {}",
@@ -188,8 +189,8 @@ void CVideoLayerBridgeDRMPRIME::SetVideoPlane(CVideoBufferDRMPRIME* buffer, cons
auto plane = m_DRM->GetVideoPlane();
m_DRM->AddProperty(plane, "FB_ID", buffer->m_fb_id);
m_DRM->AddProperty(plane, "CRTC_ID", m_DRM->GetCrtc()->GetCrtcId());
- m_DRM->AddProperty(plane, "SRC_X", 0);
- m_DRM->AddProperty(plane, "SRC_Y", 0);
+ m_DRM->AddProperty(plane, "SRC_X", buffer->GetXOffset() << 16);
+ m_DRM->AddProperty(plane, "SRC_Y", buffer->GetYOffset() << 16);
m_DRM->AddProperty(plane, "SRC_W", buffer->GetWidth() << 16);
m_DRM->AddProperty(plane, "SRC_H", buffer->GetHeight() << 16);
m_DRM->AddProperty(plane, "CRTC_X", static_cast<int32_t>(destRect.x1) & ~1);
diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/LinuxRendererGLES.cpp b/xbmc/cores/VideoPlayer/VideoRenderers/LinuxRendererGLES.cpp
index 97663f9c09..67275f920b 100644
--- a/xbmc/cores/VideoPlayer/VideoRenderers/LinuxRendererGLES.cpp
+++ b/xbmc/cores/VideoPlayer/VideoRenderers/LinuxRendererGLES.cpp
@@ -584,6 +584,8 @@ void CLinuxRendererGLES::UpdateVideoFilter()
}
m_scalingMethodGui = m_videoSettings.m_ScalingMethod;
+ if (m_scalingMethod != m_scalingMethodGui)
+ m_reloadShaders = true;
m_scalingMethod = m_scalingMethodGui;
m_viewRect = viewRect;
@@ -616,6 +618,8 @@ void CLinuxRendererGLES::UpdateVideoFilter()
return;
}
case VS_SCALINGMETHOD_LINEAR:
+ case VS_SCALINGMETHOD_LANCZOS3_FAST:
+ case VS_SCALINGMETHOD_SPLINE36_FAST:
{
CLog::Log(LOGINFO, "GLES: Selecting single pass rendering");
SetTextureFilter(GL_LINEAR);
@@ -623,8 +627,6 @@ void CLinuxRendererGLES::UpdateVideoFilter()
return;
}
case VS_SCALINGMETHOD_LANCZOS2:
- case VS_SCALINGMETHOD_SPLINE36_FAST:
- case VS_SCALINGMETHOD_LANCZOS3_FAST:
case VS_SCALINGMETHOD_SPLINE36:
case VS_SCALINGMETHOD_LANCZOS3:
case VS_SCALINGMETHOD_CUBIC_B_SPLINE:
@@ -709,9 +711,19 @@ void CLinuxRendererGLES::LoadShaders(int field)
EShaderFormat shaderFormat = GetShaderFormat();
m_toneMapMethod = m_videoSettings.m_ToneMapMethod;
- m_pYUVProgShader = new YUV2RGBProgressiveShader(
- shaderFormat, m_passthroughHDR ? m_srcPrimaries : AVColorPrimaries::AVCOL_PRI_BT709,
- m_srcPrimaries, m_toneMap, m_toneMapMethod);
+ if (m_scalingMethod == VS_SCALINGMETHOD_LANCZOS3_FAST ||
+ m_scalingMethod == VS_SCALINGMETHOD_SPLINE36_FAST)
+ {
+ m_pYUVProgShader = new YUV2RGBFilterShader(
+ shaderFormat, m_passthroughHDR ? m_srcPrimaries : AVColorPrimaries::AVCOL_PRI_BT709,
+ m_srcPrimaries, m_toneMap, m_toneMapMethod, m_scalingMethod);
+ }
+ else
+ {
+ m_pYUVProgShader = new YUV2RGBProgressiveShader(
+ shaderFormat, m_passthroughHDR ? m_srcPrimaries : AVColorPrimaries::AVCOL_PRI_BT709,
+ m_srcPrimaries, m_toneMap, m_toneMapMethod);
+ }
m_pYUVProgShader->SetConvertFullColorRange(m_fullRange);
m_pYUVBobShader = new YUV2RGBBobShader(
shaderFormat, m_passthroughHDR ? m_srcPrimaries : AVColorPrimaries::AVCOL_PRI_BT709,
@@ -1790,6 +1802,18 @@ bool CLinuxRendererGLES::Supports(ESCALINGMETHOD method) const
method == VS_SCALINGMETHOD_SPLINE36 ||
method == VS_SCALINGMETHOD_LANCZOS3)
{
+ if (method == VS_SCALINGMETHOD_SPLINE36_FAST || method == VS_SCALINGMETHOD_LANCZOS3_FAST)
+ {
+#if defined(GL_ES_VERSION_3_0)
+ // we need GLES 3.0 headers for GL_RGBA16f, but GLES 3.1 for the shader
+ uint32_t major, minor;
+ m_renderSystem->GetRenderVersion(major, minor);
+ if (major < 3 || minor == 0)
+ return false;
+#else
+ return false;
+#endif
+ }
// if scaling is below level, avoid hq scaling
float scaleX = fabs((static_cast<float>(m_sourceWidth) - m_destRect.Width()) / m_sourceWidth) * 100;
float scaleY = fabs((static_cast<float>(m_sourceHeight) - m_destRect.Height()) / m_sourceHeight) * 100;
diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/VideoShaders/ConversionMatrix.h b/xbmc/cores/VideoPlayer/VideoRenderers/VideoShaders/ConversionMatrix.h
index b2c66a7e73..4f43fc0475 100644
--- a/xbmc/cores/VideoPlayer/VideoRenderers/VideoShaders/ConversionMatrix.h
+++ b/xbmc/cores/VideoPlayer/VideoRenderers/VideoShaders/ConversionMatrix.h
@@ -10,6 +10,7 @@
#include <array>
#include <cmath>
+#include <cstdint>
#include <memory>
extern "C" {
diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/VideoShaders/YUV2RGBShaderGLES.cpp b/xbmc/cores/VideoPlayer/VideoRenderers/VideoShaders/YUV2RGBShaderGLES.cpp
index 63e4d304a4..5f4e63090a 100644
--- a/xbmc/cores/VideoPlayer/VideoRenderers/VideoShaders/YUV2RGBShaderGLES.cpp
+++ b/xbmc/cores/VideoPlayer/VideoRenderers/VideoShaders/YUV2RGBShaderGLES.cpp
@@ -1,6 +1,6 @@
/*
* Copyright (c) 2007 d4rk
- * Copyright (C) 2007-2018 Team Kodi
+ * Copyright (C) 2007-2024 Team Kodi
* This file is part of Kodi - https://kodi.tv
*
* SPDX-License-Identifier: GPL-2.0-or-later
@@ -10,6 +10,7 @@
#include "YUV2RGBShaderGLES.h"
#include "../RenderFlags.h"
+#include "ConvolutionKernels.h"
#include "ToneMappers.h"
#include "settings/AdvancedSettings.h"
#include "utils/GLUtils.h"
@@ -267,3 +268,71 @@ bool YUV2RGBBobShader::OnEnabled()
VerifyGLState();
return true;
}
+
+//------------------------------------------------------------------------------
+// YUV2RGBFilterShader
+//------------------------------------------------------------------------------
+
+YUV2RGBFilterShader::YUV2RGBFilterShader(EShaderFormat format,
+ AVColorPrimaries dstPrimaries,
+ AVColorPrimaries srcPrimaries,
+ bool toneMap,
+ ETONEMAPMETHOD toneMapMethod,
+ ESCALINGMETHOD method)
+ : BaseYUV2RGBGLSLShader(format, dstPrimaries, srcPrimaries, toneMap, toneMapMethod)
+{
+ m_scaling = method;
+ PixelShader()->LoadSource("gles310_yuv2rgb_filter.frag", m_defines);
+ VertexShader()->LoadSource("gles310_yuv2rgb.vert");
+ PixelShader()->AppendSource("gl_output.glsl");
+
+ PixelShader()->InsertSource("gl_tonemap.glsl", "void main()");
+}
+
+YUV2RGBFilterShader::~YUV2RGBFilterShader()
+{
+ if (m_kernelTex)
+ glDeleteTextures(1, &m_kernelTex);
+ m_kernelTex = 0;
+}
+
+void YUV2RGBFilterShader::OnCompiledAndLinked()
+{
+ BaseYUV2RGBGLSLShader::OnCompiledAndLinked();
+ m_hKernTex = glGetUniformLocation(ProgramHandle(), "m_kernelTex");
+
+ if (m_scaling != VS_SCALINGMETHOD_LANCZOS3_FAST && m_scaling != VS_SCALINGMETHOD_SPLINE36_FAST)
+ m_scaling = VS_SCALINGMETHOD_LANCZOS3_FAST;
+
+ CConvolutionKernel kernel(m_scaling, 256);
+
+ if (m_kernelTex)
+ {
+ glDeleteTextures(1, &m_kernelTex);
+ m_kernelTex = 0;
+ }
+ glGenTextures(1, &m_kernelTex);
+
+ glActiveTexture(GL_TEXTURE3);
+ glBindTexture(GL_TEXTURE_2D, m_kernelTex);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+
+ GLvoid* data = (GLvoid*)kernel.GetFloatPixels();
+#if defined(GL_ES_VERSION_3_0)
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16F, kernel.GetSize(), 1, 0, GL_RGBA, GL_FLOAT, data);
+#endif
+ glActiveTexture(GL_TEXTURE0);
+ VerifyGLState();
+}
+
+bool YUV2RGBFilterShader::OnEnabled()
+{
+ glActiveTexture(GL_TEXTURE3);
+ glBindTexture(GL_TEXTURE_2D, m_kernelTex);
+ glUniform1i(m_hKernTex, 3);
+ glActiveTexture(GL_TEXTURE0);
+
+ return BaseYUV2RGBGLSLShader::OnEnabled();
+}
diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/VideoShaders/YUV2RGBShaderGLES.h b/xbmc/cores/VideoPlayer/VideoRenderers/VideoShaders/YUV2RGBShaderGLES.h
index 917f0f35f4..75bf05f325 100644
--- a/xbmc/cores/VideoPlayer/VideoRenderers/VideoShaders/YUV2RGBShaderGLES.h
+++ b/xbmc/cores/VideoPlayer/VideoRenderers/VideoShaders/YUV2RGBShaderGLES.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2007-2018 Team Kodi
+ * Copyright (C) 2007-2024 Team Kodi
* This file is part of Kodi - https://kodi.tv
*
* SPDX-License-Identifier: GPL-2.0-or-later
@@ -135,5 +135,25 @@ class BaseYUV2RGBGLSLShader : public CGLSLShaderProgram
GLint m_hField = -1;
};
+ class YUV2RGBFilterShader : public BaseYUV2RGBGLSLShader
+ {
+ public:
+ YUV2RGBFilterShader(EShaderFormat format,
+ AVColorPrimaries dstPrimaries,
+ AVColorPrimaries srcPrimaries,
+ bool toneMap,
+ ETONEMAPMETHOD toneMapMethod,
+ ESCALINGMETHOD method);
+ ~YUV2RGBFilterShader() override;
+
+ protected:
+ void OnCompiledAndLinked() override;
+ bool OnEnabled() override;
+
+ GLuint m_kernelTex = 0;
+ GLint m_hKernTex = -1;
+ ESCALINGMETHOD m_scaling = VS_SCALINGMETHOD_LANCZOS3_FAST;
+ };
+
} // namespace GLES
} // end namespace
diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/VideoShaders/test/TestConversionMatrix.cpp b/xbmc/cores/VideoPlayer/VideoRenderers/VideoShaders/test/TestConversionMatrix.cpp
index cdc8b91e0e..18f76ace0d 100644
--- a/xbmc/cores/VideoPlayer/VideoRenderers/VideoShaders/test/TestConversionMatrix.cpp
+++ b/xbmc/cores/VideoPlayer/VideoRenderers/VideoShaders/test/TestConversionMatrix.cpp
@@ -10,6 +10,7 @@
#include "cores/VideoPlayer/VideoRenderers/VideoShaders/ConversionMatrix.h"
#include "xbmc/utils/MathUtils.h"
+#include <iomanip>
#include <iostream>
#include <gtest/gtest.h>
diff --git a/xbmc/cores/paplayer/VideoPlayerCodec.cpp b/xbmc/cores/paplayer/VideoPlayerCodec.cpp
index 0a2468f8ff..1303722908 100644
--- a/xbmc/cores/paplayer/VideoPlayerCodec.cpp
+++ b/xbmc/cores/paplayer/VideoPlayerCodec.cpp
@@ -253,13 +253,8 @@ bool VideoPlayerCodec::Init(const CFileItem &file, unsigned int filecache)
srcConfig.bits_per_sample = CAEUtil::DataFormatToUsedBits(m_srcFormat.m_dataFormat);
srcConfig.dither_bits = CAEUtil::DataFormatToDitherBits(m_srcFormat.m_dataFormat);
- m_pResampler->Init(dstConfig, srcConfig,
- false,
- false,
- M_SQRT1_2,
- NULL,
- AE_QUALITY_UNKNOWN,
- false);
+ m_pResampler->Init(dstConfig, srcConfig, false, false, M_SQRT1_2, NULL, AE_QUALITY_UNKNOWN,
+ false, 0.0f);
m_planes = AE_IS_PLANAR(m_srcFormat.m_dataFormat) ? m_channels : 1;
m_format = m_srcFormat;
diff --git a/xbmc/dialogs/GUIDialogColorPicker.cpp b/xbmc/dialogs/GUIDialogColorPicker.cpp
index cbbe1102d4..467b2bcef3 100644
--- a/xbmc/dialogs/GUIDialogColorPicker.cpp
+++ b/xbmc/dialogs/GUIDialogColorPicker.cpp
@@ -178,7 +178,7 @@ void CGUIDialogColorPicker::LoadColors(const std::string& filePath)
__FUNCTION__);
}
-std::string CGUIDialogColorPicker::GetSelectedColor() const
+const std::string& CGUIDialogColorPicker::GetSelectedColor() const
{
return m_selectedColor;
}
diff --git a/xbmc/dialogs/GUIDialogColorPicker.h b/xbmc/dialogs/GUIDialogColorPicker.h
index fba8ebcb46..4cc7a6d9e3 100644
--- a/xbmc/dialogs/GUIDialogColorPicker.h
+++ b/xbmc/dialogs/GUIDialogColorPicker.h
@@ -39,7 +39,7 @@ public:
*/
void LoadColors(const std::string& filePath);
/*! \brief Get the hex value of the selected color */
- std::string GetSelectedColor() const;
+ const std::string& GetSelectedColor() const;
/*! \brief Set the selected color by hex value */
void SetSelectedColor(const std::string& hexColor);
/*! \brief Set the focus to the control button */
diff --git a/xbmc/dialogs/GUIDialogContextMenu.cpp b/xbmc/dialogs/GUIDialogContextMenu.cpp
index 6ecf3d1195..48c3aafc1b 100644
--- a/xbmc/dialogs/GUIDialogContextMenu.cpp
+++ b/xbmc/dialogs/GUIDialogContextMenu.cpp
@@ -35,6 +35,7 @@
#include "settings/Settings.h"
#include "settings/SettingsComponent.h"
#include "storage/MediaManager.h"
+#include "utils/ArtUtils.h"
#include "utils/FileUtils.h"
#include "utils/StringUtils.h"
#include "utils/URIUtils.h"
@@ -387,7 +388,7 @@ bool CGUIDialogContextMenu::OnContextButton(const std::string &type, const CFile
items.Add(current);
}
// see if there's a local thumb for this item
- std::string folderThumb = item->GetFolderThumb();
+ std::string folderThumb = ART::GetFolderThumb(*item);
if (CFileUtils::Exists(folderThumb))
{
CFileItemPtr local(new CFileItem("thumb://Local", false));
diff --git a/xbmc/dialogs/GUIDialogContextMenu.h b/xbmc/dialogs/GUIDialogContextMenu.h
index 10385bd20e..7d44219c0f 100644
--- a/xbmc/dialogs/GUIDialogContextMenu.h
+++ b/xbmc/dialogs/GUIDialogContextMenu.h
@@ -58,7 +58,6 @@ enum CONTEXT_BUTTON
CONTEXT_BUTTON_CDDB,
CONTEXT_BUTTON_SCAN,
CONTEXT_BUTTON_SCAN_TO_LIBRARY,
- CONTEXT_BUTTON_SET_ARTIST_THUMB,
CONTEXT_BUTTON_SET_ART,
CONTEXT_BUTTON_CANCEL_PARTYMODE,
CONTEXT_BUTTON_MARK_WATCHED,
@@ -70,7 +69,6 @@ enum CONTEXT_BUTTON
CONTEXT_BUTTON_GO_TO_ARTIST,
CONTEXT_BUTTON_GO_TO_ALBUM,
CONTEXT_BUTTON_PLAY_OTHER,
- CONTEXT_BUTTON_SET_ACTOR_THUMB,
CONTEXT_BUTTON_UNLINK_BOOKMARK,
CONTEXT_BUTTON_ACTIVATE,
CONTEXT_BUTTON_GROUP_MANAGER,
diff --git a/xbmc/dialogs/GUIDialogMediaFilter.cpp b/xbmc/dialogs/GUIDialogMediaFilter.cpp
index df904cb354..fb85c69102 100644
--- a/xbmc/dialogs/GUIDialogMediaFilter.cpp
+++ b/xbmc/dialogs/GUIDialogMediaFilter.cpp
@@ -558,12 +558,12 @@ bool CGUIDialogMediaFilter::SetPath(const std::string &path)
delete m_dbUrl;
bool video = false;
- if (path.find("videodb://") == 0)
+ if (path.starts_with("videodb://"))
{
m_dbUrl = new CVideoDbUrl();
video = true;
}
- else if (path.find("musicdb://") == 0)
+ else if (path.starts_with("musicdb://"))
m_dbUrl = new CMusicDbUrl();
else
{
diff --git a/xbmc/dialogs/GUIDialogMediaSource.cpp b/xbmc/dialogs/GUIDialogMediaSource.cpp
index 0afbc2d060..1109157c63 100644
--- a/xbmc/dialogs/GUIDialogMediaSource.cpp
+++ b/xbmc/dialogs/GUIDialogMediaSource.cpp
@@ -36,7 +36,7 @@
#if defined(TARGET_ANDROID)
#include "utils/FileUtils.h"
-#include "platform/android/activity/XBMCApp.h"
+#include "platform/android/storage/AndroidStorageProvider.h"
#endif
#ifdef TARGET_WINDOWS_STORE
@@ -249,7 +249,8 @@ void CGUIDialogMediaSource::OnPathBrowse(int item)
#if defined(TARGET_ANDROID)
// add the default android music directory
std::string path;
- if (CXBMCApp::GetExternalStorage(path, "music") && !path.empty() && CDirectory::Exists(path))
+ if (CAndroidStorageProvider::GetExternalStorage(path, "music") && !path.empty() &&
+ CDirectory::Exists(path))
{
share1.strPath = path;
share1.strName = g_localizeStrings.Get(20240);
@@ -303,7 +304,8 @@ void CGUIDialogMediaSource::OnPathBrowse(int item)
#if defined(TARGET_ANDROID)
// add the default android video directory
std::string path;
- if (CXBMCApp::GetExternalStorage(path, "videos") && !path.empty() && CFileUtils::Exists(path))
+ if (CAndroidStorageProvider::GetExternalStorage(path, "videos") && !path.empty() &&
+ CFileUtils::Exists(path))
{
share1.strPath = path;
share1.strName = g_localizeStrings.Get(20241);
@@ -349,7 +351,8 @@ void CGUIDialogMediaSource::OnPathBrowse(int item)
#if defined(TARGET_ANDROID)
// add the default android music directory
std::string path;
- if (CXBMCApp::GetExternalStorage(path, "pictures") && !path.empty() && CFileUtils::Exists(path))
+ if (CAndroidStorageProvider::GetExternalStorage(path, "pictures") && !path.empty() &&
+ CFileUtils::Exists(path))
{
share1.strPath = path;
share1.strName = g_localizeStrings.Get(20242);
@@ -358,7 +361,8 @@ void CGUIDialogMediaSource::OnPathBrowse(int item)
}
path.clear();
- if (CXBMCApp::GetExternalStorage(path, "photos") && !path.empty() && CFileUtils::Exists(path))
+ if (CAndroidStorageProvider::GetExternalStorage(path, "photos") && !path.empty() &&
+ CFileUtils::Exists(path))
{
share1.strPath = path;
share1.strName = g_localizeStrings.Get(20243);
diff --git a/xbmc/events/EventLog.cpp b/xbmc/events/EventLog.cpp
index 89eff93195..ee84409966 100644
--- a/xbmc/events/EventLog.cpp
+++ b/xbmc/events/EventLog.cpp
@@ -57,7 +57,7 @@ EventLevel CEventLog::EventLevelFromString(const std::string& level)
return EventLevel::Information;
}
-Events CEventLog::Get() const
+const Events& CEventLog::Get() const
{
return m_events;
}
diff --git a/xbmc/events/EventLog.h b/xbmc/events/EventLog.h
index 035e448610..4c71eef767 100644
--- a/xbmc/events/EventLog.h
+++ b/xbmc/events/EventLog.h
@@ -28,7 +28,7 @@ public:
CEventLog& operator=(CEventLog const&) = delete;
~CEventLog() = default;
- Events Get() const;
+ const Events& Get() const;
Events Get(EventLevel level, bool includeHigherLevels = false) const;
EventPtr Get(const std::string& eventIdentifier) const;
diff --git a/xbmc/favourites/GUIViewStateFavourites.h b/xbmc/favourites/GUIViewStateFavourites.h
index aad4444a1f..48f561b808 100644
--- a/xbmc/favourites/GUIViewStateFavourites.h
+++ b/xbmc/favourites/GUIViewStateFavourites.h
@@ -15,7 +15,7 @@ class CFileItemList;
class CGUIViewStateFavourites : public CGUIViewState
{
public:
- CGUIViewStateFavourites(const CFileItemList& items);
+ explicit CGUIViewStateFavourites(const CFileItemList& items);
~CGUIViewStateFavourites() override = default;
protected:
diff --git a/xbmc/favourites/GUIWindowFavourites.cpp b/xbmc/favourites/GUIWindowFavourites.cpp
index de3d25de37..3bbaf3ae98 100644
--- a/xbmc/favourites/GUIWindowFavourites.cpp
+++ b/xbmc/favourites/GUIWindowFavourites.cpp
@@ -90,7 +90,7 @@ protected:
return UTILS::GUILIB::CGUIContentUtils::ShowInfoForItem(*m_item);
}
- bool OnMoreSelected() override
+ bool OnChooseSelected() override
{
CONTEXTMENU::ShowFor(m_item, CContextMenuManager::MAIN);
return true;
@@ -161,12 +161,12 @@ bool CGUIWindowFavourites::OnAction(const CAction& action)
if (action.GetID() == ACTION_PLAYER_PLAY)
{
- const auto target{
+ const auto targetItem{
CServiceBroker::GetFavouritesService().ResolveFavourite(*(*m_vecItems)[selectedItem])};
- if (!target)
+ if (!targetItem)
return false;
- const auto item{std::make_shared<CFileItem>(*target)};
+ const auto item{std::make_shared<CFileItem>(*targetItem)};
// video play action setting is for files and folders...
if (item->HasVideoInfoTag() || (item->m_bIsFolder && VIDEO::UTILS::IsItemPlayable(*item)))
diff --git a/xbmc/filesystem/AudioBookFileDirectory.cpp b/xbmc/filesystem/AudioBookFileDirectory.cpp
index dda1c0cb5f..f730ec2df4 100644
--- a/xbmc/filesystem/AudioBookFileDirectory.cpp
+++ b/xbmc/filesystem/AudioBookFileDirectory.cpp
@@ -106,20 +106,26 @@ bool CAudioBookFileDirectory::GetDirectory(const CURL& url,
item->GetMusicInfoTag()->GetTitle()));
item->SetStartOffset(CUtil::ConvertSecsToMilliSecs(m_fctx->chapters[i]->start *
av_q2d(m_fctx->chapters[i]->time_base)));
- item->SetEndOffset(m_fctx->chapters[i]->end * av_q2d(m_fctx->chapters[i]->time_base));
+ item->SetEndOffset(CUtil::ConvertSecsToMilliSecs(m_fctx->chapters[i]->end *
+ av_q2d(m_fctx->chapters[i]->time_base)));
int compare = m_fctx->streams[0]->duration * av_q2d(m_fctx->streams[0]->time_base);
- if (item->GetEndOffset() < 0 || item->GetEndOffset() > compare)
+ if (item->GetEndOffset() < 0 ||
+ item->GetEndOffset() > CUtil::ConvertMilliSecsToSecs(m_fctx->duration))
{
- if (i < m_fctx->nb_chapters-1)
- item->SetEndOffset(m_fctx->chapters[i + 1]->start *
- av_q2d(m_fctx->chapters[i + 1]->time_base));
+ if (i < m_fctx->nb_chapters - 1)
+ item->SetEndOffset(CUtil::ConvertSecsToMilliSecs(
+ m_fctx->chapters[i + 1]->start * av_q2d(m_fctx->chapters[i + 1]->time_base)));
else
- item->SetEndOffset(compare);
+ {
+ item->SetEndOffset(m_fctx->duration); // mka file
+ if (item->GetEndOffset() < 0)
+ item->SetEndOffset(compare); // m4b file
+ }
}
- item->SetEndOffset(CUtil::ConvertSecsToMilliSecs(item->GetEndOffset()));
item->GetMusicInfoTag()->SetDuration(
CUtil::ConvertMilliSecsToSecsInt(item->GetEndOffset() - item->GetStartOffset()));
item->SetProperty("item_start", item->GetStartOffset());
+ item->SetProperty("audio_bookmark", item->GetStartOffset());
if (!thumb.empty())
item->SetArt("thumb", thumb);
items.Add(item);
diff --git a/xbmc/filesystem/CurlFile.cpp b/xbmc/filesystem/CurlFile.cpp
index 0269dbfc48..58215701b1 100644
--- a/xbmc/filesystem/CurlFile.cpp
+++ b/xbmc/filesystem/CurlFile.cpp
@@ -1097,7 +1097,15 @@ bool CCurlFile::Open(const CURL& url)
m_httpresponse = m_state->Connect(m_bufferSize);
- if (m_httpresponse <= 0 || (m_failOnError && m_httpresponse >= 400))
+ long hte = 0;
+
+ // Allow HTTP response code 0 for file:// protocol
+ if (url2.IsProtocol("file"))
+ {
+ hte = -1;
+ }
+
+ if (m_httpresponse <= hte || (m_failOnError && m_httpresponse >= 400))
{
std::string error;
if (m_httpresponse >= 400 && CServiceBroker::GetLogging().CanLogComponent(LOGCURL))
diff --git a/xbmc/filesystem/DirectoryFactory.cpp b/xbmc/filesystem/DirectoryFactory.cpp
index 465816c4b9..e9c76cba97 100644
--- a/xbmc/filesystem/DirectoryFactory.cpp
+++ b/xbmc/filesystem/DirectoryFactory.cpp
@@ -97,7 +97,14 @@ using namespace XFILE;
*/
IDirectory* CDirectoryFactory::Create(const CFileItem& item)
{
- return Create(CURL{item.GetDynPath()});
+ CURL curl{item.GetDynPath()};
+
+ // Store the mimetype, allowing the PlayListFactory to set it on the created FileItem
+ const std::string& mimeType = item.GetMimeType();
+ if (!mimeType.empty())
+ curl.SetOption("mimetype", mimeType);
+
+ return Create(curl);
}
/*!
diff --git a/xbmc/filesystem/PlaylistFileDirectory.cpp b/xbmc/filesystem/PlaylistFileDirectory.cpp
index d95989067f..18e5d7affa 100644
--- a/xbmc/filesystem/PlaylistFileDirectory.cpp
+++ b/xbmc/filesystem/PlaylistFileDirectory.cpp
@@ -25,12 +25,11 @@ namespace XFILE
bool CPlaylistFileDirectory::GetDirectory(const CURL& url, CFileItemList& items)
{
- const std::string pathToUrl = url.Get();
- std::unique_ptr<PLAYLIST::CPlayList> pPlayList(PLAYLIST::CPlayListFactory::Create(pathToUrl));
+ std::unique_ptr<PLAYLIST::CPlayList> pPlayList(PLAYLIST::CPlayListFactory::Create(url));
if (nullptr != pPlayList)
{
// load it
- if (!pPlayList->Load(pathToUrl))
+ if (!pPlayList->Load(url.Get()))
return false; //hmmm unable to load playlist?
PLAYLIST::CPlayList playlist = *pPlayList;
@@ -47,12 +46,11 @@ namespace XFILE
bool CPlaylistFileDirectory::ContainsFiles(const CURL& url)
{
- const std::string pathToUrl = url.Get();
- std::unique_ptr<PLAYLIST::CPlayList> pPlayList(PLAYLIST::CPlayListFactory::Create(pathToUrl));
+ std::unique_ptr<PLAYLIST::CPlayList> pPlayList(PLAYLIST::CPlayListFactory::Create(url));
if (nullptr != pPlayList)
{
// load it
- if (!pPlayList->Load(pathToUrl))
+ if (!pPlayList->Load(url.Get()))
return false; //hmmm unable to load playlist?
return (pPlayList->size() > 1);
diff --git a/xbmc/filesystem/SmartPlaylistDirectory.cpp b/xbmc/filesystem/SmartPlaylistDirectory.cpp
index 8acddd21b6..d82727900d 100644
--- a/xbmc/filesystem/SmartPlaylistDirectory.cpp
+++ b/xbmc/filesystem/SmartPlaylistDirectory.cpp
@@ -297,9 +297,23 @@ namespace XFILE
items.SetProperty(PROPERTY_GROUP_MIXED, playlist.IsGroupMixed());
}
- // sort grouped list by label
+ // sort grouped list by label unless random was specified for musicvideo artists
if (items.Size() > 1 && !group.empty())
- items.Sort(SortByLabel, SortOrderAscending, CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_FILELISTS_IGNORETHEWHENSORTING) ? SortAttributeIgnoreArticle : SortAttributeNone);
+ {
+ if (playlist.GetOrder() == SortByRandom && group == "actors" &&
+ playlist.GetType() == "musicvideos")
+ items.Sort(SortByRandom, SortOrderAscending,
+ CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(
+ CSettings::SETTING_FILELISTS_IGNORETHEWHENSORTING)
+ ? SortAttributeIgnoreArticle
+ : SortAttributeNone);
+ else
+ items.Sort(SortByLabel, SortOrderAscending,
+ CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(
+ CSettings::SETTING_FILELISTS_IGNORETHEWHENSORTING)
+ ? SortAttributeIgnoreArticle
+ : SortAttributeNone);
+ }
// go through and set the playlist order
for (int i = 0; i < items.Size(); i++)
diff --git a/xbmc/filesystem/XbtFile.cpp b/xbmc/filesystem/XbtFile.cpp
index 3f4061ea04..34860dac43 100644
--- a/xbmc/filesystem/XbtFile.cpp
+++ b/xbmc/filesystem/XbtFile.cpp
@@ -12,6 +12,7 @@
#include "filesystem/File.h"
#include "filesystem/XbtManager.h"
#include "guilib/TextureBundleXBT.h"
+#include "guilib/TextureFormats.h"
#include "guilib/XBTFReader.h"
#include "utils/StringUtils.h"
@@ -314,6 +315,42 @@ XB_FMT CXbtFile::GetImageFormat() const
return frame.GetFormat();
}
+KD_TEX_FMT CXbtFile::GetKDFormat() const
+{
+ CXBTFFrame frame;
+ if (!GetFirstFrame(frame))
+ return KD_TEX_FMT_UNKNOWN;
+
+ return frame.GetKDFormat();
+}
+
+KD_TEX_FMT CXbtFile::GetKDFormatType() const
+{
+ CXBTFFrame frame;
+ if (!GetFirstFrame(frame))
+ return KD_TEX_FMT_UNKNOWN;
+
+ return frame.GetKDFormatType();
+}
+
+KD_TEX_ALPHA CXbtFile::GetKDAlpha() const
+{
+ CXBTFFrame frame;
+ if (!GetFirstFrame(frame))
+ return KD_TEX_ALPHA_OPAQUE;
+
+ return frame.GetKDAlpha();
+}
+
+KD_TEX_SWIZ CXbtFile::GetKDSwizzle() const
+{
+ CXBTFFrame frame;
+ if (!GetFirstFrame(frame))
+ return KD_TEX_SWIZ_RGBA;
+
+ return frame.GetKDSwizzle();
+}
+
bool CXbtFile::HasImageAlpha() const
{
CXBTFFrame frame;
diff --git a/xbmc/filesystem/XbtFile.h b/xbmc/filesystem/XbtFile.h
index 6da937c907..ff36fc809d 100644
--- a/xbmc/filesystem/XbtFile.h
+++ b/xbmc/filesystem/XbtFile.h
@@ -43,6 +43,10 @@ public:
uint32_t GetImageWidth() const;
uint32_t GetImageHeight() const;
XB_FMT GetImageFormat() const;
+ KD_TEX_FMT GetKDFormat() const;
+ KD_TEX_FMT GetKDFormatType() const;
+ KD_TEX_ALPHA GetKDAlpha() const;
+ KD_TEX_SWIZ GetKDSwizzle() const;
bool HasImageAlpha() const;
private:
diff --git a/xbmc/filesystem/ZeroconfDirectory.cpp b/xbmc/filesystem/ZeroconfDirectory.cpp
index d05184e394..209f5f37b0 100644
--- a/xbmc/filesystem/ZeroconfDirectory.cpp
+++ b/xbmc/filesystem/ZeroconfDirectory.cpp
@@ -13,12 +13,14 @@
#include "FileItemList.h"
#include "URL.h"
#include "network/ZeroconfBrowser.h"
+#include "utils/ArtUtils.h"
#include "utils/URIUtils.h"
#include "utils/log.h"
#include <cassert>
#include <stdexcept>
+using namespace KODI;
using namespace XFILE;
CZeroconfDirectory::CZeroconfDirectory()
@@ -137,7 +139,7 @@ bool GetDirectoryFromTxtRecords(const CZeroconfBrowser::ZeroconfService& zerocon
item->SetLabelPreformatted(true);
//just set the default folder icon
- item->FillInDefaultIcon();
+ ART::FillInDefaultIcon(*item);
item->m_bIsShareOrDrive=true;
items.Add(item);
ret = true;
@@ -173,7 +175,7 @@ bool CZeroconfDirectory::GetDirectory(const CURL& url, CFileItemList &items)
item->SetLabel(it.GetName() + " (" + protocol + ")");
item->SetLabelPreformatted(true);
//just set the default folder icon
- item->FillInDefaultIcon();
+ ART::FillInDefaultIcon(*item);
items.Add(item);
}
}
diff --git a/xbmc/games/addons/GameClientProperties.cpp b/xbmc/games/addons/GameClientProperties.cpp
index ae081afa92..9395e443ca 100644
--- a/xbmc/games/addons/GameClientProperties.cpp
+++ b/xbmc/games/addons/GameClientProperties.cpp
@@ -12,15 +12,21 @@
#include "FileItemList.h"
#include "GameClient.h"
#include "ServiceBroker.h"
+#include "addons/AddonInstaller.h"
#include "addons/AddonManager.h"
#include "addons/GameResource.h"
#include "addons/IAddon.h"
#include "addons/addoninfo/AddonInfo.h"
#include "addons/addoninfo/AddonType.h"
+#include "dialogs/GUIDialogProgress.h"
+#include "dialogs/GUIDialogSelect.h"
#include "dialogs/GUIDialogYesNo.h"
#include "filesystem/Directory.h"
#include "filesystem/SpecialProtocol.h"
+#include "guilib/GUIComponent.h"
+#include "guilib/GUIWindowManager.h"
#include "guilib/LocalizeStrings.h"
+#include "guilib/WindowIDs.h"
#include "messaging/helpers/DialogOKHelper.h"
#include "utils/StringUtils.h"
#include "utils/Variant.h"
@@ -201,7 +207,8 @@ unsigned int CGameClientProperties::GetExtensionCount(void) const
bool CGameClientProperties::GetProxyAddons(ADDON::VECADDONS& addons)
{
ADDON::VECADDONS ret;
- std::vector<std::string> missingDependencies; // ID or name of missing dependencies
+ std::vector<std::string> disabledDependencies; // ID or name of disabled dependencies
+ std::vector<std::string> uninstalledDependencies; // ID or name of uninstalled dependencies
for (const auto& dependency : m_parent.GetDependencies())
{
@@ -218,14 +225,14 @@ bool CGameClientProperties::GetProxyAddons(ADDON::VECADDONS& addons)
if (!CServiceBroker::GetAddonMgr().EnableAddon(dependency.id))
{
CLog::Log(LOGERROR, "Failed to enable add-on {}", dependency.id);
- missingDependencies.emplace_back(addon->Name());
+ disabledDependencies.emplace_back(addon->Name());
addon.reset();
}
}
else
{
CLog::Log(LOGERROR, "User chose to not enable add-on {}", dependency.id);
- missingDependencies.emplace_back(addon->Name());
+ disabledDependencies.emplace_back(addon->Name());
addon.reset();
}
}
@@ -242,11 +249,35 @@ bool CGameClientProperties::GetProxyAddons(ADDON::VECADDONS& addons)
else
{
CLog::Log(LOGERROR, "Missing mandatory dependency {}", dependency.id);
- missingDependencies.emplace_back(dependency.id);
+ uninstalledDependencies.emplace_back(dependency.id);
}
}
}
+ // Attempt to install missing dependencies
+ if (disabledDependencies.empty() && !uninstalledDependencies.empty())
+ {
+ if (InstallDependencies(uninstalledDependencies))
+ uninstalledDependencies.clear();
+ }
+
+ // IDs of all missing dependencies
+ std::vector<std::string> missingDependencies;
+
+ // Reserve enough space to avoid multiple allocations
+ missingDependencies.reserve(disabledDependencies.size() + uninstalledDependencies.size());
+
+ // Move elements from the first vector
+ missingDependencies.insert(missingDependencies.end(),
+ std::make_move_iterator(disabledDependencies.begin()),
+ std::make_move_iterator(disabledDependencies.end()));
+
+ // Move elements from the second vector
+ missingDependencies.insert(missingDependencies.end(),
+ std::make_move_iterator(uninstalledDependencies.begin()),
+ std::make_move_iterator(uninstalledDependencies.end()));
+
+ // Show an error dialog if any dependencies are still missing
if (!missingDependencies.empty())
{
std::string strDependencies = StringUtils::Join(missingDependencies, ", ");
@@ -289,3 +320,111 @@ bool CGameClientProperties::HasProxyDll(const std::string& strLibPath) const
}
return false;
}
+
+bool CGameClientProperties::InstallDependencies(const std::vector<std::string>& addons)
+{
+ // Only proceed if we have a GUI
+ CGUIComponent* const gui = CServiceBroker::GetGUI();
+ if (gui == nullptr)
+ return false;
+
+ const CGUIWindowManager& windowManager = gui->GetWindowManager();
+
+ // Get GUI dialogs
+ auto* selectDialog = windowManager.GetWindow<CGUIDialogSelect>(WINDOW_DIALOG_SELECT);
+ auto* progressDialog = windowManager.GetWindow<CGUIDialogProgress>(WINDOW_DIALOG_PROGRESS);
+
+ // We can only install add-ons if the dialogs are present
+ if (selectDialog == nullptr || progressDialog == nullptr)
+ return false;
+
+ // Get installable add-ons
+ VECADDONS installableAddons;
+ for (const std::string& dependency : addons)
+ {
+ AddonPtr addon;
+ if (CServiceBroker::GetAddonMgr().FindInstallableById(dependency, addon) && addon)
+ installableAddons.emplace_back(std::move(addon));
+ else
+ CLog::Log(LOGERROR, "Failed to find installable add-on for {}", dependency);
+ }
+
+ // Verify all add-ons are installable
+ if (addons.size() != installableAddons.size())
+ return false;
+
+ // Setup select dialog
+ selectDialog->Reset();
+ selectDialog->SetHeading(39020); // "The following additional add-ons will be installed"
+ selectDialog->SetUseDetails(true);
+ selectDialog->EnableButton(true, 186); // "OK""
+ selectDialog->SetButtonFocus(true);
+ for (const auto& addon : installableAddons)
+ {
+ CFileItem item{addon->Name()};
+ item.SetArt("icon", addon->Icon());
+ selectDialog->Add(item);
+ }
+
+ // Show select dialog
+ selectDialog->Open();
+ if (!selectDialog->IsButtonPressed())
+ {
+ CLog::Log(LOGDEBUG, "Dependency installer: User cancelled installation dialog");
+ return false;
+ }
+
+ CLog::Log(LOGDEBUG, "Dependency installer: Installing {} add-ons", installableAddons.size());
+
+ progressDialog->SetHeading(CVariant{24086}); // "Installing add-on..."
+ progressDialog->SetLine(0, CVariant{""});
+ progressDialog->SetLine(1, CVariant{""});
+ progressDialog->SetLine(2, CVariant{""});
+
+ progressDialog->Open();
+
+ size_t installedCount = 0;
+ while (installedCount < installableAddons.size())
+ {
+ const AddonPtr& addon = installableAddons.at(installedCount);
+
+ // Set dialog text
+ const std::string& progressTemplate = g_localizeStrings.Get(24057); // "Installing {0:s}..."
+ const std::string progressText = StringUtils::Format(progressTemplate, addon->Name());
+ progressDialog->SetLine(0, CVariant{progressText});
+
+ // Set dialog percentage
+ const unsigned int percentage =
+ 100 * (installedCount + 1) / static_cast<unsigned int>(installableAddons.size());
+ progressDialog->SetPercentage(percentage);
+
+ if (!ADDON::CAddonInstaller::GetInstance().InstallOrUpdate(
+ addon->ID(), ADDON::BackgroundJob::CHOICE_NO, ADDON::ModalJob::CHOICE_NO))
+ {
+ CLog::Log(LOGERROR, "Controller installer: Failed to install {}", addon->ID());
+ // "Error"
+ // "Failed to install add-on."
+ MESSAGING::HELPERS::ShowOKDialogText(257, 35256);
+ return false;
+ }
+
+ if (progressDialog->IsCanceled())
+ {
+ CLog::Log(LOGDEBUG, "Controller installer: User cancelled add-on installation");
+ return false;
+ }
+
+ if (windowManager.GetActiveWindowOrDialog() != WINDOW_DIALOG_PROGRESS)
+ {
+ CLog::Log(LOGDEBUG, "Controller installer: Progress dialog is hidden, canceling");
+ return false;
+ }
+
+ installedCount++;
+ }
+
+ CLog::Log(LOGDEBUG, "Controller window: Installed {} controller add-ons", installedCount);
+ progressDialog->Close();
+
+ return true;
+}
diff --git a/xbmc/games/addons/GameClientProperties.h b/xbmc/games/addons/GameClientProperties.h
index efb9a8b566..681c255be0 100644
--- a/xbmc/games/addons/GameClientProperties.h
+++ b/xbmc/games/addons/GameClientProperties.h
@@ -78,6 +78,9 @@ private:
void AddProxyDll(const GameClientPtr& gameClient);
bool HasProxyDll(const std::string& strLibPath) const;
+ // Utility functions
+ static bool InstallDependencies(const std::vector<std::string>& addons);
+
// Construction parameters
const CGameClient& m_parent;
AddonProps_Game& m_properties;
diff --git a/xbmc/games/controllers/dialogs/ControllerInstaller.cpp b/xbmc/games/controllers/dialogs/ControllerInstaller.cpp
index 18f7634a01..4fdc0ebfa4 100644
--- a/xbmc/games/controllers/dialogs/ControllerInstaller.cpp
+++ b/xbmc/games/controllers/dialogs/ControllerInstaller.cpp
@@ -74,6 +74,7 @@ void CControllerInstaller::Process()
pSelectDialog->SetHeading(39020); // "The following additional add-ons will be installed"
pSelectDialog->SetUseDetails(true);
pSelectDialog->EnableButton(true, 186); // "OK""
+ pSelectDialog->SetButtonFocus(true);
for (const auto& it : items)
pSelectDialog->Add(*it);
pSelectDialog->Open();
diff --git a/xbmc/guilib/FFmpegImage.h b/xbmc/guilib/FFmpegImage.h
index 0f7cee380c..8d34def513 100644
--- a/xbmc/guilib/FFmpegImage.h
+++ b/xbmc/guilib/FFmpegImage.h
@@ -9,6 +9,8 @@
#pragma once
#include "iimage.h"
+
+#include <cstdint>
#include <memory>
extern "C"
diff --git a/xbmc/guilib/GUITextLayout.h b/xbmc/guilib/GUITextLayout.h
index 1b01d1db8b..7634d6eb14 100644
--- a/xbmc/guilib/GUITextLayout.h
+++ b/xbmc/guilib/GUITextLayout.h
@@ -187,7 +187,8 @@ private:
inline bool CanWrapAtLetter(character_t letter) const XBMC_FORCE_INLINE
{
character_t ch = letter & 0xffff;
- return ch == L' ' || (ch >=0x4e00 && ch <= 0x9fff);
+ //! @todo: unicode spaces are not handled, to check also all other GUI parts
+ return ch == L' ';
};
static void AppendToUTF32(const std::string &utf8, character_t colStyle, vecText &utf32);
static void AppendToUTF32(const std::wstring &utf16, character_t colStyle, vecText &utf32);
diff --git a/xbmc/guilib/GUIToggleButtonControl.cpp b/xbmc/guilib/GUIToggleButtonControl.cpp
index 650dc3df69..fff49051ae 100644
--- a/xbmc/guilib/GUIToggleButtonControl.cpp
+++ b/xbmc/guilib/GUIToggleButtonControl.cpp
@@ -38,6 +38,7 @@ void CGUIToggleButtonControl::Process(unsigned int currentTime, CDirtyRegionList
m_selectButton.SetPulseOnSelect(m_pulseOnSelect);
ProcessToggle(currentTime);
m_selectButton.DoProcess(currentTime, dirtyregions);
+ CGUIControl::Process(currentTime, dirtyregions);
}
else
CGUIButtonControl::Process(currentTime, dirtyregions);
diff --git a/xbmc/guilib/GUIWindowManager.cpp b/xbmc/guilib/GUIWindowManager.cpp
index fac8742681..065a68cbb8 100644
--- a/xbmc/guilib/GUIWindowManager.cpp
+++ b/xbmc/guilib/GUIWindowManager.cpp
@@ -113,6 +113,7 @@
#include "video/dialogs/GUIDialogVideoSettings.h"
/* PVR related include Files */
+#include "dialogs/GUIDialogSlider.h"
#include "pvr/dialogs/GUIDialogPVRChannelGuide.h"
#include "pvr/dialogs/GUIDialogPVRChannelManager.h"
#include "pvr/dialogs/GUIDialogPVRChannelsOSD.h"
@@ -127,13 +128,12 @@
#include "pvr/dialogs/GUIDialogPVRTimerSettings.h"
#include "pvr/windows/GUIWindowPVRChannels.h"
#include "pvr/windows/GUIWindowPVRGuide.h"
+#include "pvr/windows/GUIWindowPVRProviders.h"
#include "pvr/windows/GUIWindowPVRRecordings.h"
#include "pvr/windows/GUIWindowPVRSearch.h"
#include "pvr/windows/GUIWindowPVRTimerRules.h"
#include "pvr/windows/GUIWindowPVRTimers.h"
-
#include "video/dialogs/GUIDialogTeletext.h"
-#include "dialogs/GUIDialogSlider.h"
#ifdef HAS_OPTICAL_DRIVE
#include "dialogs/GUIDialogPlayEject.h"
#endif
@@ -269,12 +269,14 @@ void CGUIWindowManager::CreateWindows()
Add(new CGUIWindowPVRTVTimers);
Add(new CGUIWindowPVRTVTimerRules);
Add(new CGUIWindowPVRTVSearch);
+ Add(new CGUIWindowPVRTVProviders);
Add(new CGUIWindowPVRRadioChannels);
Add(new CGUIWindowPVRRadioRecordings);
Add(new CGUIWindowPVRRadioGuide);
Add(new CGUIWindowPVRRadioTimers);
Add(new CGUIWindowPVRRadioTimerRules);
Add(new CGUIWindowPVRRadioSearch);
+ Add(new CGUIWindowPVRRadioProviders);
Add(new CGUIDialogPVRRadioRDSInfo);
Add(new CGUIDialogPVRGuideInfo);
Add(new CGUIDialogPVRRecordingInfo);
@@ -393,12 +395,14 @@ bool CGUIWindowManager::DestroyWindows()
DestroyWindow(WINDOW_TV_TIMERS);
DestroyWindow(WINDOW_TV_TIMER_RULES);
DestroyWindow(WINDOW_TV_SEARCH);
+ DestroyWindow(WINDOW_TV_PROVIDERS);
DestroyWindow(WINDOW_RADIO_CHANNELS);
DestroyWindow(WINDOW_RADIO_RECORDINGS);
DestroyWindow(WINDOW_RADIO_GUIDE);
DestroyWindow(WINDOW_RADIO_TIMERS);
DestroyWindow(WINDOW_RADIO_TIMER_RULES);
DestroyWindow(WINDOW_RADIO_SEARCH);
+ DestroyWindow(WINDOW_RADIO_PROVIDERS);
DestroyWindow(WINDOW_DIALOG_PVR_GUIDE_INFO);
DestroyWindow(WINDOW_DIALOG_PVR_RECORDING_INFO);
DestroyWindow(WINDOW_DIALOG_PVR_TIMER_SETTING);
diff --git a/xbmc/guilib/Texture.cpp b/xbmc/guilib/Texture.cpp
index 31a6029636..b59e3588e5 100644
--- a/xbmc/guilib/Texture.cpp
+++ b/xbmc/guilib/Texture.cpp
@@ -16,8 +16,10 @@
#include "filesystem/ResourceFile.h"
#include "filesystem/XbtFile.h"
#include "guilib/TextureBase.h"
+#include "guilib/TextureFormats.h"
#include "guilib/iimage.h"
#include "guilib/imagefactory.h"
+#include "messaging/ApplicationMessenger.h"
#include "utils/URIUtils.h"
#include "utils/log.h"
#include "windowing/WinSystem.h"
@@ -184,9 +186,21 @@ bool CTexture::LoadFromFileInternal(const std::string& texturePath,
XFILE::CXbtFile xbtFile;
if (!xbtFile.Open(url))
return false;
-
- return LoadFromMemory(xbtFile.GetImageWidth(), xbtFile.GetImageHeight(), 0,
- xbtFile.GetImageFormat(), xbtFile.HasImageAlpha(), buf.data());
+ if (xbtFile.GetKDFormatType())
+ {
+ return UploadFromMemory(xbtFile.GetImageWidth(), xbtFile.GetImageHeight(), 0, buf.data(),
+ xbtFile.GetKDFormat(), xbtFile.GetKDAlpha(), xbtFile.GetKDSwizzle());
+ }
+ else if (xbtFile.GetImageFormat() == XB_FMT_A8R8G8B8)
+ {
+ KD_TEX_ALPHA alpha = xbtFile.HasImageAlpha() ? KD_TEX_ALPHA_STRAIGHT : KD_TEX_ALPHA_OPAQUE;
+ return UploadFromMemory(xbtFile.GetImageWidth(), xbtFile.GetImageHeight(), 0, buf.data(),
+ KD_TEX_FMT_SDR_BGRA8, alpha, KD_TEX_SWIZ_RGBA);
+ }
+ else
+ {
+ return false;
+ }
}
IImage* pImage;
@@ -271,7 +285,7 @@ bool CTexture::LoadIImage(IImage* pImage,
if (pImage->Orientation())
m_orientation = pImage->Orientation() - 1;
- m_hasAlpha = pImage->hasAlpha();
+ m_textureAlpha = pImage->hasAlpha() ? KD_TEX_ALPHA_STRAIGHT : KD_TEX_ALPHA_OPAQUE;
m_originalWidth = pImage->originalWidth();
m_originalHeight = pImage->originalHeight();
m_imageWidth = pImage->Width();
@@ -308,11 +322,67 @@ bool CTexture::LoadFromMemory(unsigned int width,
m_imageWidth = m_originalWidth = width;
m_imageHeight = m_originalHeight = height;
m_format = format;
- m_hasAlpha = hasAlpha;
+ m_textureAlpha = hasAlpha ? KD_TEX_ALPHA_STRAIGHT : KD_TEX_ALPHA_OPAQUE;
Update(width, height, pitch, format, pixels, false);
return true;
}
+bool CTexture::UploadFromMemory(unsigned int width,
+ unsigned int height,
+ unsigned int pitch,
+ unsigned char* pixels,
+ KD_TEX_FMT format,
+ KD_TEX_ALPHA alpha,
+ KD_TEX_SWIZ swizzle)
+{
+ m_imageWidth = m_textureWidth = m_originalWidth = width;
+ m_imageHeight = m_textureHeight = m_originalHeight = height;
+ m_textureFormat = format;
+ m_textureAlpha = alpha;
+ m_textureSwizzle = swizzle;
+
+ if (!SupportsFormat(m_textureFormat, m_textureSwizzle) && !ConvertToLegacy(width, height, pixels))
+ {
+ CLog::LogF(
+ LOGERROR,
+ "Failed to upload texture. Format {} and swizzle {} not supported by the texture pipeline.",
+ m_textureFormat, m_textureSwizzle);
+
+ m_loadedToGPU = true;
+ return false;
+ }
+
+ if (CServiceBroker::GetAppMessenger()->IsProcessThread())
+ {
+ if (m_pixels)
+ {
+ LoadToGPU();
+ }
+ else
+ {
+ // just a borrowed buffer
+ m_pixels = pixels;
+ m_bCacheMemory = true;
+ LoadToGPU();
+ m_bCacheMemory = false;
+ m_pixels = nullptr;
+ }
+ }
+ else if (!m_pixels)
+ {
+ size_t size = GetPitch() * GetRows();
+ m_pixels = static_cast<unsigned char*>(KODI::MEMORY::AlignedMalloc(size, 32));
+ if (m_pixels == nullptr)
+ {
+ CLog::LogF(LOGERROR, "Could not allocate {} bytes. Out of memory.", size);
+ return false;
+ }
+ std::memcpy(m_pixels, pixels, size);
+ }
+
+ return true;
+}
+
bool CTexture::LoadPaletted(unsigned int width,
unsigned int height,
unsigned int pitch,
diff --git a/xbmc/guilib/Texture.h b/xbmc/guilib/Texture.h
index 6e5a4ff1a5..09b9577a7e 100644
--- a/xbmc/guilib/Texture.h
+++ b/xbmc/guilib/Texture.h
@@ -74,6 +74,24 @@ public:
XB_FMT format,
bool hasAlpha,
const unsigned char* pixels);
+ /*! \brief Attempts to upload a texture directly from a provided buffer
+ Unlike LoadFromMemory() which copies the texture into an intermediate buffer, the texture gets uploaded directly to
+ the GPU if circumstances allow.
+ \param width the width of the texture.
+ \param height the height of the texture.
+ \param pitch the pitch of the texture.
+ \param pixels pointer to the texture buffer.
+ \param format the format of the texture.
+ \param alpha the alpha type of the texture.
+ \param swizzle the swizzle pattern of the texture.
+ */
+ bool UploadFromMemory(unsigned int width,
+ unsigned int height,
+ unsigned int pitch,
+ unsigned char* pixels,
+ KD_TEX_FMT format = KD_TEX_FMT_SDR_RGBA8,
+ KD_TEX_ALPHA alpha = KD_TEX_ALPHA_OPAQUE,
+ KD_TEX_SWIZ swizzle = KD_TEX_SWIZ_RGBA);
bool LoadPaletted(unsigned int width,
unsigned int height,
unsigned int pitch,
@@ -102,6 +120,16 @@ public:
virtual void SyncGPU(){};
virtual void BindToUnit(unsigned int unit) = 0;
+ /*!
+ * \brief Checks if the processing pipeline can handle the texture format/swizzle
+ \param format the format of the texture.
+ \return true if the texturing pipeline supports the format
+ */
+ virtual bool SupportsFormat(KD_TEX_FMT textureFormat, KD_TEX_SWIZ textureSwizzle)
+ {
+ return !(textureFormat & KD_TEX_FMT_TYPE_MASK) && textureSwizzle == KD_TEX_SWIZ_RGBA;
+ }
+
private:
// no copy constructor
CTexture(const CTexture& copy) = delete;
diff --git a/xbmc/guilib/TextureBase.cpp b/xbmc/guilib/TextureBase.cpp
index e6154ac624..5f2aff8bf7 100644
--- a/xbmc/guilib/TextureBase.cpp
+++ b/xbmc/guilib/TextureBase.cpp
@@ -398,3 +398,77 @@ void CTextureBase::SetKDFormat(XB_FMT xbFMT)
return;
}
}
+
+bool CTextureBase::ConvertToLegacy(uint32_t width, uint32_t height, uint8_t* src)
+{
+ if (m_textureFormat == KD_TEX_FMT_SDR_BGRA8 && m_textureSwizzle == KD_TEX_SWIZ_RGBA)
+ {
+ m_format = XB_FMT_A8R8G8B8;
+ return true;
+ }
+
+ if (m_textureFormat == KD_TEX_FMT_SDR_R8)
+ {
+ if (m_textureSwizzle != KD_TEX_SWIZ_111R && m_textureSwizzle != KD_TEX_SWIZ_RRR1 &&
+ m_textureSwizzle != KD_TEX_SWIZ_RRRR)
+ return false;
+ }
+ else if (m_textureFormat == KD_TEX_FMT_SDR_RG8)
+ {
+ if (m_textureSwizzle != KD_TEX_SWIZ_RRRG)
+ return false;
+ }
+ else
+ {
+ return false;
+ }
+
+ size_t size = GetPitch() * GetRows();
+
+ Allocate(width, height, XB_FMT_A8R8G8B8);
+
+ if (m_textureSwizzle == KD_TEX_SWIZ_111R)
+ {
+ for (int32_t i = size - 1; i >= 0; i--)
+ {
+ m_pixels[i * 4 + 3] = src[i];
+ m_pixels[i * 4 + 2] = 0xff;
+ m_pixels[i * 4 + 1] = 0xff;
+ m_pixels[i * 4] = 0xff;
+ }
+ }
+ else if (m_textureSwizzle == KD_TEX_SWIZ_RRR1)
+ {
+ for (int32_t i = size - 1; i >= 0; i--)
+ {
+ m_pixels[i * 4 + 3] = 0xff;
+ m_pixels[i * 4 + 2] = src[i];
+ m_pixels[i * 4 + 1] = src[i];
+ m_pixels[i * 4] = src[i];
+ }
+ }
+ else if (m_textureSwizzle == KD_TEX_SWIZ_RRRR)
+ {
+ for (int32_t i = size - 1; i >= 0; i--)
+ {
+ m_pixels[i * 4 + 3] = src[i];
+ m_pixels[i * 4 + 2] = src[i];
+ m_pixels[i * 4 + 1] = src[i];
+ m_pixels[i * 4] = src[i];
+ }
+ }
+ else if (m_textureSwizzle == KD_TEX_SWIZ_RRRG)
+ {
+ for (int32_t i = size / 2 - 1; i >= 0; i--)
+ {
+ m_pixels[i * 4 + 3] = src[i * 2 + 1];
+ m_pixels[i * 4 + 2] = src[i * 2];
+ m_pixels[i * 4 + 1] = src[i * 2];
+ m_pixels[i * 4] = src[i * 2];
+ }
+ }
+
+ m_textureFormat = KD_TEX_FMT_SDR_BGRA8;
+ m_textureSwizzle = KD_TEX_SWIZ_RGBA;
+ return true;
+}
diff --git a/xbmc/guilib/TextureBase.h b/xbmc/guilib/TextureBase.h
index bbabe2ae46..51f8caeb20 100644
--- a/xbmc/guilib/TextureBase.h
+++ b/xbmc/guilib/TextureBase.h
@@ -30,8 +30,11 @@ public:
CTextureBase() = default;
~CTextureBase() = default;
- bool HasAlpha() const { return m_hasAlpha; }
- void SetAlpha(bool hasAlpha) { m_hasAlpha = hasAlpha; }
+ bool HasAlpha() const { return m_textureAlpha != KD_TEX_ALPHA_OPAQUE; }
+ void SetAlpha(bool hasAlpha)
+ {
+ m_textureAlpha = hasAlpha ? KD_TEX_ALPHA_STRAIGHT : KD_TEX_ALPHA_OPAQUE;
+ }
/*! \brief sets mipmapping. do not use in new code. will be replaced with proper scaling. */
void SetMipmapping() { m_mipmapping = true; }
@@ -92,6 +95,10 @@ protected:
void SetKDFormat(XB_FMT xbFMT);
+ /*! \brief Textures might be in a single/dual channel format with L/A/I/LA swizzle. DX and GLES 2.0 don't
+ handle some/all at the moment. This function can convert the texture into a traditional BGRA format.*/
+ bool ConvertToLegacy(uint32_t width, uint32_t height, uint8_t* src);
+
uint32_t m_imageWidth{0};
uint32_t m_imageHeight{0};
uint32_t m_textureWidth{0};
@@ -110,7 +117,6 @@ protected:
XB_FMT m_format{XB_FMT_UNKNOWN}; // legacy XB format, deprecated
int32_t m_orientation{0};
- bool m_hasAlpha{true};
bool m_mipmapping{false};
TEXTURE_SCALING m_scalingMethod{TEXTURE_SCALING::LINEAR};
bool m_bCacheMemory{false};
diff --git a/xbmc/guilib/TextureBundleXBT.cpp b/xbmc/guilib/TextureBundleXBT.cpp
index f557d28a26..e3a6f8e60e 100644
--- a/xbmc/guilib/TextureBundleXBT.cpp
+++ b/xbmc/guilib/TextureBundleXBT.cpp
@@ -16,6 +16,7 @@
#include "commons/ilog.h"
#include "filesystem/SpecialProtocol.h"
#include "filesystem/XbtManager.h"
+#include "guilib/TextureFormats.h"
#include "settings/Settings.h"
#include "settings/SettingsComponent.h"
#include "utils/StringUtils.h"
@@ -237,9 +238,18 @@ std::unique_ptr<CTexture> CTextureBundleXBT::ConvertFrameToTexture(const std::st
// create an xbmc texture
std::unique_ptr<CTexture> texture = CTexture::CreateTexture();
- texture->LoadFromMemory(frame.GetWidth(), frame.GetHeight(), 0, frame.GetFormat(),
- frame.HasAlpha(), buffer.data());
+ if (frame.GetKDFormatType())
+ {
+ texture->UploadFromMemory(frame.GetWidth(), frame.GetHeight(), 0, buffer.data(),
+ frame.GetKDFormat(), frame.GetKDAlpha(), frame.GetKDSwizzle());
+ }
+ else if (frame.GetFormat() == XB_FMT_A8R8G8B8)
+ {
+ KD_TEX_ALPHA alpha = frame.HasAlpha() ? KD_TEX_ALPHA_STRAIGHT : KD_TEX_ALPHA_OPAQUE;
+ texture->UploadFromMemory(frame.GetWidth(), frame.GetHeight(), 0, buffer.data(),
+ KD_TEX_FMT_SDR_BGRA8, alpha, KD_TEX_SWIZ_RGBA);
+ }
return texture;
}
diff --git a/xbmc/guilib/TextureGL.cpp b/xbmc/guilib/TextureGL.cpp
index 78dd891b1f..df4e2304aa 100644
--- a/xbmc/guilib/TextureGL.cpp
+++ b/xbmc/guilib/TextureGL.cpp
@@ -9,6 +9,7 @@
#include "TextureGL.h"
#include "ServiceBroker.h"
+#include "guilib/TextureFormats.h"
#include "guilib/TextureManager.h"
#include "rendering/RenderSystem.h"
#include "settings/AdvancedSettings.h"
@@ -19,6 +20,118 @@
#include <memory>
+namespace
+{
+// clang-format off
+static const std::map<KD_TEX_FMT, TextureFormat> TextureMapping
+{
+#if defined(GL_EXT_texture_sRGB_R8) && (GL_EXT_texture_sRGB_RG8)
+ {KD_TEX_FMT_SDR_R8, {GL_R8, GL_SR8_EXT, GL_RED}},
+ {KD_TEX_FMT_SDR_RG8, {GL_RG8, GL_SRG8_EXT, GL_RG}},
+#else
+ {KD_TEX_FMT_SDR_R8, {GL_R8, GL_FALSE, GL_RED}},
+ {KD_TEX_FMT_SDR_RG8, {GL_RG8, GL_FALSE, GL_RG}},
+#endif
+ {KD_TEX_FMT_SDR_R5G6B5, {GL_RGB565, GL_FALSE, GL_RGB, GL_UNSIGNED_SHORT_5_6_5}},
+ {KD_TEX_FMT_SDR_RGB5_A1, {GL_RGB5_A1, GL_FALSE, GL_RGBA, GL_UNSIGNED_SHORT_5_5_5_1}},
+ {KD_TEX_FMT_SDR_RGBA4, {GL_RGBA4, GL_FALSE, GL_RGBA, GL_UNSIGNED_SHORT_4_4_4_4}},
+ {KD_TEX_FMT_SDR_RGB8, {GL_RGB8, GL_SRGB8, GL_RGB}},
+ {KD_TEX_FMT_SDR_RGBA8, {GL_RGBA8, GL_SRGB8_ALPHA8, GL_RGBA}},
+ {KD_TEX_FMT_SDR_BGRA8, {GL_RGBA8, GL_SRGB8_ALPHA8, GL_BGRA}},
+
+#if defined(GL_VERSION_3_0)
+ {KD_TEX_FMT_HDR_R16f, {GL_R16F, GL_FALSE, GL_RED, GL_HALF_FLOAT}},
+ {KD_TEX_FMT_HDR_RG16f, {GL_RG16F, GL_FALSE, GL_RG, GL_HALF_FLOAT}},
+ {KD_TEX_FMT_HDR_R11F_G11F_B10F, {GL_R11F_G11F_B10F, GL_FALSE, GL_RGB, GL_UNSIGNED_INT_10F_11F_11F_REV}},
+ {KD_TEX_FMT_HDR_RGB9_E5, {GL_RGB9_E5, GL_FALSE, GL_RGB, GL_UNSIGNED_INT_5_9_9_9_REV}},
+ {KD_TEX_FMT_HDR_RGB10_A2, {GL_RGB10_A2, GL_FALSE, GL_RGBA, GL_UNSIGNED_INT_2_10_10_10_REV}},
+ {KD_TEX_FMT_HDR_RGBA16f, {GL_RGBA16F, GL_FALSE, GL_RGBA, GL_HALF_FLOAT}},
+#endif
+
+#if defined(GL_EXT_texture_compression_s3tc) && (GL_EXT_texture_sRGB)
+ {KD_TEX_FMT_S3TC_RGB8, {GL_COMPRESSED_RGB_S3TC_DXT1_EXT, GL_COMPRESSED_SRGB_S3TC_DXT1_EXT}},
+ {KD_TEX_FMT_S3TC_RGB8_A1, {GL_COMPRESSED_RGBA_S3TC_DXT1_EXT, GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT}},
+ {KD_TEX_FMT_S3TC_RGB8_A4, {GL_COMPRESSED_RGBA_S3TC_DXT3_EXT, GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT}},
+ {KD_TEX_FMT_S3TC_RGBA8, {GL_COMPRESSED_RGBA_S3TC_DXT5_EXT, GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT}},
+#elif defined(GL_EXT_texture_compression_s3tc)
+ {KD_TEX_FMT_S3TC_RGB8, {GL_COMPRESSED_RGB_S3TC_DXT1_EXT}},
+ {KD_TEX_FMT_S3TC_RGB8_A1, {GL_COMPRESSED_RGBA_S3TC_DXT1_EXT}},
+ {KD_TEX_FMT_S3TC_RGB8_A4, {GL_COMPRESSED_RGBA_S3TC_DXT3_EXT}},
+ {KD_TEX_FMT_S3TC_RGBA8, {GL_COMPRESSED_RGBA_S3TC_DXT5_EXT}},
+#elif defined(GL_EXT_texture_compression_dxt1)
+ {KD_TEX_FMT_S3TC_RGB8, {GL_COMPRESSED_RGB_S3TC_DXT1_EXT}},
+ {KD_TEX_FMT_S3TC_RGB8_A1, {GL_COMPRESSED_RGBA_S3TC_DXT1_EXT}},
+#endif
+
+#if defined(GL_EXT_texture_compression_rgtc)
+ {KD_TEX_FMT_RGTC_R11, {GL_COMPRESSED_RED_RGTC1_EXT}},
+ {KD_TEX_FMT_RGTC_RG11, {GL_COMPRESSED_RED_GREEN_RGTC2_EXT}},
+#endif
+
+#if defined(GL_ARB_texture_compression_bptc)
+ {KD_TEX_FMT_BPTC_RGB16F, {GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT_ARB}},
+ {KD_TEX_FMT_BPTC_RGBA8, {GL_COMPRESSED_RGBA_BPTC_UNORM_ARB, GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM_ARB}},
+#endif
+
+#if defined(GL_VERSION_4_3)
+ {KD_TEX_FMT_ETC1_RGB8, {GL_COMPRESSED_RGB8_ETC2, GL_COMPRESSED_SRGB8_ETC2}},
+
+ {KD_TEX_FMT_ETC2_R11, {GL_COMPRESSED_R11_EAC}},
+ {KD_TEX_FMT_ETC2_RG11, {GL_COMPRESSED_RG11_EAC}},
+ {KD_TEX_FMT_ETC2_RGB8, {GL_COMPRESSED_RGB8_ETC2, GL_COMPRESSED_SRGB8_ETC2}},
+ {KD_TEX_FMT_ETC2_RGB8_A1, {GL_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2, GL_COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2}},
+ {KD_TEX_FMT_ETC2_RGBA8, {GL_COMPRESSED_RGBA8_ETC2_EAC, GL_COMPRESSED_SRGB8_ALPHA8_ETC2_EAC}},
+#endif
+
+#if defined(GL_KHR_texture_compression_astc_ldr) || (GL_KHR_texture_compression_astc_hdr)
+ {KD_TEX_FMT_ASTC_LDR_4x4, {GL_COMPRESSED_RGBA_ASTC_4x4_KHR, GL_COMPRESSED_SRGB8_ALPHA8_ASTC_4x4_KHR}},
+ {KD_TEX_FMT_ASTC_LDR_5x4, {GL_COMPRESSED_RGBA_ASTC_5x4_KHR, GL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x4_KHR}},
+ {KD_TEX_FMT_ASTC_LDR_5x5, {GL_COMPRESSED_RGBA_ASTC_5x5_KHR, GL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x5_KHR}},
+ {KD_TEX_FMT_ASTC_LDR_6x5, {GL_COMPRESSED_RGBA_ASTC_6x5_KHR, GL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x5_KHR}},
+ {KD_TEX_FMT_ASTC_LDR_6x6, {GL_COMPRESSED_RGBA_ASTC_6x6_KHR, GL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x6_KHR}},
+ {KD_TEX_FMT_ASTC_LDR_8x5, {GL_COMPRESSED_RGBA_ASTC_8x5_KHR, GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x5_KHR}},
+ {KD_TEX_FMT_ASTC_LDR_8x6, {GL_COMPRESSED_RGBA_ASTC_8x6_KHR, GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x6_KHR}},
+ {KD_TEX_FMT_ASTC_LDR_8x8, {GL_COMPRESSED_RGBA_ASTC_8x8_KHR, GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x8_KHR}},
+ {KD_TEX_FMT_ASTC_LDR_10x5, {GL_COMPRESSED_RGBA_ASTC_10x5_KHR, GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x5_KHR}},
+ {KD_TEX_FMT_ASTC_LDR_10x6, {GL_COMPRESSED_RGBA_ASTC_10x6_KHR, GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x6_KHR}},
+ {KD_TEX_FMT_ASTC_LDR_10x8, {GL_COMPRESSED_RGBA_ASTC_10x8_KHR, GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x8_KHR}},
+ {KD_TEX_FMT_ASTC_LDR_10x10, {GL_COMPRESSED_RGBA_ASTC_10x10_KHR, GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x10_KHR}},
+ {KD_TEX_FMT_ASTC_LDR_12x10, {GL_COMPRESSED_RGBA_ASTC_12x10_KHR, GL_COMPRESSED_SRGB8_ALPHA8_ASTC_12x10_KHR}},
+ {KD_TEX_FMT_ASTC_LDR_12x12, {GL_COMPRESSED_RGBA_ASTC_12x12_KHR, GL_COMPRESSED_SRGB8_ALPHA8_ASTC_12x12_KHR}},
+
+ {KD_TEX_FMT_ASTC_HDR_4x4, {GL_COMPRESSED_RGBA_ASTC_4x4_KHR}},
+ {KD_TEX_FMT_ASTC_HDR_5x4, {GL_COMPRESSED_RGBA_ASTC_5x4_KHR}},
+ {KD_TEX_FMT_ASTC_HDR_5x5, {GL_COMPRESSED_RGBA_ASTC_5x5_KHR}},
+ {KD_TEX_FMT_ASTC_HDR_6x5, {GL_COMPRESSED_RGBA_ASTC_6x5_KHR}},
+ {KD_TEX_FMT_ASTC_HDR_6x6, {GL_COMPRESSED_RGBA_ASTC_6x6_KHR}},
+ {KD_TEX_FMT_ASTC_HDR_8x5, {GL_COMPRESSED_RGBA_ASTC_8x5_KHR}},
+ {KD_TEX_FMT_ASTC_HDR_8x6, {GL_COMPRESSED_RGBA_ASTC_8x6_KHR}},
+ {KD_TEX_FMT_ASTC_HDR_8x8, {GL_COMPRESSED_RGBA_ASTC_8x8_KHR}},
+ {KD_TEX_FMT_ASTC_HDR_10x5, {GL_COMPRESSED_RGBA_ASTC_10x5_KHR}},
+ {KD_TEX_FMT_ASTC_HDR_10x6, {GL_COMPRESSED_RGBA_ASTC_10x6_KHR}},
+ {KD_TEX_FMT_ASTC_HDR_10x8, {GL_COMPRESSED_RGBA_ASTC_10x8_KHR}},
+ {KD_TEX_FMT_ASTC_HDR_10x10, {GL_COMPRESSED_RGBA_ASTC_10x10_KHR}},
+ {KD_TEX_FMT_ASTC_HDR_12x10, {GL_COMPRESSED_RGBA_ASTC_12x10_KHR}},
+ {KD_TEX_FMT_ASTC_HDR_12x12, {GL_COMPRESSED_RGBA_ASTC_12x12_KHR}},
+#endif
+};
+
+static const std::map<KD_TEX_SWIZ, Textureswizzle> SwizzleMap
+{
+ {KD_TEX_SWIZ_RGBA, {GL_RED, GL_GREEN, GL_BLUE, GL_ALPHA}},
+ {KD_TEX_SWIZ_RGB1, {GL_RED, GL_GREEN, GL_BLUE, GL_ONE}},
+ {KD_TEX_SWIZ_RRR1, {GL_RED, GL_RED, GL_RED, GL_ONE}},
+ {KD_TEX_SWIZ_111R, {GL_ONE, GL_ONE, GL_ONE, GL_RED}},
+ {KD_TEX_SWIZ_RRRG, {GL_RED, GL_RED, GL_RED, GL_GREEN}},
+ {KD_TEX_SWIZ_RRRR, {GL_RED, GL_RED, GL_RED, GL_RED}},
+ {KD_TEX_SWIZ_GGG1, {GL_GREEN, GL_GREEN, GL_GREEN, GL_ONE}},
+ {KD_TEX_SWIZ_111G, {GL_ONE, GL_ONE, GL_ONE, GL_GREEN}},
+ {KD_TEX_SWIZ_GGGA, {GL_GREEN, GL_GREEN, GL_GREEN, GL_ALPHA}},
+ {KD_TEX_SWIZ_GGGG, {GL_GREEN, GL_GREEN, GL_GREEN, GL_GREEN}},
+};
+// clang-format on
+} // namespace
+
std::unique_ptr<CTexture> CTexture::CreateTexture(unsigned int width,
unsigned int height,
XB_FMT format)
@@ -33,6 +146,8 @@ CGLTexture::CGLTexture(unsigned int width, unsigned int height, XB_FMT format)
CServiceBroker::GetRenderSystem()->GetRenderVersion(major, minor);
if (major >= 3)
m_isOglVersion3orNewer = true;
+ if (major > 3 || (major == 3 && minor >= 3))
+ m_isOglVersion33orNewer = true;
}
CGLTexture::~CGLTexture()
@@ -119,49 +234,37 @@ void CGLTexture::LoadToGPU()
m_textureWidth = maxSize;
}
- GLenum format = GL_BGRA;
- GLint numcomponents = GL_RGBA;
+ // there might not be any padding for the following formats, so we have to
+ // read one/two bytes at the time.
+ if (m_textureFormat == KD_TEX_FMT_SDR_R8)
+ glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
+ if (m_textureFormat == KD_TEX_FMT_SDR_RG8)
+ glPixelStorei(GL_UNPACK_ALIGNMENT, 2);
+
+ SetSwizzle();
- switch (m_format)
+ TextureFormat glFormat = GetFormatGL(m_textureFormat);
+
+ if (glFormat.format == GL_FALSE)
{
- case XB_FMT_DXT1:
- format = GL_COMPRESSED_RGBA_S3TC_DXT1_EXT;
- break;
- case XB_FMT_DXT3:
- format = GL_COMPRESSED_RGBA_S3TC_DXT3_EXT;
- break;
- case XB_FMT_DXT5:
- case XB_FMT_DXT5_YCoCg:
- format = GL_COMPRESSED_RGBA_S3TC_DXT5_EXT;
- break;
- case XB_FMT_RGB8:
- format = GL_RGB;
- numcomponents = GL_RGB;
- break;
- case XB_FMT_A8R8G8B8:
- default:
- break;
+ CLog::LogF(LOGDEBUG, "Failed to load texture. Unsupported format {}", m_textureFormat);
+ m_loadedToGPU = true;
+ return;
}
- if ((m_format & XB_FMT_DXT_MASK) == 0)
+ if ((m_textureFormat & KD_TEX_FMT_SDR) || (m_textureFormat & KD_TEX_FMT_HDR))
{
- glTexImage2D(GL_TEXTURE_2D, 0, numcomponents,
- m_textureWidth, m_textureHeight, 0,
- format, GL_UNSIGNED_BYTE, m_pixels);
+ glTexImage2D(GL_TEXTURE_2D, 0, glFormat.internalFormat, m_textureWidth, m_textureHeight, 0,
+ glFormat.format, glFormat.type, m_pixels);
}
else
{
- glCompressedTexImage2D(GL_TEXTURE_2D, 0, format,
- m_textureWidth, m_textureHeight, 0,
- GetPitch() * GetRows(), m_pixels);
+ glCompressedTexImage2D(GL_TEXTURE_2D, 0, glFormat.internalFormat, m_textureWidth,
+ m_textureHeight, 0, GetPitch() * GetRows(), m_pixels);
}
if (IsMipmapped() && m_isOglVersion3orNewer)
- {
glGenerateMipmap(GL_TEXTURE_2D);
- }
-
- glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
VerifyGLState();
@@ -185,3 +288,41 @@ void CGLTexture::BindToUnit(unsigned int unit)
glBindTexture(GL_TEXTURE_2D, m_texture);
}
+void CGLTexture::SetSwizzle()
+{
+ if (!SwizzleMap.contains(m_textureSwizzle))
+ return;
+
+ Textureswizzle swiz = SwizzleMap.at(m_textureSwizzle);
+
+ // GL_TEXTURE_SWIZZLE_RGBA and GL_TEXTURE_SWIZZLE_RGBA_EXT should be the same
+ // token, but just to be sure...
+#if defined(GL_VERSION_3_3) || (GL_ARB_texture_swizzle)
+ if (m_isOglVersion33orNewer ||
+ CServiceBroker::GetRenderSystem()->IsExtSupported("GL_ARB_texture_swizzle"))
+ {
+ glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_RGBA, &swiz.r);
+ return;
+ }
+#endif
+#if defined(GL_EXT_texture_swizzle)
+ if (CServiceBroker::GetRenderSystem()->IsExtSupported("GL_EXT_texture_swizzle"))
+ {
+ glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_RGBA_EXT, &swiz.r);
+ return;
+ }
+#endif
+}
+
+TextureFormat CGLTexture::GetFormatGL(KD_TEX_FMT textureFormat)
+{
+ TextureFormat glFormat;
+
+ const auto it = TextureMapping.find(textureFormat);
+ if (it != TextureMapping.cend())
+ {
+ glFormat = it->second;
+ }
+
+ return glFormat;
+}
diff --git a/xbmc/guilib/TextureGL.h b/xbmc/guilib/TextureGL.h
index 0a979065e1..cc3e41b179 100644
--- a/xbmc/guilib/TextureGL.h
+++ b/xbmc/guilib/TextureGL.h
@@ -12,6 +12,22 @@
#include "system_gl.h"
+struct TextureFormat
+{
+ GLenum internalFormat{GL_FALSE};
+ GLenum internalFormatSRGB{GL_FALSE};
+ GLint format{GL_FALSE};
+ GLenum type{GL_UNSIGNED_BYTE};
+};
+
+struct Textureswizzle
+{
+ GLint r{GL_RED};
+ GLint g{GL_GREEN};
+ GLint b{GL_BLUE};
+ GLint a{GL_ALPHA};
+};
+
/************************************************************************/
/* CGLTexture */
/************************************************************************/
@@ -27,8 +43,17 @@ public:
void SyncGPU() override;
void BindToUnit(unsigned int unit) override;
+ bool SupportsFormat(KD_TEX_FMT textureFormat, KD_TEX_SWIZ textureSwizzle) override
+ {
+ return true;
+ }
+
protected:
- GLuint m_texture = 0;
- bool m_isOglVersion3orNewer = false;
+ void SetSwizzle();
+ TextureFormat GetFormatGL(KD_TEX_FMT textureFormat);
+
+ GLuint m_texture{0};
+ bool m_isOglVersion3orNewer{false};
+ bool m_isOglVersion33orNewer{false};
};
diff --git a/xbmc/guilib/TextureGLES.cpp b/xbmc/guilib/TextureGLES.cpp
index 828d005edb..e475fecf47 100644
--- a/xbmc/guilib/TextureGLES.cpp
+++ b/xbmc/guilib/TextureGLES.cpp
@@ -9,6 +9,7 @@
#include "TextureGLES.h"
#include "ServiceBroker.h"
+#include "guilib/TextureFormats.h"
#include "guilib/TextureManager.h"
#include "rendering/RenderSystem.h"
#include "settings/AdvancedSettings.h"
@@ -19,6 +20,156 @@
#include <memory>
+namespace
+{
+// clang-format off
+// GLES 2.0 texture formats.
+// Any extension used here is in the core 3.0 profile (except BGRA)
+// format = (unsized) internalFormat (with core 2.0)
+static const std::map<KD_TEX_FMT, TextureFormat> TextureMappingGLES20
+{
+#if defined(GL_EXT_texture_rg)
+ {KD_TEX_FMT_SDR_R8, {GL_RED_EXT}},
+ {KD_TEX_FMT_SDR_RG8, {GL_RG_EXT}},
+#endif
+ {KD_TEX_FMT_SDR_R5G6B5, {GL_RGB, GL_FALSE, GL_FALSE, GL_UNSIGNED_SHORT_5_6_5}},
+ {KD_TEX_FMT_SDR_RGB5_A1, {GL_RGBA, GL_FALSE, GL_FALSE, GL_UNSIGNED_SHORT_5_5_5_1}},
+ {KD_TEX_FMT_SDR_RGBA4, {GL_RGBA, GL_FALSE, GL_FALSE, GL_UNSIGNED_SHORT_4_4_4_4}},
+#if defined(GL_EXT_sRGB)
+ {KD_TEX_FMT_SDR_RGB8, {GL_RGB, GL_SRGB_EXT}},
+ {KD_TEX_FMT_SDR_RGBA8, {GL_RGBA, GL_SRGB_ALPHA_EXT}},
+#else
+ {KD_TEX_FMT_SDR_RGB8, {GL_RGB}},
+ {KD_TEX_FMT_SDR_RGBA8, {GL_RGBA}},
+#endif
+
+#if defined(GL_EXT_texture_format_BGRA8888) || (GL_IMG_texture_format_BGRA8888)
+ {KD_TEX_FMT_SDR_BGRA8, {GL_BGRA_EXT}},
+#endif
+
+#if defined(GL_EXT_texture_type_2_10_10_10_REV)
+ {KD_TEX_FMT_HDR_RGB10_A2, {GL_RGBA, GL_FALSE, GL_FALSE, GL_UNSIGNED_INT_2_10_10_10_REV_EXT}},
+#endif
+#if defined(GL_OES_texture_half_float_linear)
+ {KD_TEX_FMT_HDR_RGBA16f, {GL_RGBA, GL_FALSE, GL_FALSE, GL_HALF_FLOAT_OES}},
+#endif
+
+#if defined(GL_OES_compressed_ETC1_RGB8_texture)
+ {KD_TEX_FMT_ETC1_RGB8, {GL_ETC1_RGB8_OES}},
+#endif
+};
+
+// GLES 3.0 texture formats.
+#if defined(GL_ES_VERSION_3_0)
+std::map<KD_TEX_FMT, TextureFormat> TextureMappingGLES30
+{
+#if defined(GL_EXT_texture_sRGB_R8) && (GL_EXT_texture_sRGB_RG8) // in gl2ext.h, but spec says >= 3.0
+ {KD_TEX_FMT_SDR_R8, {GL_R8, GL_SR8_EXT, GL_RED}},
+ {KD_TEX_FMT_SDR_RG8, {GL_RG8, GL_SRG8_EXT, GL_RG}},
+#else
+ {KD_TEX_FMT_SDR_R8, {GL_R8, GL_FALSE, GL_RED}},
+ {KD_TEX_FMT_SDR_RG8, {GL_RG8, GL_FALSE, GL_RG}},
+#endif
+ {KD_TEX_FMT_SDR_R5G6B5, {GL_RGB565, GL_FALSE, GL_RGB, GL_UNSIGNED_SHORT_5_6_5}},
+ {KD_TEX_FMT_SDR_RGB5_A1, {GL_RGB5_A1, GL_FALSE, GL_RGBA, GL_UNSIGNED_SHORT_5_5_5_1}},
+ {KD_TEX_FMT_SDR_RGBA4, {GL_RGBA4, GL_FALSE, GL_RGBA, GL_UNSIGNED_SHORT_4_4_4_4}},
+ {KD_TEX_FMT_SDR_RGB8, {GL_RGB8, GL_SRGB8, GL_RGB}},
+ {KD_TEX_FMT_SDR_RGBA8, {GL_RGBA8, GL_SRGB8_ALPHA8, GL_RGBA}},
+
+ {KD_TEX_FMT_HDR_R16f, {GL_R16F, GL_FALSE, GL_RED, GL_HALF_FLOAT}},
+ {KD_TEX_FMT_HDR_RG16f, {GL_RG16F, GL_FALSE, GL_RG, GL_HALF_FLOAT}},
+ {KD_TEX_FMT_HDR_R11F_G11F_B10F, {GL_R11F_G11F_B10F, GL_FALSE, GL_RGB, GL_UNSIGNED_INT_10F_11F_11F_REV}},
+ {KD_TEX_FMT_HDR_RGB9_E5, {GL_RGB9_E5, GL_FALSE, GL_RGB, GL_UNSIGNED_INT_5_9_9_9_REV}},
+ {KD_TEX_FMT_HDR_RGB10_A2, {GL_RGB10_A2, GL_FALSE, GL_RGBA, GL_UNSIGNED_INT_2_10_10_10_REV}},
+ {KD_TEX_FMT_HDR_RGBA16f, {GL_RGBA16F, GL_FALSE, GL_RGBA, GL_HALF_FLOAT}},
+
+ {KD_TEX_FMT_ETC1_RGB8, {GL_COMPRESSED_RGB8_ETC2, GL_COMPRESSED_SRGB8_ETC2}},
+
+ {KD_TEX_FMT_ETC2_R11, {GL_COMPRESSED_R11_EAC}},
+ {KD_TEX_FMT_ETC2_RG11, {GL_COMPRESSED_RG11_EAC}},
+ {KD_TEX_FMT_ETC2_RGB8, {GL_COMPRESSED_RGB8_ETC2, GL_COMPRESSED_SRGB8_ETC2}},
+ {KD_TEX_FMT_ETC2_RGB8_A1, {GL_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2, GL_COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2}},
+ {KD_TEX_FMT_ETC2_RGBA8, {GL_COMPRESSED_RGBA8_ETC2_EAC, GL_COMPRESSED_SRGB8_ALPHA8_ETC2_EAC}},
+};
+#endif // GL_ES_VERSION_3_0
+
+// Common GLES extensions (texture compression)
+static const std::map<KD_TEX_FMT, TextureFormat> TextureMappingGLESExtensions
+{
+#if defined(GL_EXT_texture_compression_s3tc) && (GL_EXT_texture_compression_s3tc_srgb)
+ {KD_TEX_FMT_S3TC_RGB8, {GL_COMPRESSED_RGB_S3TC_DXT1_EXT, GL_COMPRESSED_SRGB_S3TC_DXT1_EXT}},
+ {KD_TEX_FMT_S3TC_RGB8_A1, {GL_COMPRESSED_RGBA_S3TC_DXT1_EXT, GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT}},
+ {KD_TEX_FMT_S3TC_RGB8_A4, {GL_COMPRESSED_RGBA_S3TC_DXT3_EXT, GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT}},
+ {KD_TEX_FMT_S3TC_RGBA8, {GL_COMPRESSED_RGBA_S3TC_DXT5_EXT, GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT}},
+#elif defined(GL_EXT_texture_compression_s3tc)
+ {KD_TEX_FMT_S3TC_RGB8, {GL_COMPRESSED_RGB_S3TC_DXT1_EXT}},
+ {KD_TEX_FMT_S3TC_RGB8_A1, {GL_COMPRESSED_RGBA_S3TC_DXT1_EXT}},
+ {KD_TEX_FMT_S3TC_RGB8_A4, {GL_COMPRESSED_RGBA_S3TC_DXT3_EXT}},
+ {KD_TEX_FMT_S3TC_RGBA8, {GL_COMPRESSED_RGBA_S3TC_DXT5_EXT}},
+#elif defined(GL_EXT_texture_compression_dxt1)
+ {KD_TEX_FMT_S3TC_RGB8, {GL_COMPRESSED_RGB_S3TC_DXT1_EXT}},
+ {KD_TEX_FMT_S3TC_RGB8_A1, {GL_COMPRESSED_RGBA_S3TC_DXT1_EXT}},
+#endif
+
+#if defined(GL_EXT_texture_compression_rgtc)
+ {KD_TEX_FMT_RGTC_R11, {GL_COMPRESSED_RED_RGTC1_EXT}},
+ {KD_TEX_FMT_RGTC_RG11, {GL_COMPRESSED_RED_GREEN_RGTC2_EXT}},
+#endif
+
+#if defined(GL_EXT_texture_compression_bptc)
+ {KD_TEX_FMT_BPTC_RGB16F, {GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT_EXT}},
+ {KD_TEX_FMT_BPTC_RGBA8, {GL_COMPRESSED_RGBA_BPTC_UNORM_EXT, GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM_EXT}},
+#endif
+
+#if defined(GL_KHR_texture_compression_astc_ldr) || (GL_KHR_texture_compression_astc_hdr)
+ {KD_TEX_FMT_ASTC_LDR_4x4, {GL_COMPRESSED_RGBA_ASTC_4x4_KHR, GL_COMPRESSED_SRGB8_ALPHA8_ASTC_4x4_KHR}},
+ {KD_TEX_FMT_ASTC_LDR_5x4, {GL_COMPRESSED_RGBA_ASTC_5x4_KHR, GL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x4_KHR}},
+ {KD_TEX_FMT_ASTC_LDR_5x5, {GL_COMPRESSED_RGBA_ASTC_5x5_KHR, GL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x5_KHR}},
+ {KD_TEX_FMT_ASTC_LDR_6x5, {GL_COMPRESSED_RGBA_ASTC_6x5_KHR, GL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x5_KHR}},
+ {KD_TEX_FMT_ASTC_LDR_6x6, {GL_COMPRESSED_RGBA_ASTC_6x6_KHR, GL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x6_KHR}},
+ {KD_TEX_FMT_ASTC_LDR_8x5, {GL_COMPRESSED_RGBA_ASTC_8x5_KHR, GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x5_KHR}},
+ {KD_TEX_FMT_ASTC_LDR_8x6, {GL_COMPRESSED_RGBA_ASTC_8x6_KHR, GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x6_KHR}},
+ {KD_TEX_FMT_ASTC_LDR_8x8, {GL_COMPRESSED_RGBA_ASTC_8x8_KHR, GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x8_KHR}},
+ {KD_TEX_FMT_ASTC_LDR_10x5, {GL_COMPRESSED_RGBA_ASTC_10x5_KHR, GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x5_KHR}},
+ {KD_TEX_FMT_ASTC_LDR_10x6, {GL_COMPRESSED_RGBA_ASTC_10x6_KHR, GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x6_KHR}},
+ {KD_TEX_FMT_ASTC_LDR_10x8, {GL_COMPRESSED_RGBA_ASTC_10x8_KHR, GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x8_KHR}},
+ {KD_TEX_FMT_ASTC_LDR_10x10, {GL_COMPRESSED_RGBA_ASTC_10x10_KHR, GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x10_KHR}},
+ {KD_TEX_FMT_ASTC_LDR_12x10, {GL_COMPRESSED_RGBA_ASTC_12x10_KHR, GL_COMPRESSED_SRGB8_ALPHA8_ASTC_12x10_KHR}},
+ {KD_TEX_FMT_ASTC_LDR_12x12, {GL_COMPRESSED_RGBA_ASTC_12x12_KHR, GL_COMPRESSED_SRGB8_ALPHA8_ASTC_12x12_KHR}},
+
+ {KD_TEX_FMT_ASTC_HDR_4x4, {GL_COMPRESSED_RGBA_ASTC_4x4_KHR}},
+ {KD_TEX_FMT_ASTC_HDR_5x4, {GL_COMPRESSED_RGBA_ASTC_5x4_KHR}},
+ {KD_TEX_FMT_ASTC_HDR_5x5, {GL_COMPRESSED_RGBA_ASTC_5x5_KHR}},
+ {KD_TEX_FMT_ASTC_HDR_6x5, {GL_COMPRESSED_RGBA_ASTC_6x5_KHR}},
+ {KD_TEX_FMT_ASTC_HDR_6x6, {GL_COMPRESSED_RGBA_ASTC_6x6_KHR}},
+ {KD_TEX_FMT_ASTC_HDR_8x5, {GL_COMPRESSED_RGBA_ASTC_8x5_KHR}},
+ {KD_TEX_FMT_ASTC_HDR_8x6, {GL_COMPRESSED_RGBA_ASTC_8x6_KHR}},
+ {KD_TEX_FMT_ASTC_HDR_8x8, {GL_COMPRESSED_RGBA_ASTC_8x8_KHR}},
+ {KD_TEX_FMT_ASTC_HDR_10x5, {GL_COMPRESSED_RGBA_ASTC_10x5_KHR}},
+ {KD_TEX_FMT_ASTC_HDR_10x6, {GL_COMPRESSED_RGBA_ASTC_10x6_KHR}},
+ {KD_TEX_FMT_ASTC_HDR_10x8, {GL_COMPRESSED_RGBA_ASTC_10x8_KHR}},
+ {KD_TEX_FMT_ASTC_HDR_10x10, {GL_COMPRESSED_RGBA_ASTC_10x10_KHR}},
+ {KD_TEX_FMT_ASTC_HDR_12x10, {GL_COMPRESSED_RGBA_ASTC_12x10_KHR}},
+ {KD_TEX_FMT_ASTC_HDR_12x12, {GL_COMPRESSED_RGBA_ASTC_12x12_KHR}},
+#endif
+};
+
+static const std::map<KD_TEX_SWIZ, TextureSwizzle> SwizzleMapGLES
+{
+ {KD_TEX_SWIZ_RGBA, {GL_RED, GL_GREEN, GL_BLUE, GL_ALPHA}},
+ {KD_TEX_SWIZ_RGB1, {GL_RED, GL_GREEN, GL_BLUE, GL_ONE}},
+ {KD_TEX_SWIZ_RRR1, {GL_RED, GL_RED, GL_RED, GL_ONE}},
+ {KD_TEX_SWIZ_111R, {GL_ONE, GL_ONE, GL_ONE, GL_RED}},
+ {KD_TEX_SWIZ_RRRG, {GL_RED, GL_RED, GL_RED, GL_GREEN}},
+ {KD_TEX_SWIZ_RRRR, {GL_RED, GL_RED, GL_RED, GL_RED}},
+ {KD_TEX_SWIZ_GGG1, {GL_GREEN, GL_GREEN, GL_GREEN, GL_ONE}},
+ {KD_TEX_SWIZ_111G, {GL_ONE, GL_ONE, GL_ONE, GL_GREEN}},
+ {KD_TEX_SWIZ_GGGA, {GL_GREEN, GL_GREEN, GL_GREEN, GL_ALPHA}},
+ {KD_TEX_SWIZ_GGGG, {GL_GREEN, GL_GREEN, GL_GREEN, GL_GREEN}},
+};
+// clang-format on
+} // namespace
+
std::unique_ptr<CTexture> CTexture::CreateTexture(unsigned int width,
unsigned int height,
XB_FMT format)
@@ -31,7 +182,9 @@ CGLESTexture::CGLESTexture(unsigned int width, unsigned int height, XB_FMT forma
{
unsigned int major, minor;
CServiceBroker::GetRenderSystem()->GetRenderVersion(major, minor);
+#if defined(GL_ES_VERSION_3_0)
m_isGLESVersion30orNewer = major >= 3;
+#endif
}
CGLESTexture::~CGLESTexture()
@@ -125,52 +278,49 @@ void CGLESTexture::LoadToGPU()
}
}
- // All incoming textures are BGRA, which GLES does not necessarily support.
- // Some (most?) hardware supports BGRA textures via an extension.
- // If not, we convert to RGBA first to avoid having to swizzle in shaders.
- // Explicitly define GL_BGRA_EXT here in the case that it's not defined by
- // system headers, and trust the extension list instead.
-#ifndef GL_BGRA_EXT
-#define GL_BGRA_EXT 0x80E1
-#endif
-
- GLint internalformat;
- GLenum pixelformat;
-
- switch (m_format)
- {
- default:
- case XB_FMT_RGBA8:
- internalformat = pixelformat = GL_RGBA;
- break;
- case XB_FMT_RGB8:
- internalformat = pixelformat = GL_RGB;
- break;
- case XB_FMT_A8R8G8B8:
- if (CServiceBroker::GetRenderSystem()->IsExtSupported("GL_EXT_texture_format_BGRA8888") ||
- CServiceBroker::GetRenderSystem()->IsExtSupported("GL_IMG_texture_format_BGRA8888"))
- {
- internalformat = pixelformat = GL_BGRA_EXT;
- }
- else if (CServiceBroker::GetRenderSystem()->IsExtSupported(
- "GL_APPLE_texture_format_BGRA8888"))
- {
- // Apple's implementation does not conform to spec. Instead, they require
- // differing format/internalformat, more like GL.
- internalformat = GL_RGBA;
- pixelformat = GL_BGRA_EXT;
- }
- else
- {
- SwapBlueRed(m_pixels, m_textureHeight, GetPitch());
- internalformat = pixelformat = GL_RGBA;
- }
- break;
- }
-
- glTexImage2D(GL_TEXTURE_2D, 0, internalformat, m_textureWidth, m_textureHeight, 0, pixelformat,
- GL_UNSIGNED_BYTE, m_pixels);
+ // there might not be any padding for the following formats, so we have to
+ // read one/two bytes at the time.
+ if (m_textureFormat == KD_TEX_FMT_SDR_R8)
+ glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
+ if (m_textureFormat == KD_TEX_FMT_SDR_RG8)
+ glPixelStorei(GL_UNPACK_ALIGNMENT, 2);
+ TextureFormat glesFormat;
+ if (m_isGLESVersion30orNewer)
+ {
+ KD_TEX_FMT textureFormat = m_textureFormat;
+ bool swapRB = false;
+ // Support for BGRA is hit and miss, swizzle instead
+ if (textureFormat == KD_TEX_FMT_SDR_BGRA8)
+ {
+ textureFormat = KD_TEX_FMT_SDR_RGBA8;
+ swapRB = true;
+ }
+ SetSwizzle(swapRB);
+ glesFormat = GetFormatGLES30(textureFormat);
+ }
+ else
+ {
+ glesFormat = GetFormatGLES20(m_textureFormat);
+ }
+
+ if (glesFormat.internalFormat == GL_FALSE)
+ {
+ CLog::LogF(LOGDEBUG, "Failed to load texture. Unsupported format {}", m_textureFormat);
+ m_loadedToGPU = true;
+ return;
+ }
+
+ if ((m_textureFormat & KD_TEX_FMT_SDR) || (m_textureFormat & KD_TEX_FMT_HDR))
+ {
+ glTexImage2D(GL_TEXTURE_2D, 0, glesFormat.internalFormat, m_textureWidth, m_textureHeight, 0,
+ glesFormat.format, glesFormat.type, m_pixels);
+ }
+ else
+ {
+ glCompressedTexImage2D(GL_TEXTURE_2D, 0, glesFormat.internalFormat, m_textureWidth,
+ m_textureHeight, 0, GetPitch() * GetRows(), m_pixels);
+ }
if (IsMipmapped())
{
glGenerateMipmap(GL_TEXTURE_2D);
@@ -196,3 +346,131 @@ void CGLESTexture::BindToUnit(unsigned int unit)
glActiveTexture(GL_TEXTURE0 + unit);
glBindTexture(GL_TEXTURE_2D, m_texture);
}
+
+bool CGLESTexture::SupportsFormat(KD_TEX_FMT textureFormat, KD_TEX_SWIZ textureSwizzle)
+{
+ // GLES 3.0 supports swizzles
+ if (m_isGLESVersion30orNewer)
+ return true;
+
+ // GL_LUMINANCE;
+ if (textureFormat == KD_TEX_FMT_SDR_R8 && textureSwizzle == KD_TEX_SWIZ_RRR1)
+ return true;
+
+ // GL_LUMINANCE_ALPHA;
+ if (textureFormat == KD_TEX_FMT_SDR_RG8 && textureSwizzle == KD_TEX_SWIZ_RRRG)
+ return true;
+
+ // all other GLES 2.0 swizzles would need separate shaders
+ return textureSwizzle == KD_TEX_SWIZ_RGBA;
+}
+
+void CGLESTexture::SetSwizzle(bool swapRB)
+{
+#if defined(GL_ES_VERSION_3_0)
+ TextureSwizzle swiz;
+
+ const auto it = SwizzleMapGLES.find(m_textureSwizzle);
+ if (it != SwizzleMapGLES.cend())
+ swiz = it->second;
+
+ if (swapRB)
+ {
+ SwapBlueRedSwizzle(swiz.r);
+ SwapBlueRedSwizzle(swiz.g);
+ SwapBlueRedSwizzle(swiz.b);
+ SwapBlueRedSwizzle(swiz.a);
+ }
+
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_R, swiz.r);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_G, swiz.g);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_B, swiz.b);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_A, swiz.a);
+#endif
+}
+
+void CGLESTexture::SwapBlueRedSwizzle(GLint& component)
+{
+ if (component == GL_RED)
+ component = GL_BLUE;
+ else if (component == GL_BLUE)
+ component = GL_RED;
+}
+
+TextureFormat CGLESTexture::GetFormatGLES20(KD_TEX_FMT textureFormat)
+{
+ TextureFormat glFormat;
+
+ // GLES 2.0 does not support swizzling. But for some Kodi formats+swizzles,
+ // we can map GLES formats (Luminance, Luminance-Alpha, BGRA). Other swizzles
+ // would have to be supported in the shader, or converted before upload.
+
+ if (m_textureFormat == KD_TEX_FMT_SDR_R8 && m_textureSwizzle == KD_TEX_SWIZ_RRR1)
+ {
+ glFormat.format = glFormat.internalFormat = GL_LUMINANCE;
+ }
+ else if (m_textureFormat == KD_TEX_FMT_SDR_RG8 && m_textureSwizzle == KD_TEX_SWIZ_RRRG)
+ {
+ glFormat.format = glFormat.internalFormat = GL_LUMINANCE_ALPHA;
+ }
+ else if (m_textureFormat == KD_TEX_FMT_SDR_BGRA8 && m_textureSwizzle == KD_TEX_SWIZ_RGBA &&
+ !CServiceBroker::GetRenderSystem()->IsExtSupported("GL_EXT_texture_format_BGRA8888") &&
+ !CServiceBroker::GetRenderSystem()->IsExtSupported("GL_IMG_texture_format_BGRA8888"))
+ {
+#if defined(GL_APPLE_texture_format_BGRA8888)
+ if (CServiceBroker::GetRenderSystem()->IsExtSupported("GL_APPLE_texture_format_BGRA8888"))
+ {
+ glFormat.internalFormat = GL_RGBA;
+ glFormat.format = GL_BGRA_EXT;
+ }
+ else
+#endif
+ {
+ SwapBlueRed(m_pixels, m_textureHeight, GetPitch());
+ glFormat.format = glFormat.internalFormat = GL_RGBA;
+ }
+ }
+ else if (textureFormat & KD_TEX_FMT_SDR || textureFormat & KD_TEX_FMT_HDR ||
+ textureFormat & KD_TEX_FMT_ETC1)
+ {
+ const auto it = TextureMappingGLES20.find(textureFormat);
+ if (it != TextureMappingGLES20.cend())
+ glFormat = it->second;
+ glFormat.format = glFormat.internalFormat;
+ }
+ else
+ {
+ const auto it = TextureMappingGLESExtensions.find(textureFormat);
+ if (it != TextureMappingGLESExtensions.cend())
+ glFormat = it->second;
+ }
+
+ return glFormat;
+}
+
+TextureFormat CGLESTexture::GetFormatGLES30(KD_TEX_FMT textureFormat)
+{
+ TextureFormat glFormat;
+
+ if (textureFormat & KD_TEX_FMT_SDR || textureFormat & KD_TEX_FMT_HDR)
+ {
+#if defined(GL_ES_VERSION_3_0)
+ const auto it = TextureMappingGLES30.find(textureFormat);
+ if (it != TextureMappingGLES30.cend())
+ glFormat = it->second;
+#else
+ const auto it = TextureMappingGLES20.find(textureFormat);
+ if (it != TextureMappingGLES20.cend())
+ glFormat = it->second;
+ glFormat.format = glFormat.internalFormat;
+#endif
+ }
+ else
+ {
+ const auto it = TextureMappingGLESExtensions.find(textureFormat);
+ if (it != TextureMappingGLESExtensions.cend())
+ glFormat = it->second;
+ }
+
+ return glFormat;
+}
diff --git a/xbmc/guilib/TextureGLES.h b/xbmc/guilib/TextureGLES.h
index 4a9cb0502f..fe7af24917 100644
--- a/xbmc/guilib/TextureGLES.h
+++ b/xbmc/guilib/TextureGLES.h
@@ -12,6 +12,22 @@
#include "system_gl.h"
+struct TextureFormat
+{
+ GLenum internalFormat{GL_FALSE};
+ GLenum internalFormatSRGB{GL_FALSE};
+ GLint format{GL_FALSE};
+ GLenum type{GL_UNSIGNED_BYTE};
+};
+
+struct TextureSwizzle
+{
+ GLint r{GL_RED};
+ GLint g{GL_GREEN};
+ GLint b{GL_BLUE};
+ GLint a{GL_ALPHA};
+};
+
class CGLESTexture : public CTexture
{
public:
@@ -22,8 +38,14 @@ public:
void DestroyTextureObject() override;
void LoadToGPU() override;
void BindToUnit(unsigned int unit) override;
+ bool SupportsFormat(KD_TEX_FMT textureFormat, KD_TEX_SWIZ textureSwizzle) override;
protected:
+ void SetSwizzle(bool swapRB);
+ void SwapBlueRedSwizzle(GLint& component);
+ TextureFormat GetFormatGLES20(KD_TEX_FMT textureFormat);
+ TextureFormat GetFormatGLES30(KD_TEX_FMT textureFormat);
+
GLuint m_texture = 0;
bool m_isGLESVersion30orNewer{false};
};
diff --git a/xbmc/guilib/WindowIDs.h b/xbmc/guilib/WindowIDs.h
index 8c4884205a..40e4724543 100644
--- a/xbmc/guilib/WindowIDs.h
+++ b/xbmc/guilib/WindowIDs.h
@@ -134,7 +134,9 @@
#define WINDOW_RADIO_SEARCH (WINDOW_PVR_ID_START+9)
#define WINDOW_TV_TIMER_RULES (WINDOW_PVR_ID_START+10)
#define WINDOW_RADIO_TIMER_RULES (WINDOW_PVR_ID_START+11)
-#define WINDOW_PVR_ID_END WINDOW_RADIO_TIMER_RULES
+#define WINDOW_TV_PROVIDERS (WINDOW_PVR_ID_START + 12)
+#define WINDOW_RADIO_PROVIDERS (WINDOW_PVR_ID_START + 13)
+#define WINDOW_PVR_ID_END WINDOW_RADIO_PROVIDERS
// virtual windows for PVR specific keymap bindings in fullscreen playback
#define WINDOW_FULLSCREEN_LIVETV 10800
diff --git a/xbmc/guilib/XBTF.cpp b/xbmc/guilib/XBTF.cpp
index fb89c97889..92848bd69e 100644
--- a/xbmc/guilib/XBTF.cpp
+++ b/xbmc/guilib/XBTF.cpp
@@ -72,14 +72,14 @@ void CXBTFFrame::SetUnpackedSize(uint64_t size)
m_unpackedSize = size;
}
-void CXBTFFrame::SetFormat(XB_FMT format)
+void CXBTFFrame::SetFormat(uint32_t format)
{
m_format = format;
}
XB_FMT CXBTFFrame::GetFormat(bool raw) const
{
- return raw ? m_format : static_cast<XB_FMT>(m_format & XB_FMT_MASK);
+ return static_cast<XB_FMT>(raw ? m_format : m_format & XB_FMT_MASK);
}
uint64_t CXBTFFrame::GetOffset() const
@@ -116,6 +116,26 @@ uint64_t CXBTFFrame::GetHeaderSize() const
return result;
}
+KD_TEX_FMT CXBTFFrame::GetKDFormat() const
+{
+ return static_cast<KD_TEX_FMT>(m_format & KD_TEX_FMT_MASK);
+}
+
+KD_TEX_FMT CXBTFFrame::GetKDFormatType() const
+{
+ return static_cast<KD_TEX_FMT>(m_format & KD_TEX_FMT_TYPE_MASK);
+}
+
+KD_TEX_ALPHA CXBTFFrame::GetKDAlpha() const
+{
+ return static_cast<KD_TEX_ALPHA>(m_format & KD_TEX_ALPHA_MASK);
+}
+
+KD_TEX_SWIZ CXBTFFrame::GetKDSwizzle() const
+{
+ return static_cast<KD_TEX_SWIZ>(m_format & KD_TEX_SWIZ_MASK);
+}
+
CXBTFFile::CXBTFFile()
: m_path(),
m_frames()
diff --git a/xbmc/guilib/XBTF.h b/xbmc/guilib/XBTF.h
index 588ba6234d..af6b77c205 100644
--- a/xbmc/guilib/XBTF.h
+++ b/xbmc/guilib/XBTF.h
@@ -16,7 +16,8 @@
#include <stdint.h>
static const std::string XBTF_MAGIC = "XBTF";
-static const std::string XBTF_VERSION = "2";
+static const std::string XBTF_VERSION = "3";
+static const char XBTF_VERSION_MIN = '2';
#include "TextureFormats.h"
@@ -29,7 +30,7 @@ public:
void SetWidth(uint32_t width);
XB_FMT GetFormat(bool raw = false) const;
- void SetFormat(XB_FMT format);
+ void SetFormat(uint32_t format);
uint32_t GetHeight() const;
void SetHeight(uint32_t height);
@@ -51,10 +52,15 @@ public:
bool IsPacked() const;
bool HasAlpha() const;
+ KD_TEX_FMT GetKDFormat() const;
+ KD_TEX_FMT GetKDFormatType() const;
+ KD_TEX_ALPHA GetKDAlpha() const;
+ KD_TEX_SWIZ GetKDSwizzle() const;
+
private:
uint32_t m_width;
uint32_t m_height;
- XB_FMT m_format;
+ uint32_t m_format;
uint64_t m_packedSize;
uint64_t m_unpackedSize;
uint64_t m_offset;
diff --git a/xbmc/guilib/XBTFReader.cpp b/xbmc/guilib/XBTFReader.cpp
index 6d74f46b0d..b4f561f615 100644
--- a/xbmc/guilib/XBTFReader.cpp
+++ b/xbmc/guilib/XBTFReader.cpp
@@ -29,6 +29,17 @@ static bool ReadString(FILE* file, char* str, size_t max_length)
return (fread(str, max_length, 1, file) == 1);
}
+static bool ReadChar(FILE* file, char& value)
+{
+ if (file == nullptr)
+ return false;
+
+ if (fread(&value, sizeof(char), 1, file) != 1)
+ return false;
+
+ return true;
+}
+
static bool ReadUInt32(FILE* file, uint32_t& value)
{
if (file == nullptr)
@@ -89,11 +100,11 @@ bool CXBTFReader::Open(const std::string& path)
return false;
// read the version
- char version[1];
- if (!ReadString(m_file, version, sizeof(version)))
+ char version;
+ if (!ReadChar(m_file, version))
return false;
- if (strncmp(XBTF_VERSION.c_str(), version, sizeof(version)) != 0)
+ if (version < XBTF_VERSION_MIN)
return false;
unsigned int nofFiles;
diff --git a/xbmc/guilib/guiinfo/AddonsGUIInfo.cpp b/xbmc/guilib/guiinfo/AddonsGUIInfo.cpp
index 6630fa14d8..7f8c29e371 100644
--- a/xbmc/guilib/guiinfo/AddonsGUIInfo.cpp
+++ b/xbmc/guilib/guiinfo/AddonsGUIInfo.cpp
@@ -240,7 +240,6 @@ bool CAddonsGUIInfo::GetBool(bool& value, const CGUIListItem *gitem, int context
///////////////////////////////////////////////////////////////////////////////////////////////
case SYSTEM_HAS_ADDON:
{
- ADDON::AddonPtr addon;
value = CServiceBroker::GetAddonMgr().IsAddonInstalled(info.GetData3());
return true;
}
diff --git a/xbmc/guilib/guiinfo/GUIControlsGUIInfo.cpp b/xbmc/guilib/guiinfo/GUIControlsGUIInfo.cpp
index a5ee133580..74483bd2b1 100644
--- a/xbmc/guilib/guiinfo/GUIControlsGUIInfo.cpp
+++ b/xbmc/guilib/guiinfo/GUIControlsGUIInfo.cpp
@@ -204,17 +204,19 @@ bool CGUIControlsGUIInfo::GetLabel(std::string& value, const CFileItem *item, in
{
int count = 0;
const CFileItemList& items = window->CurrentDirectory();
- for (const auto& item : items)
+ for (const auto& i : items)
{
// Iterate through container and count watched, unwatched and total duration.
- if (info.m_info == CONTAINER_TOTALWATCHED && item->HasVideoInfoTag() && item->GetVideoInfoTag()->GetPlayCount() > 0)
+ if (info.m_info == CONTAINER_TOTALWATCHED && i->HasVideoInfoTag() &&
+ i->GetVideoInfoTag()->GetPlayCount() > 0)
count += 1;
- else if (info.m_info == CONTAINER_TOTALUNWATCHED && item->HasVideoInfoTag() && item->GetVideoInfoTag()->GetPlayCount() == 0)
+ else if (info.m_info == CONTAINER_TOTALUNWATCHED && i->HasVideoInfoTag() &&
+ i->GetVideoInfoTag()->GetPlayCount() == 0)
count += 1;
- else if (info.m_info == CONTAINER_TOTALTIME && item->HasMusicInfoTag())
- count += item->GetMusicInfoTag()->GetDuration();
- else if (info.m_info == CONTAINER_TOTALTIME && item->HasVideoInfoTag())
- count += item->GetVideoInfoTag()->m_streamDetails.GetVideoDuration();
+ else if (info.m_info == CONTAINER_TOTALTIME && i->HasMusicInfoTag())
+ count += i->GetMusicInfoTag()->GetDuration();
+ else if (info.m_info == CONTAINER_TOTALTIME && i->HasVideoInfoTag())
+ count += i->GetVideoInfoTag()->m_streamDetails.GetVideoDuration();
}
if (info.m_info == CONTAINER_TOTALTIME && count > 0)
{
@@ -531,9 +533,9 @@ bool CGUIControlsGUIInfo::GetBool(bool& value, const CGUIListItem *gitem, int co
}
if (content.empty())
{
- CGUIMediaWindow* window = GUIINFO::GetMediaWindow(contextWindow);
- if (window)
- content = window->CurrentDirectory().GetContent();
+ CGUIMediaWindow* mediaWindow = GUIINFO::GetMediaWindow(contextWindow);
+ if (mediaWindow)
+ content = mediaWindow->CurrentDirectory().GetContent();
}
value = StringUtils::EqualsNoCase(info.GetData3(), content);
return true;
diff --git a/xbmc/guilib/guiinfo/GUIInfo.h b/xbmc/guilib/guiinfo/GUIInfo.h
index c5bffe8704..10604e623e 100644
--- a/xbmc/guilib/guiinfo/GUIInfo.h
+++ b/xbmc/guilib/guiinfo/GUIInfo.h
@@ -35,49 +35,31 @@ public:
}
explicit CGUIInfo(int info, uint32_t data1 = 0, int data2 = 0, uint32_t flag = 0)
- : m_info(info),
- m_data1(data1),
- m_data2(data2),
- m_data4(0)
+ : m_info(info), m_data1(data1), m_data2(data2)
{
if (flag)
SetInfoFlag(flag);
}
CGUIInfo(int info, uint32_t data1, int data2, const std::string& data3)
- : m_info(info), m_data1(data1), m_data2(data2), m_data3(data3), m_data4(0)
+ : m_info(info), m_data1(data1), m_data2(data2), m_data3(data3)
{
}
CGUIInfo(int info, uint32_t data1, const std::string& data3)
- : m_info(info),
- m_data1(data1),
- m_data2(0),
- m_data3(data3),
- m_data4(0)
+ : m_info(info), m_data1(data1), m_data3(data3)
{
}
- CGUIInfo(int info, const std::string& data3)
- : m_info(info),
- m_data1(0),
- m_data2(0),
- m_data3(data3),
- m_data4(0)
- {
- }
+ CGUIInfo(int info, const std::string& data3) : m_info(info), m_data3(data3) {}
CGUIInfo(int info, const std::string& data3, int data2)
- : m_info(info),
- m_data1(0),
- m_data2(data2),
- m_data3(data3),
- m_data4(0)
+ : m_info(info), m_data2(data2), m_data3(data3)
{
}
CGUIInfo(int info, const std::string& data3, const std::string& data5)
- : m_info(info), m_data1(0), m_data3(data3), m_data4(0), m_data5(data5)
+ : m_info(info), m_data3(data3), m_data5(data5)
{
}
@@ -98,10 +80,10 @@ public:
private:
void SetInfoFlag(uint32_t flag);
- uint32_t m_data1;
- int m_data2;
+ uint32_t m_data1{0};
+ int m_data2{0};
std::string m_data3;
- int m_data4;
+ int m_data4{0};
std::string m_data5;
};
diff --git a/xbmc/guilib/guiinfo/GUIInfoLabel.h b/xbmc/guilib/guiinfo/GUIInfoLabel.h
index fd5da50154..b2d58f9412 100644
--- a/xbmc/guilib/guiinfo/GUIInfoLabel.h
+++ b/xbmc/guilib/guiinfo/GUIInfoLabel.h
@@ -32,9 +32,9 @@ class CGUIInfoLabel
{
public:
CGUIInfoLabel() = default;
- CGUIInfoLabel(const std::string& label,
- const std::string& fallback = "",
- int context = INFO::DEFAULT_CONTEXT);
+ explicit CGUIInfoLabel(const std::string& label,
+ const std::string& fallback = "",
+ int context = INFO::DEFAULT_CONTEXT);
void SetLabel(const std::string& label,
const std::string& fallback,
diff --git a/xbmc/guilib/guiinfo/GUIInfoLabels.h b/xbmc/guilib/guiinfo/GUIInfoLabels.h
index c801a1eb33..613aff347f 100644
--- a/xbmc/guilib/guiinfo/GUIInfoLabels.h
+++ b/xbmc/guilib/guiinfo/GUIInfoLabels.h
@@ -321,8 +321,16 @@
#define RETROPLAYER_STRETCH_MODE 331
#define RETROPLAYER_VIDEO_ROTATION 332
-// More PVR infolabels
+// More VideoPlayer infolabels
#define VIDEOPLAYER_CHANNEL_LOGO 333
+#define VIDEOPLAYER_EPISODEPART 334
+#define VIDEOPLAYER_PARENTAL_RATING_CODE 335
+#define VIDEOPLAYER_PARENTAL_RATING_ICON 336
+#define VIDEOPLAYER_PARENTAL_RATING_SOURCE 337
+#define VIDEOPLAYER_MEDIAPROVIDERS 338
+
+// More MusicPlayer infolabels
+#define MUSICPLAYER_MEDIAPROVIDERS 339
#define CONTAINER_HAS_PARENT_ITEM 341
#define CONTAINER_CAN_FILTER 342
@@ -989,6 +997,10 @@ static constexpr unsigned int SYSTEM_LOCALE = 1012;
#define LISTITEM_PVR_INSTANCE_NAME (LISTITEM_START + 218)
#define LISTITEM_CHANNEL_LOGO (LISTITEM_START + 219)
#define LISTITEM_PVR_GROUP_ORIGIN (LISTITEM_START + 220)
+#define LISTITEM_PARENTAL_RATING_ICON (LISTITEM_START + 221)
+#define LISTITEM_PARENTAL_RATING_SOURCE (LISTITEM_START + 222)
+#define LISTITEM_EPISODEPART (LISTITEM_START + 223)
+#define LISTITEM_MEDIAPROVIDERS (LISTITEM_START + 224)
#define LISTITEM_END (LISTITEM_START + 2500)
diff --git a/xbmc/guilib/guiinfo/VisualisationGUIInfo.cpp b/xbmc/guilib/guiinfo/VisualisationGUIInfo.cpp
index 27a892f106..a988ff402a 100644
--- a/xbmc/guilib/guiinfo/VisualisationGUIInfo.cpp
+++ b/xbmc/guilib/guiinfo/VisualisationGUIInfo.cpp
@@ -42,12 +42,9 @@ bool CVisualisationGUIInfo::GetLabel(std::string& value, const CFileItem *item,
if (msg.GetPointer())
{
CGUIVisualisationControl* viz = static_cast<CGUIVisualisationControl*>(msg.GetPointer());
- if (viz)
- {
- value = viz->GetActivePresetName();
- URIUtils::RemoveExtension(value);
- return true;
- }
+ value = viz->GetActivePresetName();
+ URIUtils::RemoveExtension(value);
+ return true;
}
break;
}
@@ -86,8 +83,8 @@ bool CVisualisationGUIInfo::GetBool(bool& value, const CGUIListItem *gitem, int
CServiceBroker::GetGUI()->GetWindowManager().SendMessage(msg);
if (msg.GetPointer())
{
- CGUIVisualisationControl *pVis = static_cast<CGUIVisualisationControl*>(msg.GetPointer());
- value = pVis->IsLocked();
+ CGUIVisualisationControl* viz = static_cast<CGUIVisualisationControl*>(msg.GetPointer());
+ value = viz->IsLocked();
return true;
}
break;
@@ -104,7 +101,7 @@ bool CVisualisationGUIInfo::GetBool(bool& value, const CGUIListItem *gitem, int
if (msg.GetPointer())
{
CGUIVisualisationControl* viz = static_cast<CGUIVisualisationControl*>(msg.GetPointer());
- value = (viz && viz->HasPresets());
+ value = viz->HasPresets();
return true;
}
break;
diff --git a/xbmc/guilib/listproviders/DirectoryProvider.cpp b/xbmc/guilib/listproviders/DirectoryProvider.cpp
index c71855255a..e0cd42d039 100644
--- a/xbmc/guilib/listproviders/DirectoryProvider.cpp
+++ b/xbmc/guilib/listproviders/DirectoryProvider.cpp
@@ -516,7 +516,7 @@ protected:
return true;
}
- bool OnMoreSelected() override
+ bool OnChooseSelected() override
{
m_provider.OnContextMenu(m_item);
return true;
diff --git a/xbmc/input/WindowTranslator.cpp b/xbmc/input/WindowTranslator.cpp
index c8c48374ce..795c854f0f 100644
--- a/xbmc/input/WindowTranslator.cpp
+++ b/xbmc/input/WindowTranslator.cpp
@@ -36,11 +36,13 @@ const CWindowTranslator::WindowMapByName CWindowTranslator::WindowMappingByName
{"tvguide", WINDOW_TV_GUIDE},
{"tvtimers", WINDOW_TV_TIMERS},
{"tvsearch", WINDOW_TV_SEARCH},
+ {"tvproviders", WINDOW_TV_PROVIDERS},
{"radiochannels", WINDOW_RADIO_CHANNELS},
{"radiorecordings", WINDOW_RADIO_RECORDINGS},
{"radioguide", WINDOW_RADIO_GUIDE},
{"radiotimers", WINDOW_RADIO_TIMERS},
{"radiosearch", WINDOW_RADIO_SEARCH},
+ {"radioproviders", WINDOW_RADIO_PROVIDERS},
{"gamecontrollers", WINDOW_DIALOG_GAME_CONTROLLERS},
{"gameports", WINDOW_DIALOG_GAME_PORTS},
{"games", WINDOW_GAMES},
diff --git a/xbmc/input/keymaps/remote/IRTranslator.h b/xbmc/input/keymaps/remote/IRTranslator.h
index 96eddcf153..862fb7599c 100644
--- a/xbmc/input/keymaps/remote/IRTranslator.h
+++ b/xbmc/input/keymaps/remote/IRTranslator.h
@@ -8,6 +8,7 @@
#pragma once
+#include <cstdint>
#include <map>
#include <memory>
#include <string>
diff --git a/xbmc/interfaces/json-rpc/AudioLibrary.cpp b/xbmc/interfaces/json-rpc/AudioLibrary.cpp
index ab08cdf833..bed043b421 100644
--- a/xbmc/interfaces/json-rpc/AudioLibrary.cpp
+++ b/xbmc/interfaces/json-rpc/AudioLibrary.cpp
@@ -32,6 +32,8 @@
#include <memory>
+#include <music/MusicLibraryQueue.h>
+
using namespace MUSIC_INFO;
using namespace JSONRPC;
using namespace XFILE;
@@ -1354,6 +1356,60 @@ JSONRPC_STATUS CAudioLibrary::GetAdditionalSongDetails(const CVariant& parameter
return OK;
}
+JSONRPC_STATUS CAudioLibrary::RefreshArtist(const std::string& method,
+ ITransportLayer* transport,
+ IClient* client,
+ const CVariant& parameterObject,
+ CVariant& result)
+{
+ int artistID = (int)parameterObject["artistid"].asInteger();
+
+ CMusicDbUrl musicUrl;
+ if (!musicUrl.FromString("musicdb://artists/"))
+ return InternalError;
+
+ CMusicDatabase musicdatabase;
+ if (!musicdatabase.Open())
+ return InternalError;
+
+ //checking if artistID is a valid one
+ if (!musicdatabase.GetArtistExists(artistID))
+ return InvalidParams;
+
+ //set the artist id on the musicdb url
+ musicUrl.AddOption("artistid", artistID);
+
+ //executing the StartArtistScan for refreshing the artist scraped informations
+ CMusicLibraryQueue::GetInstance().StartArtistScan(musicUrl.ToString(), true);
+
+ return ACK;
+}
+
+JSONRPC_STATUS CAudioLibrary::RefreshAlbum(const std::string& method,
+ ITransportLayer* transport,
+ IClient* client,
+ const CVariant& parameterObject,
+ CVariant& result)
+{
+ int albumID = (int)parameterObject["albumid"].asInteger();
+
+ CMusicDatabase musicdatabase;
+ if (!musicdatabase.Open())
+ return InternalError;
+
+ //check if albumID is a valid one
+ CAlbum album;
+ if (!musicdatabase.GetAlbum(albumID, album, false))
+ return InvalidParams;
+
+ std::string path = StringUtils::Format("musicdb://albums/{}/", albumID);
+
+ //execute the album refresh job
+ CMusicLibraryQueue::GetInstance().StartAlbumScan(path, true);
+
+ return ACK;
+}
+
bool CAudioLibrary::CheckForAdditionalProperties(const CVariant &properties, const std::set<std::string> &checkProperties, std::set<std::string> &foundProperties)
{
if (!properties.isArray() || properties.empty())
diff --git a/xbmc/interfaces/json-rpc/AudioLibrary.h b/xbmc/interfaces/json-rpc/AudioLibrary.h
index 9946f9d998..674bb0a0d3 100644
--- a/xbmc/interfaces/json-rpc/AudioLibrary.h
+++ b/xbmc/interfaces/json-rpc/AudioLibrary.h
@@ -69,6 +69,16 @@ namespace JSONRPC
static JSONRPC_STATUS GetAdditionalSongDetails(const CVariant& parameterObject,
const CFileItemList& items,
CMusicDatabase& musicdatabase);
+ static JSONRPC_STATUS RefreshArtist(const std::string& method,
+ ITransportLayer* transport,
+ IClient* client,
+ const CVariant& parameterObject,
+ CVariant& result);
+ static JSONRPC_STATUS RefreshAlbum(const std::string& method,
+ ITransportLayer* transport,
+ IClient* client,
+ const CVariant& parameterObject,
+ CVariant& result);
private:
static void FillAlbumItem(const CAlbum& album,
diff --git a/xbmc/interfaces/json-rpc/JSONServiceDescription.cpp b/xbmc/interfaces/json-rpc/JSONServiceDescription.cpp
index 0ce49b9898..a5b918f64f 100644
--- a/xbmc/interfaces/json-rpc/JSONServiceDescription.cpp
+++ b/xbmc/interfaces/json-rpc/JSONServiceDescription.cpp
@@ -119,6 +119,8 @@ JsonRpcMethodMap CJSONServiceDescription::m_methodMaps[] = {
{ "AudioLibrary.SetArtistDetails", CAudioLibrary::SetArtistDetails },
{ "AudioLibrary.SetAlbumDetails", CAudioLibrary::SetAlbumDetails },
{ "AudioLibrary.SetSongDetails", CAudioLibrary::SetSongDetails },
+ { "AudioLibrary.RefreshArtist", CAudioLibrary::RefreshArtist },
+ { "AudioLibrary.RefreshAlbum", CAudioLibrary::RefreshAlbum },
{ "AudioLibrary.Scan", CAudioLibrary::Scan },
{ "AudioLibrary.Export", CAudioLibrary::Export },
{ "AudioLibrary.Clean", CAudioLibrary::Clean },
diff --git a/xbmc/interfaces/json-rpc/schema/methods.json b/xbmc/interfaces/json-rpc/schema/methods.json
index 19be352bb1..aa70a2879f 100644
--- a/xbmc/interfaces/json-rpc/schema/methods.json
+++ b/xbmc/interfaces/json-rpc/schema/methods.json
@@ -3063,6 +3063,34 @@
],
"returns": "string"
},
+ "AudioLibrary.RefreshArtist": {
+ "type": "method",
+ "description": "Refresh the given artist in the library",
+ "transport": "Response",
+ "permission": "UpdateData",
+ "params": [
+ {
+ "name": "artistid",
+ "$ref": "Library.Id",
+ "required": true
+ }
+ ],
+ "returns": "string"
+ },
+ "AudioLibrary.RefreshAlbum": {
+ "type": "method",
+ "description": "Refresh the given album in the library",
+ "transport": "Response",
+ "permission": "UpdateData",
+ "params": [
+ {
+ "name": "albumid",
+ "$ref": "Library.Id",
+ "required": true
+ }
+ ],
+ "returns": "string"
+ },
"AudioLibrary.Scan": {
"type": "method",
"description": "Scans the audio sources for new library items",
diff --git a/xbmc/interfaces/json-rpc/schema/version.txt b/xbmc/interfaces/json-rpc/schema/version.txt
index 4727db3092..eecf12c86d 100644
--- a/xbmc/interfaces/json-rpc/schema/version.txt
+++ b/xbmc/interfaces/json-rpc/schema/version.txt
@@ -1 +1 @@
-JSONRPC_VERSION 13.6.0
+JSONRPC_VERSION 13.7.0
diff --git a/xbmc/messaging/ThreadMessage.h b/xbmc/messaging/ThreadMessage.h
index 1a277ffcd4..8360d30cc1 100644
--- a/xbmc/messaging/ThreadMessage.h
+++ b/xbmc/messaging/ThreadMessage.h
@@ -8,6 +8,7 @@
#pragma once
+#include <cstdint>
#include <memory>
#include <string>
#include <utility>
diff --git a/xbmc/music/MusicDatabase.cpp b/xbmc/music/MusicDatabase.cpp
index 33896766ba..99d5a76ffc 100644
--- a/xbmc/music/MusicDatabase.cpp
+++ b/xbmc/music/MusicDatabase.cpp
@@ -13234,6 +13234,13 @@ int CMusicDatabase::GetOrderFilter(const std::string& type,
}
}
+ // Get the right tableview as if we are using strArtistSort the column name is ambiguous
+ std::string table;
+ if (StringUtils::StartsWithNoCase(type, "album"))
+ table = "albumview.";
+ else if (StringUtils::StartsWithNoCase(type, "song"))
+ table = "songview.";
+
// Convert field names into order by statement elements
for (auto& name : orderfields)
{
@@ -13242,7 +13249,8 @@ int CMusicDatabase::GetOrderFilter(const std::string& type,
if (StringUtils::EndsWith(name, "strArtists") || StringUtils::EndsWith(name, "strArtist"))
{
if (StringUtils::EndsWith(name, "strArtists"))
- sortSQL = SortnameBuildSQL("artistsortname", sorting.sortAttributes, name, "strArtistSort");
+ sortSQL = SortnameBuildSQL("artistsortname", sorting.sortAttributes, name,
+ table + "strArtistSort");
else
sortSQL = SortnameBuildSQL("artistsortname", sorting.sortAttributes, name, "strSortName");
if (!sortSQL.empty())
diff --git a/xbmc/network/cddb.cpp b/xbmc/network/cddb.cpp
index 3d807963c5..977411b54e 100644
--- a/xbmc/network/cddb.cpp
+++ b/xbmc/network/cddb.cpp
@@ -126,11 +126,10 @@ bool Xcddb::Send( const void *buffer, int bytes )
{
std::unique_ptr<char[]> tmp_buffer(new char[bytes + 10]);
strcpy(tmp_buffer.get(), (const char*)buffer);
- tmp_buffer.get()[bytes] = '.';
- tmp_buffer.get()[bytes + 1] = 0x0d;
- tmp_buffer.get()[bytes + 2] = 0x0a;
- tmp_buffer.get()[bytes + 3] = 0x00;
- int iErr = send((SOCKET)m_cddb_socket, (const char*)tmp_buffer.get(), bytes + 3, 0);
+ tmp_buffer.get()[bytes] = 0x0d;
+ tmp_buffer.get()[bytes + 1] = 0x0a;
+ tmp_buffer.get()[bytes + 2] = 0x00;
+ int iErr = send((SOCKET)m_cddb_socket, (const char*)tmp_buffer.get(), bytes + 2, 0);
if (iErr <= 0)
{
return false;
diff --git a/xbmc/pictures/GUIWindowSlideShow.cpp b/xbmc/pictures/GUIWindowSlideShow.cpp
index 6dc76a5c9a..fbe77a374f 100644
--- a/xbmc/pictures/GUIWindowSlideShow.cpp
+++ b/xbmc/pictures/GUIWindowSlideShow.cpp
@@ -152,12 +152,30 @@ CGUIWindowSlideShow::CGUIWindowSlideShow(void)
m_loadType = KEEP_IN_MEMORY;
m_bLoadNextPic = false;
CServiceBroker::GetSlideShowDelegator().SetDelegate(this);
+ CServiceBroker::GetAnnouncementManager()->AddAnnouncer(this);
Reset();
}
CGUIWindowSlideShow::~CGUIWindowSlideShow()
{
CServiceBroker::GetSlideShowDelegator().ResetDelegate();
+ CServiceBroker::GetAnnouncementManager()->RemoveAnnouncer(this);
+}
+
+void CGUIWindowSlideShow::Announce(ANNOUNCEMENT::AnnouncementFlag flag,
+ const std::string& sender,
+ const std::string& message,
+ const CVariant& data)
+{
+ if (flag & ANNOUNCEMENT::Player)
+ {
+ if (message == "OnPlay" || message == "OnResume")
+ {
+ if (data.isMember("player") && data["player"].isMember("playerid") &&
+ data["player"]["playerid"] == static_cast<int>(PLAYLIST::Id::TYPE_VIDEO))
+ Close();
+ }
+ }
}
void CGUIWindowSlideShow::AnnouncePlayerPlay(const CFileItemPtr& item)
diff --git a/xbmc/pictures/GUIWindowSlideShow.h b/xbmc/pictures/GUIWindowSlideShow.h
index 0033a39667..5c6e82268a 100644
--- a/xbmc/pictures/GUIWindowSlideShow.h
+++ b/xbmc/pictures/GUIWindowSlideShow.h
@@ -10,6 +10,7 @@
#include "SlideShowPicture.h"
#include "guilib/GUIDialog.h"
+#include "interfaces/IAnnouncer.h"
#include "interfaces/ISlideShowDelegate.h"
#include "threads/Event.h"
#include "threads/Thread.h"
@@ -48,7 +49,9 @@ private:
CGUIWindowSlideShow* m_pCallback = nullptr;
};
-class CGUIWindowSlideShow : public CGUIDialog, public ISlideShowDelegate
+class CGUIWindowSlideShow : public CGUIDialog,
+ public ISlideShowDelegate,
+ public ANNOUNCEMENT::IAnnouncer
{
public:
CGUIWindowSlideShow(void);
@@ -87,6 +90,12 @@ public:
void Shuffle() override;
int GetDirection() const override { return m_iDirection; }
+ // implementation of IAnnouncer
+ void Announce(ANNOUNCEMENT::AnnouncementFlag flag,
+ const std::string& sender,
+ const std::string& message,
+ const CVariant& data) override;
+
bool OnMessage(CGUIMessage& message) override;
EVENT_RESULT OnMouseEvent(const CPoint& point, const KODI::MOUSE::CMouseEvent& event) override;
bool OnAction(const CAction& action) override;
diff --git a/xbmc/pictures/PictureThumbLoader.cpp b/xbmc/pictures/PictureThumbLoader.cpp
index d26c7bab38..bdbc020347 100644
--- a/xbmc/pictures/PictureThumbLoader.cpp
+++ b/xbmc/pictures/PictureThumbLoader.cpp
@@ -23,6 +23,7 @@
#include "settings/AdvancedSettings.h"
#include "settings/Settings.h"
#include "settings/SettingsComponent.h"
+#include "utils/ArtUtils.h"
#include "utils/FileExtensionProvider.h"
#include "utils/FileUtils.h"
#include "utils/URIUtils.h"
@@ -100,7 +101,7 @@ bool CPictureThumbLoader::LoadItemCached(CFileItem* pItem)
CServiceBroker::GetTextureCache()->BackgroundCacheImage(thumb);
pItem->SetArt("thumb", thumb);
}
- pItem->FillInDefaultIcon();
+ ART::FillInDefaultIcon(*pItem);
return true;
}
@@ -215,6 +216,6 @@ void CPictureThumbLoader::ProcessFoldersAndArchives(CFileItem *pItem)
}
}
// refill in the icon to get it to update
- pItem->FillInDefaultIcon();
+ ART::FillInDefaultIcon(*pItem);
}
}
diff --git a/xbmc/platform/android/PlatformAndroid.cpp b/xbmc/platform/android/PlatformAndroid.cpp
index ba9208f6f5..d35896cd14 100644
--- a/xbmc/platform/android/PlatformAndroid.cpp
+++ b/xbmc/platform/android/PlatformAndroid.cpp
@@ -17,6 +17,7 @@
#include "platform/android/activity/XBMCApp.h"
#include "platform/android/powermanagement/AndroidPowerSyscall.h"
+#include "platform/android/storage/AndroidStorageProvider.h"
#include <stdlib.h>
@@ -66,7 +67,7 @@ void CPlatformAndroid::PlatformSyslog()
CJNIBuild::BRAND, CJNIBuild::MODEL, CJNIBuild::HARDWARE);
std::string extstorage;
- bool extready = CXBMCApp::GetExternalStorage(extstorage);
+ const bool extready = CAndroidStorageProvider::GetExternalStorage(extstorage);
CLog::Log(
LOGINFO, "External storage path = {}; status = {}; Permissions = {}{}", extstorage,
extready ? "ok" : "nok",
diff --git a/xbmc/platform/android/activity/XBMCApp.cpp b/xbmc/platform/android/activity/XBMCApp.cpp
index b4cdde669c..10e00d0146 100644
--- a/xbmc/platform/android/activity/XBMCApp.cpp
+++ b/xbmc/platform/android/activity/XBMCApp.cpp
@@ -81,7 +81,6 @@
#include <androidjni/Cursor.h>
#include <androidjni/Display.h>
#include <androidjni/DisplayManager.h>
-#include <androidjni/Environment.h>
#include <androidjni/File.h>
#include <androidjni/Intent.h>
#include <androidjni/IntentFilter.h>
@@ -1091,42 +1090,6 @@ int CXBMCApp::GetBatteryLevel() const
return m_batteryLevel;
}
-bool CXBMCApp::GetExternalStorage(std::string &path, const std::string &type /* = "" */)
-{
- std::string sType;
- std::string mountedState;
- bool mounted = false;
-
- if(type == "files" || type.empty())
- {
- CJNIFile external = CJNIEnvironment::getExternalStorageDirectory();
- if (external)
- path = external.getAbsolutePath();
- }
- else
- {
- if (type == "music")
- sType = "Music"; // Environment.DIRECTORY_MUSIC
- else if (type == "videos")
- sType = "Movies"; // Environment.DIRECTORY_MOVIES
- else if (type == "pictures")
- sType = "Pictures"; // Environment.DIRECTORY_PICTURES
- else if (type == "photos")
- sType = "DCIM"; // Environment.DIRECTORY_DCIM
- else if (type == "downloads")
- sType = "Download"; // Environment.DIRECTORY_DOWNLOADS
- if (!sType.empty())
- {
- CJNIFile external = CJNIEnvironment::getExternalStoragePublicDirectory(sType);
- if (external)
- path = external.getAbsolutePath();
- }
- }
- mountedState = CJNIEnvironment::getExternalStorageState();
- mounted = (mountedState == "mounted" || mountedState == "mounted_ro");
- return mounted && !path.empty();
-}
-
// Used in Application.cpp to figure out volume steps
int CXBMCApp::GetMaxSystemVolume()
{
diff --git a/xbmc/platform/android/activity/XBMCApp.h b/xbmc/platform/android/activity/XBMCApp.h
index 6fa89cbce3..73f5dcdf09 100644
--- a/xbmc/platform/android/activity/XBMCApp.h
+++ b/xbmc/platform/android/activity/XBMCApp.h
@@ -166,13 +166,6 @@ public:
const std::string& className = std::string());
std::vector<androidPackage> GetApplications() const;
- /*!
- * \brief If external storage is available, it returns the path for the external storage (for the specified type)
- * \param path will contain the path of the external storage (for the specified type)
- * \param type optional type. Possible values are "", "files", "music", "videos", "pictures", "photos, "downloads"
- * \return true if external storage is available and a valid path has been stored in the path parameter
- */
- static bool GetExternalStorage(std::string& path, const std::string& type = "");
static int GetMaxSystemVolume();
static float GetSystemVolume();
static void SetSystemVolume(float percent);
diff --git a/xbmc/platform/android/storage/AndroidStorageProvider.cpp b/xbmc/platform/android/storage/AndroidStorageProvider.cpp
index 33f7ab8a23..59fe3d6ae2 100644
--- a/xbmc/platform/android/storage/AndroidStorageProvider.cpp
+++ b/xbmc/platform/android/storage/AndroidStorageProvider.cpp
@@ -17,8 +17,6 @@
#include "utils/URIUtils.h"
#include "utils/log.h"
-#include "platform/android/activity/XBMCApp.h"
-
#include <array>
#include <cstdio>
#include <cstdlib>
@@ -131,7 +129,7 @@ void CAndroidStorageProvider::GetLocalDrives(VECSOURCES &localDrives)
// external directory
std::string path;
- if (CXBMCApp::GetExternalStorage(path) && !path.empty() && XFILE::CDirectory::Exists(path))
+ if (GetExternalStorage(path) && !path.empty() && XFILE::CDirectory::Exists(path))
{
share.strPath = path;
share.strName = g_localizeStrings.Get(21456);
@@ -391,8 +389,7 @@ std::vector<std::string> CAndroidStorageProvider::GetDiskUsage()
usage.clear();
// add external storage if available
std::string path;
- if (CXBMCApp::GetExternalStorage(path) && !path.empty() && GetStorageUsage(path, usage) &&
- !usage.empty())
+ if (GetExternalStorage(path) && !path.empty() && GetStorageUsage(path, usage) && !usage.empty())
result.push_back(usage);
// add removable storage
@@ -451,3 +448,41 @@ bool CAndroidStorageProvider::GetStorageUsage(const std::string& path, std::stri
PATH_MAXLEN, totalSize, "G", usedSize, "G", freeSize, "G", usedPercentage, "%");
return true;
}
+
+bool CAndroidStorageProvider::GetExternalStorage(std::string& path,
+ const std::string& type /* = "" */)
+{
+ std::string sType;
+ std::string mountedState;
+ bool mounted = false;
+
+ if (type == "files" || type.empty())
+ {
+ CJNIFile external = CJNIEnvironment::getExternalStorageDirectory();
+ if (external)
+ path = external.getAbsolutePath();
+ }
+ else
+ {
+ if (type == "music")
+ sType = "Music"; // Environment.DIRECTORY_MUSIC
+ else if (type == "videos")
+ sType = "Movies"; // Environment.DIRECTORY_MOVIES
+ else if (type == "pictures")
+ sType = "Pictures"; // Environment.DIRECTORY_PICTURES
+ else if (type == "photos")
+ sType = "DCIM"; // Environment.DIRECTORY_DCIM
+ else if (type == "downloads")
+ sType = "Download"; // Environment.DIRECTORY_DOWNLOADS
+ if (!sType.empty())
+ {
+ CJNIFile external = CJNIEnvironment::getExternalStoragePublicDirectory(sType);
+ if (external)
+ path = external.getAbsolutePath();
+ }
+ }
+
+ mountedState = CJNIEnvironment::getExternalStorageState();
+ mounted = (mountedState == "mounted" || mountedState == "mounted_ro");
+ return mounted && !path.empty();
+}
diff --git a/xbmc/platform/android/storage/AndroidStorageProvider.h b/xbmc/platform/android/storage/AndroidStorageProvider.h
index e39ff60dea..abd7b8e3b2 100644
--- a/xbmc/platform/android/storage/AndroidStorageProvider.h
+++ b/xbmc/platform/android/storage/AndroidStorageProvider.h
@@ -30,6 +30,14 @@ public:
bool PumpDriveChangeEvents(IStorageEventsCallback* callback) override;
+ /*!
+ * \brief If external storage is available, it returns the path for the external storage (for the specified type)
+ * \param path will contain the path of the external storage (for the specified type)
+ * \param type optional type. Possible values are "", "files", "music", "videos", "pictures", "photos, "downloads"
+ * \return true if external storage is available and a valid path has been stored in the path parameter
+ */
+ static bool GetExternalStorage(std::string& path, const std::string& type = "");
+
private:
std::string unescape(const std::string& str);
VECSOURCES m_removableDrives;
diff --git a/xbmc/platform/win10/AsyncHelpers.h b/xbmc/platform/win10/AsyncHelpers.h
index dd2a290b9f..7ad9320dd5 100644
--- a/xbmc/platform/win10/AsyncHelpers.h
+++ b/xbmc/platform/win10/AsyncHelpers.h
@@ -12,6 +12,10 @@
#include <ppltasks.h>
#include <sdkddkver.h>
+#ifndef TARGET_WINDOWS_STORE
+#include <winrt/windows.foundation.h>
+#endif
+
namespace winrt
{
using namespace Windows::Foundation;
diff --git a/xbmc/playlists/PlayListFactory.cpp b/xbmc/playlists/PlayListFactory.cpp
index 7fe5ff6fcb..63b9bd401d 100644
--- a/xbmc/playlists/PlayListFactory.cpp
+++ b/xbmc/playlists/PlayListFactory.cpp
@@ -9,6 +9,7 @@
#include "PlayListFactory.h"
#include "FileItem.h"
+#include "URL.h"
#include "network/NetworkFileItemClassify.h"
#include "playlists/PlayListASX.h"
#include "playlists/PlayListB4S.h"
@@ -25,6 +26,19 @@
namespace KODI::PLAYLIST
{
+CPlayList* CPlayListFactory::Create(const CURL& url)
+{
+ CFileItem item{url.Get(), false};
+
+ if (url.HasOption("mimetype"))
+ {
+ item.SetContentLookup(false);
+ item.SetMimeType(url.GetOption("mimetype"));
+ }
+
+ return Create(item);
+}
+
CPlayList* CPlayListFactory::Create(const std::string& filename)
{
CFileItem item(filename,false);
diff --git a/xbmc/playlists/PlayListFactory.h b/xbmc/playlists/PlayListFactory.h
index 5161150ea5..410c1e392d 100644
--- a/xbmc/playlists/PlayListFactory.h
+++ b/xbmc/playlists/PlayListFactory.h
@@ -20,6 +20,7 @@ namespace KODI::PLAYLIST
class CPlayListFactory
{
public:
+ static CPlayList* Create(const CURL& url);
static CPlayList* Create(const std::string& filename);
static CPlayList* Create(const CFileItem& item);
static bool IsPlaylist(const CURL& url);
diff --git a/xbmc/pvr/CMakeLists.txt b/xbmc/pvr/CMakeLists.txt
index b7cbc7d11e..002b10b784 100644
--- a/xbmc/pvr/CMakeLists.txt
+++ b/xbmc/pvr/CMakeLists.txt
@@ -9,6 +9,7 @@ set(SOURCES PVRCachedImage.cpp
PVREventLogJob.cpp
PVRItem.cpp
PVRManager.cpp
+ PVRPathUtils.cpp
PVRPlaybackState.cpp
PVRStreamProperties.cpp
PVRThumbLoader.cpp)
@@ -19,6 +20,7 @@ set(HEADERS IPVRComponent.h
PVRChannelGroupImageFileLoader.h
PVRChannelNumberInputHandler.h
PVRComponentRegistration.h
+ PVRConstants.h
PVRContextMenus.h
PVRDatabase.h
PVRDescrambleInfo.h
@@ -26,6 +28,7 @@ set(HEADERS IPVRComponent.h
PVREventLogJob.h
PVRItem.h
PVRManager.h
+ PVRPathUtils.h
PVRPlaybackState.h
PVRSignalStatus.h
PVRStreamProperties.h
diff --git a/xbmc/pvr/PVRConstants.h b/xbmc/pvr/PVRConstants.h
new file mode 100644
index 0000000000..06d389fe96
--- /dev/null
+++ b/xbmc/pvr/PVRConstants.h
@@ -0,0 +1,18 @@
+/*
+ * Copyright (C) 2024 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+namespace PVR
+{
+/*!
+ * @brief Denotes an invalid PVR client UID.
+ */
+static constexpr int PVR_CLIENT_INVALID_UID{-1};
+
+} // namespace PVR
diff --git a/xbmc/pvr/PVRContextMenus.cpp b/xbmc/pvr/PVRContextMenus.cpp
index 47004fa744..9faa27d1e8 100644
--- a/xbmc/pvr/PVRContextMenus.cpp
+++ b/xbmc/pvr/PVRContextMenus.cpp
@@ -80,6 +80,8 @@ DECL_STATICCONTEXTMENUITEM(AddReminder);
DECL_STATICCONTEXTMENUITEM(ExecuteSearch);
DECL_STATICCONTEXTMENUITEM(EditSearch);
DECL_STATICCONTEXTMENUITEM(RenameSearch);
+DECL_STATICCONTEXTMENUITEM(ChooseIconForSearch);
+DECL_STATICCONTEXTMENUITEM(DuplicateSearch);
DECL_STATICCONTEXTMENUITEM(DeleteSearch);
class PVRClientMenuHook : public IContextMenuItem
@@ -266,53 +268,37 @@ bool FindSimilar::Execute(const CFileItemPtr& item) const
bool StartRecording::IsVisible(const CFileItem& item) const
{
- const std::shared_ptr<const CPVRClient> client = CServiceBroker::GetPVRManager().GetClient(item);
-
- std::shared_ptr<CPVRChannel> channel = item.GetPVRChannelInfoTag();
+ const std::shared_ptr<CPVRChannel> channel{item.GetPVRChannelInfoTag()};
if (channel)
+ {
+ const std::shared_ptr<const CPVRClient> client{CServiceBroker::GetPVRManager().GetClient(item)};
return client && client->GetClientCapabilities().SupportsTimers() &&
!CServiceBroker::GetPVRManager().Timers()->IsRecordingOnChannel(*channel);
+ }
- const std::shared_ptr<const CPVREpgInfoTag> epg = item.GetEPGInfoTag();
- if (epg && epg->IsRecordable())
+ const std::shared_ptr<const CPVREpgInfoTag> epgTag{item.GetEPGInfoTag()};
+ if (epgTag && epgTag->IsRecordable())
{
- if (epg->IsGapTag())
- {
- channel = CServiceBroker::GetPVRManager().ChannelGroups()->GetChannelForEpgTag(epg);
- if (channel)
- {
- return client && client->GetClientCapabilities().SupportsTimers() &&
- !CServiceBroker::GetPVRManager().Timers()->IsRecordingOnChannel(*channel);
- }
- }
- else
- {
- return client && client->GetClientCapabilities().SupportsTimers() &&
- !CServiceBroker::GetPVRManager().Timers()->GetTimerForEpgTag(epg);
- }
+ const std::shared_ptr<const CPVRClient> client{CServiceBroker::GetPVRManager().GetClient(item)};
+ return client && client->GetClientCapabilities().SupportsTimers() &&
+ !CServiceBroker::GetPVRManager().Timers()->GetTimerForEpgTag(epgTag);
}
+
return false;
}
bool StartRecording::Execute(const CFileItemPtr& item) const
{
- const std::shared_ptr<const CPVREpgInfoTag> epgTag = item->GetEPGInfoTag();
- if (!epgTag || epgTag->IsActive())
- {
- // instant recording
- std::shared_ptr<CPVRChannel> channel;
- if (epgTag)
- channel = CServiceBroker::GetPVRManager().ChannelGroups()->GetChannelForEpgTag(epgTag);
-
- if (!channel)
- channel = item->GetPVRChannelInfoTag();
+ const std::shared_ptr<CPVRChannel> channel{item->GetPVRChannelInfoTag()};
+ if (channel)
+ return CServiceBroker::GetPVRManager().Get<PVR::GUI::Timers>().SetRecordingOnChannel(channel,
+ true);
- if (channel)
- return CServiceBroker::GetPVRManager().Get<PVR::GUI::Timers>().SetRecordingOnChannel(channel,
- true);
- }
+ const std::shared_ptr<const CPVREpgInfoTag> epgTag{item->GetEPGInfoTag()};
+ if (epgTag)
+ return CServiceBroker::GetPVRManager().Get<PVR::GUI::Timers>().AddTimer(*item, false);
- return CServiceBroker::GetPVRManager().Get<PVR::GUI::Timers>().AddTimer(*item, false);
+ return false;
}
///////////////////////////////////////////////////////////////////////////////
@@ -335,9 +321,13 @@ bool StopRecording::IsVisible(const CFileItem& item) const
const std::shared_ptr<const CPVREpgInfoTag> epg = item.GetEPGInfoTag();
if (epg && epg->IsGapTag())
{
- channel = CServiceBroker::GetPVRManager().ChannelGroups()->GetChannelForEpgTag(epg);
- if (channel)
- return CServiceBroker::GetPVRManager().Timers()->IsRecordingOnChannel(*channel);
+ const CDateTime now{CDateTime::GetUTCDateTime()};
+ if (epg->StartAsUTC() <= now && epg->EndAsUTC() >= now)
+ {
+ channel = CServiceBroker::GetPVRManager().ChannelGroups()->GetChannelForEpgTag(epg);
+ if (channel)
+ return CServiceBroker::GetPVRManager().Timers()->IsRecordingOnChannel(*channel);
+ }
}
return false;
@@ -348,12 +338,16 @@ bool StopRecording::Execute(const CFileItemPtr& item) const
const std::shared_ptr<const CPVREpgInfoTag> epgTag = item->GetEPGInfoTag();
if (epgTag && epgTag->IsGapTag())
{
- // instance recording
- const std::shared_ptr<CPVRChannel> channel =
- CServiceBroker::GetPVRManager().ChannelGroups()->GetChannelForEpgTag(epgTag);
- if (channel)
- return CServiceBroker::GetPVRManager().Get<PVR::GUI::Timers>().SetRecordingOnChannel(channel,
- false);
+ const CDateTime now{CDateTime::GetUTCDateTime()};
+ if (epgTag->StartAsUTC() <= now && epgTag->EndAsUTC() >= now)
+ {
+ const std::shared_ptr<CPVRChannel> channel{
+ CServiceBroker::GetPVRManager().ChannelGroups()->GetChannelForEpgTag(epgTag)};
+ if (channel)
+ return CServiceBroker::GetPVRManager().Get<PVR::GUI::Timers>().SetRecordingOnChannel(
+ channel, false);
+ }
+ return false;
}
return CServiceBroker::GetPVRManager().Get<PVR::GUI::Timers>().StopRecording(*item);
@@ -735,6 +729,32 @@ bool RenameSearch::Execute(const std::shared_ptr<CFileItem>& item) const
}
///////////////////////////////////////////////////////////////////////////////
+// Choose icon for saved search
+
+bool ChooseIconForSearch::IsVisible(const CFileItem& item) const
+{
+ return item.HasEPGSearchFilter();
+}
+
+bool ChooseIconForSearch::Execute(const std::shared_ptr<CFileItem>& item) const
+{
+ return CServiceBroker::GetPVRManager().Get<PVR::GUI::EPG>().ChooseIconForSavedSearch(*item);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Duplicate a saved search
+
+bool DuplicateSearch::IsVisible(const CFileItem& item) const
+{
+ return item.HasEPGSearchFilter();
+}
+
+bool DuplicateSearch::Execute(const std::shared_ptr<CFileItem>& item) const
+{
+ return CServiceBroker::GetPVRManager().Get<PVR::GUI::EPG>().DuplicateSavedSearch(*item);
+}
+
+///////////////////////////////////////////////////////////////////////////////
// Delete saved search
bool DeleteSearch::IsVisible(const CFileItem& item) const
@@ -779,6 +799,8 @@ CPVRContextMenuManager::CPVRContextMenuManager()
std::make_shared<CONTEXTMENUITEM::ExecuteSearch>(137), /* Search */
std::make_shared<CONTEXTMENUITEM::EditSearch>(21450), /* Edit */
std::make_shared<CONTEXTMENUITEM::RenameSearch>(118), /* Rename */
+ std::make_shared<CONTEXTMENUITEM::ChooseIconForSearch>(19284), /* Choose icon */
+ std::make_shared<CONTEXTMENUITEM::DuplicateSearch>(19355), /* Duplicate */
std::make_shared<CONTEXTMENUITEM::DeleteSearch>(117), /* Delete */
})
{
diff --git a/xbmc/pvr/PVRDatabase.cpp b/xbmc/pvr/PVRDatabase.cpp
index c84b658e3c..84e7eb99c9 100644
--- a/xbmc/pvr/PVRDatabase.cpp
+++ b/xbmc/pvr/PVRDatabase.cpp
@@ -9,8 +9,13 @@
#include "PVRDatabase.h"
#include "ServiceBroker.h"
+#include "addons/AddonManager.h"
+#include "addons/addoninfo/AddonInfo.h"
+#include "addons/addoninfo/AddonType.h"
#include "dbwrappers/dataset.h"
+#include "pvr/PVRConstants.h" // PVR_CLIENT_INVALID_UID
#include "pvr/addons/PVRClient.h"
+#include "pvr/addons/PVRClientUID.h"
#include "pvr/channels/PVRChannel.h"
#include "pvr/channels/PVRChannelGroupFactory.h"
#include "pvr/channels/PVRChannelGroupMember.h"
@@ -29,6 +34,7 @@
#include <memory>
#include <mutex>
#include <string>
+#include <tuple>
#include <utility>
#include <vector>
@@ -191,12 +197,13 @@ void CPVRDatabase::CreateTables()
);
CLog::LogFC(LOGDEBUG, LOGPVR, "Creating table 'clients'");
- m_pDS->exec(
- "CREATE TABLE clients ("
- "idClient integer primary key, "
- "iPriority integer"
- ")"
- );
+ m_pDS->exec("CREATE TABLE clients ("
+ "idClient integer primary key, "
+ "iPriority integer, "
+ "sAddonID TEXT, "
+ "iInstanceID integer,"
+ "sDateTimeFirstChannelsAdded varchar(20)"
+ ")");
CLog::LogFC(LOGDEBUG, LOGPVR, "Creating table 'timers'");
m_pDS->exec(sqlCreateTimersTable);
@@ -376,6 +383,20 @@ void CPVRDatabase::UpdateTables(int iVersion)
m_pDS->exec("ALTER TABLE channels ADD sDateTimeAdded varchar(20)");
m_pDS->exec("UPDATE channels SET sDateTimeAdded = ''");
}
+
+ if (iVersion < 46)
+ {
+ m_pDS->exec("ALTER TABLE clients ADD sAddonID TEXT");
+ m_pDS->exec("ALTER TABLE clients ADD iInstanceID integer");
+
+ FixupClientIDs();
+ }
+
+ if (iVersion < 47)
+ {
+ m_pDS->exec("ALTER TABLE clients ADD sDateTimeFirstChannelsAdded varchar(20)");
+ m_pDS->exec("UPDATE clients SET sDateTimeFirstChannelsAdded = ''");
+ }
}
/********** Client methods **********/
@@ -390,22 +411,29 @@ bool CPVRDatabase::DeleteClients()
bool CPVRDatabase::Persist(const CPVRClient& client)
{
- if (client.GetID() == PVR_INVALID_CLIENT_ID)
+ if (client.GetID() == PVR_CLIENT_INVALID_UID)
return false;
CLog::LogFC(LOGDEBUG, LOGPVR, "Persisting client {} to database", client.GetID());
- std::unique_lock<CCriticalSection> lock(m_critSection);
+ const CDateTime& dateTime{client.GetDateTimeFirstChannelsAdded()};
+ std::string dateTimeAdded;
+ if (dateTime.IsValid())
+ dateTimeAdded = dateTime.GetAsDBDateTime();
- const std::string strQuery = PrepareSQL("REPLACE INTO clients (idClient, iPriority) VALUES (%i, %i);",
- client.GetID(), client.GetPriority());
+ std::unique_lock<CCriticalSection> lock(m_critSection);
- return ExecuteQuery(strQuery);
+ const std::string sql{
+ PrepareSQL("REPLACE INTO clients (idClient, iPriority, sAddonID, iInstanceID, "
+ "sDateTimeFirstChannelsAdded) VALUES (%i, %i, '%s', %i, '%s')",
+ client.GetID(), client.GetPriority(), client.ID().c_str(), client.InstanceID(),
+ dateTimeAdded.c_str())};
+ return ExecuteQuery(sql);
}
bool CPVRDatabase::Delete(const CPVRClient& client)
{
- if (client.GetID() == PVR_INVALID_CLIENT_ID)
+ if (client.GetID() == PVR_CLIENT_INVALID_UID)
return false;
CLog::LogFC(LOGDEBUG, LOGPVR, "Deleting client {} from the database", client.GetID());
@@ -420,7 +448,7 @@ bool CPVRDatabase::Delete(const CPVRClient& client)
int CPVRDatabase::GetPriority(const CPVRClient& client) const
{
- if (client.GetID() == PVR_INVALID_CLIENT_ID)
+ if (client.GetID() == PVR_CLIENT_INVALID_UID)
return 0;
CLog::LogFC(LOGDEBUG, LOGPVR, "Getting priority for client {} from the database", client.GetID());
@@ -436,6 +464,135 @@ int CPVRDatabase::GetPriority(const CPVRClient& client) const
return atoi(strValue.c_str());
}
+CDateTime CPVRDatabase::GetDateTimeFirstChannelsAdded(const CPVRClient& client) const
+{
+ if (client.GetID() == PVR_CLIENT_INVALID_UID)
+ return {};
+
+ CLog::LogFC(LOGDEBUG, LOGPVR,
+ "Getting datetime first channels added for client {} from the database",
+ client.GetID());
+
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+
+ const std::string whereClause{PrepareSQL("idClient = %i", client.GetID())};
+ const std::string value{GetSingleValue("clients", "sDateTimeFirstChannelsAdded", whereClause)};
+
+ if (value.empty())
+ return {};
+
+ return CDateTime::FromDBDateTime(value);
+}
+
+void CPVRDatabase::FixupClientIDs()
+{
+ // Get enabled and disabled PVR client addon infos
+ std::vector<ADDON::AddonInfoPtr> addonInfos;
+ CServiceBroker::GetAddonMgr().GetAddonInfos(addonInfos, false, ADDON::AddonType::PVRDLL);
+
+ std::vector<std::tuple<std::string, ADDON::AddonInstanceId, std::string>> clientInfos;
+ for (const auto& addonInfo : addonInfos)
+ {
+ const std::vector<ADDON::AddonInstanceId> instanceIds{addonInfo->GetKnownInstanceIds()};
+ for (const auto instanceId : instanceIds)
+ {
+ clientInfos.emplace_back(addonInfo->ID(), instanceId, addonInfo->Name());
+ }
+ }
+
+ for (const auto& [addonID, instanceID, addonName] : clientInfos)
+ {
+ // Entry with legacy client id present in clients or channels table?
+ const CPVRClientUID uid{addonID, instanceID};
+ int legacyID{uid.GetLegacyUID()};
+ std::string sql{PrepareSQL("idClient = %i", legacyID)};
+ int id{GetSingleValueInt("clients", "idClient", sql)};
+ if (id == legacyID)
+ {
+ // Add addon id and instance id to existing clients table entry.
+ sql = PrepareSQL("UPDATE clients SET sAddonID = '%s', iInstanceID = %i WHERE idClient = %i",
+ addonID.c_str(), instanceID, legacyID);
+ ExecuteQuery(sql);
+ }
+ else
+ {
+ sql = PrepareSQL("iClientId = %i", legacyID);
+ id = GetSingleValueInt("channels", "iClientId", sql);
+ if (id == legacyID)
+ {
+ // Create a new entry with the legacy client id in clients table.
+ sql = PrepareSQL("REPLACE INTO clients (idClient, iPriority, sAddonID, iInstanceID) VALUES "
+ "(%i, %i, '%s', %i)",
+ legacyID, 0, addonID.c_str(), instanceID);
+ ExecuteQuery(sql);
+ }
+ else
+ {
+ // The legacy id was not found in channels table. This happens if the std::hash
+ // implementation changed (for example after a Kodi update), which according to std::hash
+ // documentation can happen: "Hash functions are only required to produce the same result
+ // for the same input within a single execution of a program"
+
+ // We can only fix some of the ids in this case: We can try to find the legacy id via the
+ // addon's name in the providers table. This is not guaranteed to always work (theoretically
+ // addon name might have changed) and cannot work if providers table contains more than one
+ // entry for the same multi-instance addon.
+
+ sql = PrepareSQL("SELECT iClientId FROM providers WHERE iType = 1 AND sName = '%s'",
+ addonName.c_str());
+
+ if (ResultQuery(sql))
+ {
+ if (m_pDS->num_rows() != 1)
+ {
+ CLog::Log(LOGERROR, "Unable to fixup client id {} for addon '{}', instance {}!",
+ legacyID, addonID.c_str(), instanceID);
+ }
+ else
+ {
+ // There is exactly one provider with the addon name in question.
+ // Its client id is the legacy id we're looking for!
+ try
+ {
+ legacyID = m_pDS->fv("iClientId").get_asInt();
+ sql = PrepareSQL(
+ "REPLACE INTO clients (idClient, iPriority, sAddonID, iInstanceID) VALUES "
+ "(%i, %i, '%s', %i)",
+ legacyID, 0, addonID.c_str(), instanceID);
+ ExecuteQuery(sql);
+ }
+ catch (...)
+ {
+ CLog::LogF(LOGERROR, "Couldn't obtain providers for addon '{}'.", addonID);
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+int CPVRDatabase::GetClientID(const std::string& addonID, unsigned int instanceID)
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+
+ // Client id already present in clients table?
+ std::string sql{PrepareSQL("sAddonID = '%s' AND iInstanceID = %i", addonID.c_str(), instanceID)};
+ const std::string idString{GetSingleValue("clients", "idClient", sql)};
+ if (!idString.empty())
+ return std::atoi(idString.c_str());
+
+ // Create a new entry with a new client id in clients table.
+ // Priority and ChannelsAdded fields will be populated with real values later, on-demand.
+ sql = PrepareSQL("INSERT INTO clients (iPriority, sAddonID, iInstanceID, "
+ "sDateTimeFirstChannelsAdded) VALUES (%i, '%s', %i, '%s')",
+ 0, addonID.c_str(), instanceID, "");
+ if (ExecuteQuery(sql))
+ return static_cast<int>(m_pDS->lastinsertid());
+
+ return PVR_CLIENT_INVALID_UID;
+}
+
/********** Channel provider methods **********/
bool CPVRDatabase::DeleteProviders()
@@ -1149,7 +1306,8 @@ std::vector<std::shared_ptr<CPVRTimerInfoTag>> CPVRDatabase::GetTimers(
newTag->m_iClientIndex = -m_pDS->fv("iClientIndex").get_asInt();
newTag->m_iParentClientIndex = m_pDS->fv("iParentClientIndex").get_asInt();
newTag->m_iClientId = m_pDS->fv("iClientId").get_asInt();
- newTag->SetTimerType(CPVRTimerType::CreateFromIds(m_pDS->fv("iTimerType").get_asInt(), -1));
+ newTag->SetTimerType(CPVRTimerType::CreateFromIds(m_pDS->fv("iTimerType").get_asInt(),
+ PVR_CLIENT_INVALID_UID));
newTag->m_state = static_cast<PVR_TIMER_STATE>(m_pDS->fv("iState").get_asInt());
newTag->m_strTitle = m_pDS->fv("sTitle").get_asString().c_str();
newTag->m_iClientChannelUid = m_pDS->fv("iClientChannelUid").get_asInt();
diff --git a/xbmc/pvr/PVRDatabase.h b/xbmc/pvr/PVRDatabase.h
index 509360d5b6..93bd10c4ed 100644
--- a/xbmc/pvr/PVRDatabase.h
+++ b/xbmc/pvr/PVRDatabase.h
@@ -14,6 +14,8 @@
#include <map>
#include <vector>
+class CDateTime;
+
namespace PVR
{
class CPVRChannel;
@@ -64,7 +66,7 @@ namespace PVR
* @brief Get the minimal database version that is required to operate correctly.
* @return The minimal database version.
*/
- int GetSchemaVersion() const override { return 45; }
+ int GetSchemaVersion() const override { return 47; }
/*!
* @brief Get the default sqlite database filename.
@@ -102,6 +104,21 @@ namespace PVR
*/
int GetPriority(const CPVRClient& client) const;
+ /*!
+ * @brief Get the date and time first channels were added for the given client.
+ * @param client The client.
+ * @return The date and time first channels were added.
+ */
+ CDateTime GetDateTimeFirstChannelsAdded(const CPVRClient& client) const;
+
+ /*!
+ * @brief Get the numeric client ID for given addon ID and instance ID from the database.
+ * @param addonID The addon ID.
+ * @param instanceID The instance ID.
+ * @return The client ID on success, PVR_CLIENT_INVALID_UID otherwise.
+ */
+ int GetClientID(const std::string& addonID, unsigned int instanceID);
+
/*! @name Channel methods */
//@{
@@ -327,6 +344,8 @@ namespace PVR
bool RemoveChannelsFromGroup(const CPVRChannelGroup& group);
+ void FixupClientIDs();
+
mutable CCriticalSection m_critSection;
};
}
diff --git a/xbmc/pvr/PVRDescrambleInfo.h b/xbmc/pvr/PVRDescrambleInfo.h
index 83046909c0..ec8d5dacc6 100644
--- a/xbmc/pvr/PVRDescrambleInfo.h
+++ b/xbmc/pvr/PVRDescrambleInfo.h
@@ -27,7 +27,7 @@ public:
m_info.iHops = PVR_DESCRAMBLE_INFO_NOT_AVAILABLE;
}
- CPVRDescrambleInfo(const PVR_DESCRAMBLE_INFO& info)
+ explicit CPVRDescrambleInfo(const PVR_DESCRAMBLE_INFO& info)
: m_info(info),
m_cardSystem(info.strCardSystem ? info.strCardSystem : ""),
m_reader(info.strReader ? info.strReader : ""),
diff --git a/xbmc/pvr/PVRManager.cpp b/xbmc/pvr/PVRManager.cpp
index 47bc3a43fc..24ee777ad1 100644
--- a/xbmc/pvr/PVRManager.cpp
+++ b/xbmc/pvr/PVRManager.cpp
@@ -14,6 +14,7 @@
#include "interfaces/AnnouncementManager.h"
#include "messaging/ApplicationMessenger.h"
#include "pvr/PVRComponentRegistration.h"
+#include "pvr/PVRConstants.h" // PVR_CLIENT_INVALID_UID
#include "pvr/PVRDatabase.h"
#include "pvr/PVRPlaybackState.h"
#include "pvr/addons/PVRClient.h"
@@ -281,7 +282,7 @@ std::shared_ptr<CPVRClients> CPVRManager::Clients() const
std::shared_ptr<CPVRClient> CPVRManager::GetClient(const CFileItem& item) const
{
- int iClientID = PVR_INVALID_CLIENT_ID;
+ int iClientID = PVR_CLIENT_INVALID_UID;
if (item.HasPVRChannelInfoTag())
iClientID = item.GetPVRChannelInfoTag()->ClientID();
@@ -716,7 +717,7 @@ bool CPVRManager::UpdateComponents(ManagerState stateToCheck,
if (progressHandler)
progressHandler->UpdateProgress(g_localizeStrings.Get(19236), 0); // Loading channels and groups
- if (!m_providers->Update(newClients) || (stateToCheck != GetState()))
+ if (!m_providers->Update(newClients))
{
CLog::LogF(LOGERROR, "Failed to load PVR providers.");
m_knownClients.clear(); // start over
@@ -724,7 +725,10 @@ bool CPVRManager::UpdateComponents(ManagerState stateToCheck,
return false;
}
- if (!m_channelGroups->Update(newClients) || (stateToCheck != GetState()))
+ if (stateToCheck != GetState())
+ return false;
+
+ if (!m_channelGroups->Update(newClients))
{
CLog::LogF(LOGERROR, "Failed to load PVR channels / groups.");
m_knownClients.clear(); // start over
@@ -736,7 +740,7 @@ bool CPVRManager::UpdateComponents(ManagerState stateToCheck,
if (progressHandler)
progressHandler->UpdateProgress(g_localizeStrings.Get(19237), 50); // Loading timers
- if (!m_timers->Update(newClients) || (stateToCheck != GetState()))
+ if (!m_timers->Update(newClients))
{
CLog::LogF(LOGERROR, "Failed to load PVR timers.");
m_knownClients.clear(); // start over
@@ -748,7 +752,7 @@ bool CPVRManager::UpdateComponents(ManagerState stateToCheck,
if (progressHandler)
progressHandler->UpdateProgress(g_localizeStrings.Get(19238), 75); // Loading recordings
- if (!m_recordings->Update(newClients) || (stateToCheck != GetState()))
+ if (!m_recordings->Update(newClients))
{
CLog::LogF(LOGERROR, "Failed to load PVR recordings.");
m_knownClients.clear(); // start over
diff --git a/xbmc/pvr/PVRPathUtils.cpp b/xbmc/pvr/PVRPathUtils.cpp
new file mode 100644
index 0000000000..a529f7d274
--- /dev/null
+++ b/xbmc/pvr/PVRPathUtils.cpp
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2024 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "PVRPathUtils.h"
+
+#include "ServiceBroker.h"
+#include "URL.h"
+#include "addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_providers.h" // PVR_PROVIDER_INVALID_UID
+#include "pvr/PVRConstants.h" // PVR_CLIENT_INVALID_UID
+#include "pvr/PVRManager.h"
+#include "pvr/providers/PVRProvider.h"
+#include "pvr/providers/PVRProviders.h"
+#include "utils/StringUtils.h"
+
+#include <cstdlib>
+
+namespace PVR::UTILS
+{
+
+bool HasClientAndProvider(const std::string& path)
+{
+ const CURL url{path};
+ const std::string clientIdStr{url.GetOption("clientid")};
+ const std::string providerIdStr{url.GetOption("providerid")};
+ return (!clientIdStr.empty() && !providerIdStr.empty() && StringUtils::IsInteger(clientIdStr) &&
+ StringUtils::IsInteger(providerIdStr));
+}
+
+bool GetClientAndProviderFromPath(const CURL& url, int& clientId, int& providerId)
+{
+ const std::string clientIdStr{url.GetOption("clientid")};
+ const std::string providerIdStr{url.GetOption("providerid")};
+ const bool filterByClientAndProvider{!clientIdStr.empty() && !providerIdStr.empty() &&
+ StringUtils::IsInteger(clientIdStr) &&
+ StringUtils::IsInteger(providerIdStr)};
+ if (filterByClientAndProvider)
+ {
+ clientId = std::atoi(clientIdStr.c_str());
+ providerId = std::atoi(providerIdStr.c_str());
+ return true;
+ }
+ else
+ {
+ clientId = PVR_CLIENT_INVALID_UID;
+ providerId = PVR_PROVIDER_INVALID_UID;
+ return false;
+ }
+}
+
+std::string GetProviderNameFromPath(const std::string& path)
+{
+ const CURL url{path};
+ int clientId{PVR_CLIENT_INVALID_UID};
+ int providerId{PVR_PROVIDER_INVALID_UID};
+ if (GetClientAndProviderFromPath(url, clientId, providerId))
+ {
+ const std::shared_ptr<const CPVRProvider> provider{
+ CServiceBroker::GetPVRManager().Providers()->GetByClient(clientId, providerId)};
+ if (provider)
+ return provider->GetName();
+ }
+ return {};
+}
+
+} // namespace PVR::UTILS
diff --git a/xbmc/pvr/PVRPathUtils.h b/xbmc/pvr/PVRPathUtils.h
new file mode 100644
index 0000000000..3092aa5188
--- /dev/null
+++ b/xbmc/pvr/PVRPathUtils.h
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2024 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include <string>
+
+class CURL;
+
+namespace PVR::UTILS
+{
+/*!
+ * @brief Check whether the given path contains a client id and a provider id.
+ * @param path The path.
+ * @return True if both client id and provider id are present, false otherwise.
+ */
+bool HasClientAndProvider(const std::string& path);
+
+/*!
+ * @brief Get client id and provider id from the given URL.
+ * @param url The URL.
+ * @param clientId Filled with the client id on success, PVR_CLIENT_INVALID_UID otherwise
+ * @param providerId Filled with the provider id on success, PVR_PROVIDER_INVALID_UID otherwise
+ * @return True on success, false otherwise.
+ */
+bool GetClientAndProviderFromPath(const CURL& url, int& clientId, int& providerId);
+
+/*!
+ * @brief Get the name of a provider from the given path.
+ * @param path The path.
+ * @return the name on success, an empty string otherwise.
+ */
+std::string GetProviderNameFromPath(const std::string& path);
+
+} // namespace PVR::UTILS
diff --git a/xbmc/pvr/PVRPlaybackState.cpp b/xbmc/pvr/PVRPlaybackState.cpp
index 4879a119a6..e69ff0f983 100644
--- a/xbmc/pvr/PVRPlaybackState.cpp
+++ b/xbmc/pvr/PVRPlaybackState.cpp
@@ -69,7 +69,7 @@ void CPVRPlaybackState::ReInit()
Clear();
- if (m_playingClientId != -1)
+ if (m_playingClientId != PVR_CLIENT_INVALID_UID)
{
if (m_playingChannelUniqueId != -1)
{
@@ -123,7 +123,7 @@ void CPVRPlaybackState::ClearData()
m_strPlayingRecordingUniqueId.clear();
m_playingEpgTagChannelUniqueId = -1;
m_playingEpgTagUniqueId = 0;
- m_playingClientId = -1;
+ m_playingClientId = PVR_CLIENT_INVALID_UID;
m_strPlayingClientName.clear();
}
@@ -217,7 +217,7 @@ void CPVRPlaybackState::OnPlaybackStarted(const CFileItem& item)
CLog::LogFC(LOGERROR, LOGPVR, "Channel item without channel group member!");
}
- if (m_playingClientId != -1)
+ if (m_playingClientId != PVR_CLIENT_INVALID_UID)
{
const std::shared_ptr<const CPVRClient> client =
CServiceBroker::GetPVRManager().GetClient(m_playingClientId);
@@ -341,14 +341,15 @@ bool CPVRPlaybackState::OnPlaybackEnded(const CFileItem& item)
std::unique_ptr<CFileItem> nextToPlay{GetNextAutoplayItem(item)};
if (nextToPlay)
- StartPlayback(nextToPlay.release());
+ StartPlayback(nextToPlay, ContentUtils::PlayMode::CHECK_AUTO_PLAY_NEXT_ITEM,
+ PVR_SOURCE::DEFAULT);
return OnPlaybackStopped(item);
}
-void CPVRPlaybackState::StartPlayback(
- CFileItem* item,
- ContentUtils::PlayMode mode /* = ContentUtils::PlayMode::CHECK_AUTO_PLAY_NEXT_ITEM */) const
+void CPVRPlaybackState::StartPlayback(std::unique_ptr<CFileItem>& item,
+ ContentUtils::PlayMode mode,
+ PVR_SOURCE source) const
{
// Obtain dynamic playback url and properties from the respective pvr client
const std::shared_ptr<const CPVRClient> client = CServiceBroker::GetPVRManager().GetClient(*item);
@@ -358,7 +359,24 @@ void CPVRPlaybackState::StartPlayback(
if (item->IsPVRChannel())
{
- client->GetChannelStreamProperties(item->GetPVRChannelInfoTag(), props);
+ if (source == PVR_SOURCE::DEFAULT)
+ {
+ PVR_ERROR retVal = client->StreamClosed();
+ if (retVal != PVR_ERROR_NO_ERROR)
+ CLog::LogFC(LOGERROR, LOGPVR, "Client error on call to StreamClosed(): {}",
+ CPVRClient::ToString(retVal));
+ }
+
+ client->GetChannelStreamProperties(item->GetPVRChannelInfoTag(), source, props);
+
+ if (props.LivePlaybackAsEPG())
+ {
+ const std::shared_ptr<CPVREpgInfoTag> epgTag = item->GetPVRChannelInfoTag()->GetEPGNow();
+ if (epgTag)
+ {
+ item = std::make_unique<CFileItem>(epgTag);
+ }
+ }
}
else if (item->IsPVRRecording())
{
@@ -366,6 +384,11 @@ void CPVRPlaybackState::StartPlayback(
}
else if (item->IsEPG())
{
+ PVR_ERROR retVal = client->StreamClosed();
+ if (retVal != PVR_ERROR_NO_ERROR)
+ CLog::LogFC(LOGERROR, LOGPVR, "Client error on call to StreamClosed(): {}",
+ CPVRClient::ToString(retVal));
+
client->GetEpgTagStreamProperties(item->GetEPGInfoTag(), props);
if (mode == ContentUtils::PlayMode::CHECK_AUTO_PLAY_NEXT_ITEM)
@@ -398,7 +421,8 @@ void CPVRPlaybackState::StartPlayback(
}
}
- CServiceBroker::GetAppMessenger()->PostMsg(TMSG_MEDIA_PLAY, 0, 0, static_cast<void*>(item));
+ CServiceBroker::GetAppMessenger()->PostMsg(TMSG_MEDIA_PLAY, 0, 0,
+ static_cast<void*>(item.release()));
}
bool CPVRPlaybackState::IsPlaying() const
@@ -630,7 +654,8 @@ CDateTime CPVRPlaybackState::GetPlaybackTime(int iClientID, int iUniqueChannelID
{
// playing an epg tag on requested channel
return epgTag->StartAsUTC() +
- CDateTimeSpan(0, 0, 0, CServiceBroker::GetDataCacheCore().GetPlayTime() / 1000);
+ CDateTimeSpan(0, 0, 0,
+ static_cast<int>(CServiceBroker::GetDataCacheCore().GetPlayTime()) / 1000);
}
// not playing / playing live / playing timeshifted
diff --git a/xbmc/pvr/PVRPlaybackState.h b/xbmc/pvr/PVRPlaybackState.h
index 63a721c124..3e41c130eb 100644
--- a/xbmc/pvr/PVRPlaybackState.h
+++ b/xbmc/pvr/PVRPlaybackState.h
@@ -8,6 +8,8 @@
#pragma once
+#include "addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_general.h"
+#include "pvr/PVRConstants.h" // PVR_CLIENT_INVALID_UID
#include "threads/CriticalSection.h"
#include "utils/ContentUtils.h"
@@ -74,9 +76,9 @@ public:
* @param item containing a channel, a recording or an epg tag.
* @param mode playback mode.
*/
- void StartPlayback(
- CFileItem* item,
- ContentUtils::PlayMode mode = ContentUtils::PlayMode::CHECK_AUTO_PLAY_NEXT_ITEM) const;
+ void StartPlayback(std::unique_ptr<CFileItem>& item,
+ ContentUtils::PlayMode mode,
+ PVR_SOURCE source) const;
/*!
* @brief Check if a TV channel, radio channel or recording is playing.
@@ -181,7 +183,7 @@ public:
/*!
* @brief Get the ID of the playing client, if there is one.
- * @return The ID or -1 if no client is playing.
+ * @return The ID or PVR_CLIENT_INVALID_UID if no client is playing.
*/
int GetPlayingClientID() const;
@@ -289,7 +291,7 @@ private:
std::shared_ptr<CPVRChannelGroupMember> m_previousToLastPlayedChannelRadio;
std::string m_strPlayingClientName;
int m_playingGroupId = -1;
- int m_playingClientId = -1;
+ int m_playingClientId = PVR_CLIENT_INVALID_UID;
int m_playingChannelUniqueId = -1;
std::string m_strPlayingRecordingUniqueId;
int m_playingEpgTagChannelUniqueId = -1;
diff --git a/xbmc/pvr/PVRSignalStatus.h b/xbmc/pvr/PVRSignalStatus.h
index bd17d33757..81aac9ea8b 100644
--- a/xbmc/pvr/PVRSignalStatus.h
+++ b/xbmc/pvr/PVRSignalStatus.h
@@ -25,7 +25,7 @@ public:
{
}
- CPVRSignalStatus(const PVR_SIGNAL_STATUS& status)
+ explicit CPVRSignalStatus(const PVR_SIGNAL_STATUS& status)
: m_status(status),
m_adapterName(status.strAdapterName ? status.strAdapterName : ""),
m_adapterStatus(status.strAdapterStatus ? status.strAdapterStatus : ""),
diff --git a/xbmc/pvr/PVRStreamProperties.cpp b/xbmc/pvr/PVRStreamProperties.cpp
index 272b33aa25..c5bcaa10ee 100644
--- a/xbmc/pvr/PVRStreamProperties.cpp
+++ b/xbmc/pvr/PVRStreamProperties.cpp
@@ -38,3 +38,11 @@ bool CPVRStreamProperties::EPGPlaybackAsLive() const
});
return it != cend() ? StringUtils::EqualsNoCase((*it).second, "true") : false;
}
+
+bool CPVRStreamProperties::LivePlaybackAsEPG() const
+{
+ const auto it = std::find_if(cbegin(), cend(),
+ [](const auto& prop)
+ { return prop.first == PVR_STREAM_PROPERTY_LIVEPLAYBACKASEPG; });
+ return it != cend() ? StringUtils::EqualsNoCase((*it).second, "true") : false;
+}
diff --git a/xbmc/pvr/PVRStreamProperties.h b/xbmc/pvr/PVRStreamProperties.h
index 6690660601..34430db231 100644
--- a/xbmc/pvr/PVRStreamProperties.h
+++ b/xbmc/pvr/PVRStreamProperties.h
@@ -38,6 +38,12 @@ public:
* @return true if it should be played back as live, false otherwise.
*/
bool EPGPlaybackAsLive() const;
+
+ /*!
+ * @brief If props are from an channel indicates if playback should be as a video playback would be
+ * @return true if it should be played back as live, false otherwise.
+ */
+ bool LivePlaybackAsEPG() const;
};
} // namespace PVR
diff --git a/xbmc/pvr/addons/PVRClient.cpp b/xbmc/pvr/addons/PVRClient.cpp
index e5cada8e31..8d56392567 100644
--- a/xbmc/pvr/addons/PVRClient.cpp
+++ b/xbmc/pvr/addons/PVRClient.cpp
@@ -10,6 +10,7 @@
#include "ServiceBroker.h"
#include "addons/AddonManager.h"
+#include "addons/AddonVersion.h"
#include "addons/binary-addons/AddonDll.h"
#include "cores/EdlEdit.h"
#include "cores/VideoPlayer/DVDDemuxers/DVDDemuxUtils.h"
@@ -18,6 +19,7 @@
#include "events/NotificationEvent.h"
#include "filesystem/SpecialProtocol.h"
#include "guilib/LocalizeStrings.h"
+#include "pvr/PVRConstants.h" // PVR_CLIENT_INVALID_UID
#include "pvr/PVRDatabase.h"
#include "pvr/PVRDescrambleInfo.h"
#include "pvr/PVRManager.h"
@@ -68,6 +70,10 @@ class CAddonChannelGroup : public PVR_CHANNEL_GROUP
public:
explicit CAddonChannelGroup(const CPVRChannelGroup& group) : m_groupName(group.ClientGroupName())
{
+ // zero-init base struct members
+ PVR_CHANNEL_GROUP* base = static_cast<PVR_CHANNEL_GROUP*>(this);
+ *base = {};
+
bIsRadio = group.IsRadio();
strGroupName = m_groupName.c_str();
iPosition = group.GetClientPosition();
@@ -81,11 +87,15 @@ private:
class CAddonChannel : public PVR_CHANNEL
{
public:
- CAddonChannel(const CPVRChannel& channel, const std::string& newChannelName = "")
+ explicit CAddonChannel(const CPVRChannel& channel, const std::string& newChannelName = "")
: m_channelName(newChannelName.empty() ? channel.ClientChannelName() : newChannelName),
m_mimeType(channel.MimeType()),
m_iconPath(channel.ClientIconPath())
{
+ // zero-init base struct members
+ PVR_CHANNEL* base = static_cast<PVR_CHANNEL*>(this);
+ *base = {};
+
iUniqueId = channel.UniqueID();
iChannelNumber = channel.ClientChannelNumber().GetChannelNumber();
iSubChannelNumber = channel.ClientChannelNumber().GetSubChannelNumber();
@@ -122,8 +132,15 @@ public:
m_thumbnailPath(recording.ClientThumbnailPath()),
m_fanartPath(recording.ClientFanartPath()),
m_firstAired(recording.FirstAired().IsValid() ? recording.FirstAired().GetAsW3CDate() : ""),
- m_providerName(recording.ProviderName())
+ m_providerName(recording.ProviderName()),
+ m_parentalRatingCode(""), //! @todo
+ m_parentalRatingIcon(""), //! @todo
+ m_parentalRatingSource("") //! @todo
{
+ // zero-init base struct members
+ PVR_RECORDING* base = static_cast<PVR_RECORDING*>(this);
+ *base = {};
+
time_t recTime;
recording.RecordingTimeAsUTC().GetAsTime(recTime);
@@ -132,6 +149,7 @@ public:
strEpisodeName = m_episodeName.c_str();
iSeriesNumber = recording.m_iSeason;
iEpisodeNumber = recording.m_iEpisode;
+ iEpisodePartNumber = recording.EpisodePart();
iYear = recording.GetYear();
strDirectory = m_directory.c_str();
strPlotOutline = m_plotOutline.c_str();
@@ -150,7 +168,8 @@ public:
iGenreType = recording.GenreType();
iGenreSubType = recording.GenreSubType();
iPlayCount = recording.GetLocalPlayCount();
- iLastPlayedPosition = std::lrint(recording.GetLocalResumePoint().timeInSeconds);
+ iLastPlayedPosition =
+ static_cast<int>(std::lrint(recording.GetLocalResumePoint().timeInSeconds));
bIsDeleted = recording.IsDeleted();
iEpgEventId = recording.BroadcastUid();
iChannelUid = recording.ChannelUid();
@@ -160,7 +179,10 @@ public:
iFlags = recording.Flags();
sizeInBytes = recording.GetSizeInBytes();
strProviderName = m_providerName.c_str();
- iClientProviderUid = recording.ClientProviderUniqueId();
+ iClientProviderUid = recording.ClientProviderUid();
+ strParentalRatingCode = m_parentalRatingCode.c_str();
+ strParentalRatingIcon = m_parentalRatingIcon.c_str();
+ strParentalRatingSource = m_parentalRatingSource.c_str();
}
virtual ~CAddonRecording() = default;
@@ -178,6 +200,9 @@ private:
const std::string m_fanartPath;
const std::string m_firstAired;
const std::string m_providerName;
+ const std::string m_parentalRatingCode;
+ const std::string m_parentalRatingIcon;
+ const std::string m_parentalRatingSource;
};
class CAddonTimer : public PVR_TIMER
@@ -190,12 +215,16 @@ public:
m_summary(timer.Summary()),
m_seriesLink(timer.SeriesLink())
{
+ // zero-init base struct members
+ PVR_TIMER* base = static_cast<PVR_TIMER*>(this);
+ *base = {};
+
time_t start;
timer.StartAsUTC().GetAsTime(start);
time_t end;
timer.EndAsUTC().GetAsTime(end);
- time_t firstDay;
- timer.FirstDayAsUTC().GetAsTime(firstDay);
+ time_t first;
+ timer.FirstDayAsUTC().GetAsTime(first);
const std::shared_ptr<const CPVREpgInfoTag> epgTag{timer.GetEpgInfoTag()};
const int timeCorrection{
CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_iPVRTimeCorrection};
@@ -219,7 +248,7 @@ public:
endTime = end - timeCorrection;
bStartAnyTime = timer.IsStartAnyTime();
bEndAnyTime = timer.IsEndAnyTime();
- firstDay = firstDay - timeCorrection;
+ firstDay = first - timeCorrection;
iEpgUid = epgTag ? epgTag->UniqueBroadcastID() : PVR_TIMER_NO_EPG_UID;
strSummary = m_summary.c_str();
iMarginStart = timer.MarginStart();
@@ -227,6 +256,24 @@ public:
iGenreType = epgTag ? epgTag->GenreType() : 0;
iGenreSubType = epgTag ? epgTag->GenreSubType() : 0;
strSeriesLink = m_seriesLink.c_str();
+
+ const auto& props{timer.GetCustomProperties()};
+ iCustomPropsSize = static_cast<unsigned int>(props.size());
+ if (iCustomPropsSize)
+ {
+ m_customProps = std::make_unique<PVR_SETTING_KEY_VALUE_PAIR[]>(iCustomPropsSize);
+ int idx{0};
+ for (const auto& entry : props)
+ {
+ PVR_SETTING_KEY_VALUE_PAIR& prop{m_customProps[idx]};
+ prop.iKey = entry.first;
+ prop.eType = entry.second.type;
+ prop.iValue = entry.second.value.asInteger32();
+ prop.strValue = entry.second.value.asString().c_str();
+ ++idx;
+ }
+ customProps = m_customProps.get();
+ }
}
virtual ~CAddonTimer() = default;
@@ -236,6 +283,96 @@ private:
const std::string m_directory;
const std::string m_summary;
const std::string m_seriesLink;
+ std::unique_ptr<PVR_SETTING_KEY_VALUE_PAIR[]> m_customProps;
+};
+
+class CAddonEpgTag : public EPG_TAG
+{
+public:
+ explicit CAddonEpgTag(const CPVREpgInfoTag& tag)
+ : m_title(tag.Title()),
+ m_plotOutline(tag.PlotOutline()),
+ m_plot(tag.Plot()),
+ m_originalTitle(tag.OriginalTitle()),
+ m_cast(tag.DeTokenize(tag.Cast())),
+ m_director(tag.DeTokenize(tag.Directors())),
+ m_writer(tag.DeTokenize(tag.Writers())),
+ m_IMDBNumber(tag.IMDBNumber()),
+ m_episodeName(tag.EpisodeName()),
+ m_iconPath(tag.ClientIconPath()),
+ m_seriesLink(tag.SeriesLink()),
+ m_genreDescription(tag.GenreDescription()),
+ m_firstAired(GetFirstAired(tag)),
+ m_parentalRatingCode(tag.ParentalRatingCode()),
+ m_parentalRatingIcon(""), //! @todo
+ m_parentalRatingSource("") //! @todo
+ {
+ // zero-init base struct members
+ EPG_TAG* base = static_cast<EPG_TAG*>(this);
+ *base = {};
+
+ time_t t;
+ tag.StartAsUTC().GetAsTime(t);
+ startTime = t;
+ tag.EndAsUTC().GetAsTime(t);
+ endTime = t;
+
+ iUniqueBroadcastId = tag.UniqueBroadcastID();
+ iUniqueChannelId = tag.UniqueChannelID();
+ iParentalRating = tag.ParentalRating();
+ iSeriesNumber = tag.SeriesNumber();
+ iEpisodeNumber = tag.EpisodeNumber();
+ iEpisodePartNumber = tag.EpisodePart();
+ iStarRating = tag.StarRating();
+ iYear = tag.Year();
+ iFlags = tag.Flags();
+ iGenreType = tag.GenreType();
+ iGenreSubType = tag.GenreSubType();
+ strTitle = m_title.c_str();
+ strPlotOutline = m_plotOutline.c_str();
+ strPlot = m_plot.c_str();
+ strOriginalTitle = m_originalTitle.c_str();
+ strCast = m_cast.c_str();
+ strDirector = m_director.c_str();
+ strWriter = m_writer.c_str();
+ strIMDBNumber = m_IMDBNumber.c_str();
+ strEpisodeName = m_episodeName.c_str();
+ strIconPath = m_iconPath.c_str();
+ strSeriesLink = m_seriesLink.c_str();
+ strGenreDescription = m_genreDescription.c_str();
+ strFirstAired = m_firstAired.c_str();
+ strParentalRatingCode = m_parentalRatingCode.c_str();
+ strParentalRatingIcon = m_parentalRatingIcon.c_str();
+ strParentalRatingSource = m_parentalRatingSource.c_str();
+ }
+
+ virtual ~CAddonEpgTag() = default;
+
+private:
+ static std::string GetFirstAired(const CPVREpgInfoTag& tag)
+ {
+ const CDateTime firstAired{tag.FirstAired()};
+ if (firstAired.IsValid())
+ return firstAired.GetAsW3CDate();
+ return {};
+ }
+
+ const std::string m_title;
+ const std::string m_plotOutline;
+ const std::string m_plot;
+ const std::string m_originalTitle;
+ const std::string m_cast;
+ const std::string m_director;
+ const std::string m_writer;
+ const std::string m_IMDBNumber;
+ const std::string m_episodeName;
+ const std::string m_iconPath;
+ const std::string m_seriesLink;
+ const std::string m_genreDescription;
+ const std::string m_firstAired;
+ const std::string m_parentalRatingCode;
+ const std::string m_parentalRatingIcon;
+ const std::string m_parentalRatingSource;
};
EDL::Edit ConvertAddonEdl(const PVR_EDL_ENTRY& entry)
@@ -520,9 +657,8 @@ bool CPVRClient::GetAddonProperties()
/* get the capabilities */
PVR_ERROR retVal = DoAddonCall(
__func__,
- [&addonCapabilities](const AddonInstance* addon) {
- return addon->toAddon->GetCapabilities(addon, &addonCapabilities);
- },
+ [&addonCapabilities](const AddonInstance* addon)
+ { return addon->toAddon->GetCapabilities(addon, &addonCapabilities); },
true, false);
if (retVal == PVR_ERROR_NO_ERROR)
@@ -678,17 +814,20 @@ PVR_ERROR CPVRClient::GetDriveSpace(uint64_t& iTotal, uint64_t& iUsed) const
iTotal = 0;
iUsed = 0;
- return DoAddonCall(__func__, [&iTotal, &iUsed](const AddonInstance* addon) {
- uint64_t iTotalSpace = 0;
- uint64_t iUsedSpace = 0;
- PVR_ERROR error = addon->toAddon->GetDriveSpace(addon, &iTotalSpace, &iUsedSpace);
- if (error == PVR_ERROR_NO_ERROR)
- {
- iTotal = iTotalSpace;
- iUsed = iUsedSpace;
- }
- return error;
- });
+ return DoAddonCall(__func__,
+ [&iTotal, &iUsed](const AddonInstance* addon)
+ {
+ uint64_t iTotalSpace = 0;
+ uint64_t iUsedSpace = 0;
+ PVR_ERROR error =
+ addon->toAddon->GetDriveSpace(addon, &iTotalSpace, &iUsedSpace);
+ if (error == PVR_ERROR_NO_ERROR)
+ {
+ iTotal = iTotalSpace;
+ iUsed = iUsedSpace;
+ }
+ return error;
+ });
}
PVR_ERROR CPVRClient::StartChannelScan()
@@ -754,7 +893,8 @@ PVR_ERROR CPVRClient::GetEPGForChannel(int iChannelUid,
{
return DoAddonCall(
__func__,
- [this, iChannelUid, epg, start, end](const AddonInstance* addon) {
+ [this, iChannelUid, epg, start, end](const AddonInstance* addon)
+ {
PVR_HANDLE_STRUCT handle = {};
handle.callerAddress = this;
handle.dataAddress = epg;
@@ -773,9 +913,8 @@ PVR_ERROR CPVRClient::SetEPGMaxPastDays(int iPastDays)
{
return DoAddonCall(
__func__,
- [iPastDays](const AddonInstance* addon) {
- return addon->toAddon->SetEPGMaxPastDays(addon, iPastDays);
- },
+ [iPastDays](const AddonInstance* addon)
+ { return addon->toAddon->SetEPGMaxPastDays(addon, iPastDays); },
m_clientCapabilities.SupportsEPG());
}
@@ -783,100 +922,19 @@ PVR_ERROR CPVRClient::SetEPGMaxFutureDays(int iFutureDays)
{
return DoAddonCall(
__func__,
- [iFutureDays](const AddonInstance* addon) {
- return addon->toAddon->SetEPGMaxFutureDays(addon, iFutureDays);
- },
+ [iFutureDays](const AddonInstance* addon)
+ { return addon->toAddon->SetEPGMaxFutureDays(addon, iFutureDays); },
m_clientCapabilities.SupportsEPG());
}
-// This class wraps an EPG_TAG (PVR Addon API struct) to ensure that the string members of
-// that struct, which are const char pointers, stay valid until the EPG_TAG gets destructed.
-// Please note that this struct is also used to transfer huge amount of EPG_TAGs from
-// addon to Kodi. Thus, changing the struct to contain char arrays is not recommended,
-// because this would lead to huge amount of string copies when transferring epg data
-// from addon to Kodi.
-class CAddonEpgTag : public EPG_TAG
-{
-public:
- CAddonEpgTag() = delete;
- explicit CAddonEpgTag(const std::shared_ptr<const CPVREpgInfoTag>& kodiTag)
- : m_strTitle(kodiTag->Title()),
- m_strPlotOutline(kodiTag->PlotOutline()),
- m_strPlot(kodiTag->Plot()),
- m_strOriginalTitle(kodiTag->OriginalTitle()),
- m_strCast(kodiTag->DeTokenize(kodiTag->Cast())),
- m_strDirector(kodiTag->DeTokenize(kodiTag->Directors())),
- m_strWriter(kodiTag->DeTokenize(kodiTag->Writers())),
- m_strIMDBNumber(kodiTag->IMDBNumber()),
- m_strEpisodeName(kodiTag->EpisodeName()),
- m_strIconPath(kodiTag->ClientIconPath()),
- m_strSeriesLink(kodiTag->SeriesLink()),
- m_strGenreDescription(kodiTag->GenreDescription()),
- m_strParentalRatingCode(kodiTag->ParentalRatingCode())
- {
- time_t t;
- kodiTag->StartAsUTC().GetAsTime(t);
- startTime = t;
- kodiTag->EndAsUTC().GetAsTime(t);
- endTime = t;
-
- const CDateTime firstAired = kodiTag->FirstAired();
- if (firstAired.IsValid())
- m_strFirstAired = firstAired.GetAsW3CDate();
-
- iUniqueBroadcastId = kodiTag->UniqueBroadcastID();
- iUniqueChannelId = kodiTag->UniqueChannelID();
- iParentalRating = kodiTag->ParentalRating();
- iSeriesNumber = kodiTag->SeriesNumber();
- iEpisodeNumber = kodiTag->EpisodeNumber();
- iEpisodePartNumber = kodiTag->EpisodePart();
- iStarRating = kodiTag->StarRating();
- iYear = kodiTag->Year();
- iFlags = kodiTag->Flags();
- iGenreType = kodiTag->GenreType();
- iGenreSubType = kodiTag->GenreSubType();
- strTitle = m_strTitle.c_str();
- strPlotOutline = m_strPlotOutline.c_str();
- strPlot = m_strPlot.c_str();
- strOriginalTitle = m_strOriginalTitle.c_str();
- strCast = m_strCast.c_str();
- strDirector = m_strDirector.c_str();
- strWriter = m_strWriter.c_str();
- strIMDBNumber = m_strIMDBNumber.c_str();
- strEpisodeName = m_strEpisodeName.c_str();
- strIconPath = m_strIconPath.c_str();
- strSeriesLink = m_strSeriesLink.c_str();
- strGenreDescription = m_strGenreDescription.c_str();
- strFirstAired = m_strFirstAired.c_str();
- strParentalRatingCode = m_strParentalRatingCode.c_str();
- }
-
- virtual ~CAddonEpgTag() = default;
-
-private:
- std::string m_strTitle;
- std::string m_strPlotOutline;
- std::string m_strPlot;
- std::string m_strOriginalTitle;
- std::string m_strCast;
- std::string m_strDirector;
- std::string m_strWriter;
- std::string m_strIMDBNumber;
- std::string m_strEpisodeName;
- std::string m_strIconPath;
- std::string m_strSeriesLink;
- std::string m_strGenreDescription;
- std::string m_strFirstAired;
- std::string m_strParentalRatingCode;
-};
-
PVR_ERROR CPVRClient::IsRecordable(const std::shared_ptr<const CPVREpgInfoTag>& tag,
bool& bIsRecordable) const
{
return DoAddonCall(
__func__,
- [tag, &bIsRecordable](const AddonInstance* addon) {
- CAddonEpgTag addonTag(tag);
+ [tag, &bIsRecordable](const AddonInstance* addon)
+ {
+ CAddonEpgTag addonTag(*tag);
return addon->toAddon->IsEPGTagRecordable(addon, &addonTag, &bIsRecordable);
},
m_clientCapabilities.SupportsRecordings() && m_clientCapabilities.SupportsEPG());
@@ -887,8 +945,9 @@ PVR_ERROR CPVRClient::IsPlayable(const std::shared_ptr<const CPVREpgInfoTag>& ta
{
return DoAddonCall(
__func__,
- [tag, &bIsPlayable](const AddonInstance* addon) {
- CAddonEpgTag addonTag(tag);
+ [tag, &bIsPlayable](const AddonInstance* addon)
+ {
+ CAddonEpgTag addonTag(*tag);
return addon->toAddon->IsEPGTagPlayable(addon, &addonTag, &bIsPlayable);
},
m_clientCapabilities.SupportsEPG());
@@ -908,19 +967,21 @@ void CPVRClient::WriteStreamProperties(PVR_NAMED_VALUE** properties,
PVR_ERROR CPVRClient::GetEpgTagStreamProperties(const std::shared_ptr<const CPVREpgInfoTag>& tag,
CPVRStreamProperties& props) const
{
- return DoAddonCall(__func__, [&tag, &props](const AddonInstance* addon) {
- CAddonEpgTag addonTag(tag);
+ return DoAddonCall(__func__,
+ [&tag, &props](const AddonInstance* addon)
+ {
+ CAddonEpgTag addonTag(*tag);
- PVR_NAMED_VALUE** property_array{nullptr};
- unsigned int size{0};
- const PVR_ERROR error{
- addon->toAddon->GetEPGTagStreamProperties(addon, &addonTag, &property_array, &size)};
- if (error == PVR_ERROR_NO_ERROR)
- WriteStreamProperties(property_array, size, props);
+ PVR_NAMED_VALUE** property_array{nullptr};
+ unsigned int size{0};
+ const PVR_ERROR error{addon->toAddon->GetEPGTagStreamProperties(
+ addon, &addonTag, &property_array, &size)};
+ if (error == PVR_ERROR_NO_ERROR)
+ WriteStreamProperties(property_array, size, props);
- addon->toAddon->FreeProperties(addon, property_array, size);
- return error;
- });
+ addon->toAddon->FreeProperties(addon, property_array, size);
+ return error;
+ });
}
PVR_ERROR CPVRClient::GetEpgTagEdl(const std::shared_ptr<const CPVREpgInfoTag>& epgTag,
@@ -929,8 +990,9 @@ PVR_ERROR CPVRClient::GetEpgTagEdl(const std::shared_ptr<const CPVREpgInfoTag>&
edls.clear();
return DoAddonCall(
__func__,
- [&epgTag, &edls](const AddonInstance* addon) {
- CAddonEpgTag addonTag(epgTag);
+ [&epgTag, &edls](const AddonInstance* addon)
+ {
+ CAddonEpgTag addonTag(*epgTag);
PVR_EDL_ENTRY** edl_array{nullptr};
unsigned int size{0};
@@ -952,9 +1014,8 @@ PVR_ERROR CPVRClient::GetChannelGroupsAmount(int& iGroups) const
iGroups = -1;
return DoAddonCall(
__func__,
- [&iGroups](const AddonInstance* addon) {
- return addon->toAddon->GetChannelGroupsAmount(addon, &iGroups);
- },
+ [&iGroups](const AddonInstance* addon)
+ { return addon->toAddon->GetChannelGroupsAmount(addon, &iGroups); },
m_clientCapabilities.SupportsChannelGroups());
}
@@ -999,43 +1060,58 @@ PVR_ERROR CPVRClient::GetChannelGroupMembers(
PVR_ERROR CPVRClient::GetProvidersAmount(int& iProviders) const
{
iProviders = -1;
- return DoAddonCall(__func__, [&iProviders](const AddonInstance* addon) {
- return addon->toAddon->GetProvidersAmount(addon, &iProviders);
- });
+ return DoAddonCall(__func__, [&iProviders](const AddonInstance* addon)
+ { return addon->toAddon->GetProvidersAmount(addon, &iProviders); });
}
PVR_ERROR CPVRClient::GetProviders(CPVRProvidersContainer& providers) const
{
- return DoAddonCall(__func__,
- [this, &providers](const AddonInstance* addon) {
- PVR_HANDLE_STRUCT handle = {};
- handle.callerAddress = this;
- handle.dataAddress = &providers;
- return addon->toAddon->GetProviders(addon, &handle);
- },
- m_clientCapabilities.SupportsProviders());
+ return DoAddonCall(
+ __func__,
+ [this, &providers](const AddonInstance* addon)
+ {
+ PVR_HANDLE_STRUCT handle = {};
+ handle.callerAddress = this;
+ handle.dataAddress = &providers;
+ return addon->toAddon->GetProviders(addon, &handle);
+ },
+ m_clientCapabilities.SupportsProviders());
}
PVR_ERROR CPVRClient::GetChannelsAmount(int& iChannels) const
{
iChannels = -1;
- return DoAddonCall(__func__, [&iChannels](const AddonInstance* addon) {
- return addon->toAddon->GetChannelsAmount(addon, &iChannels);
- });
+ return DoAddonCall(__func__, [&iChannels](const AddonInstance* addon)
+ { return addon->toAddon->GetChannelsAmount(addon, &iChannels); });
}
PVR_ERROR CPVRClient::GetChannels(bool radio,
std::vector<std::shared_ptr<CPVRChannel>>& channels) const
{
- return DoAddonCall(__func__,
- [this, radio, &channels](const AddonInstance* addon) {
- PVR_HANDLE_STRUCT handle = {};
- handle.callerAddress = this;
- handle.dataAddress = &channels;
- return addon->toAddon->GetChannels(addon, &handle, radio);
- },
- (radio && m_clientCapabilities.SupportsRadio()) ||
- (!radio && m_clientCapabilities.SupportsTV()));
+ return DoAddonCall(
+ __func__,
+ [this, radio, &channels](const AddonInstance* addon)
+ {
+ PVR_HANDLE_STRUCT handle = {};
+ handle.callerAddress = this;
+ handle.dataAddress = &channels;
+ const PVR_ERROR error{addon->toAddon->GetChannels(addon, &handle, radio)};
+
+ if (error == PVR_ERROR_NO_ERROR)
+ {
+ const CDateTime& dateTime{GetDateTimeFirstChannelsAdded()};
+ if (!dateTime.IsValid())
+ {
+ // Remember when first channels were added for this client.
+ const_cast<CPVRClient*>(this)->SetDateTimeFirstChannelsAdded(
+ CDateTime::GetUTCDateTime());
+ }
+ }
+
+ return error;
+ },
+ (radio && m_clientCapabilities.SupportsRadio()) ||
+ (!radio && m_clientCapabilities.SupportsTV()));
}
PVR_ERROR CPVRClient::GetRecordingsAmount(bool deleted, int& iRecordings) const
@@ -1043,24 +1119,25 @@ PVR_ERROR CPVRClient::GetRecordingsAmount(bool deleted, int& iRecordings) const
iRecordings = -1;
return DoAddonCall(
__func__,
- [deleted, &iRecordings](const AddonInstance* addon) {
- return addon->toAddon->GetRecordingsAmount(addon, deleted, &iRecordings);
- },
+ [deleted, &iRecordings](const AddonInstance* addon)
+ { return addon->toAddon->GetRecordingsAmount(addon, deleted, &iRecordings); },
m_clientCapabilities.SupportsRecordings() &&
(!deleted || m_clientCapabilities.SupportsRecordingsUndelete()));
}
PVR_ERROR CPVRClient::GetRecordings(CPVRRecordings* results, bool deleted) const
{
- return DoAddonCall(__func__,
- [this, results, deleted](const AddonInstance* addon) {
- PVR_HANDLE_STRUCT handle = {};
- handle.callerAddress = this;
- handle.dataAddress = results;
- return addon->toAddon->GetRecordings(addon, &handle, deleted);
- },
- m_clientCapabilities.SupportsRecordings() &&
- (!deleted || m_clientCapabilities.SupportsRecordingsUndelete()));
+ return DoAddonCall(
+ __func__,
+ [this, results, deleted](const AddonInstance* addon)
+ {
+ PVR_HANDLE_STRUCT handle = {};
+ handle.callerAddress = this;
+ handle.dataAddress = results;
+ return addon->toAddon->GetRecordings(addon, &handle, deleted);
+ },
+ m_clientCapabilities.SupportsRecordings() &&
+ (!deleted || m_clientCapabilities.SupportsRecordingsUndelete()));
}
PVR_ERROR CPVRClient::DeleteRecording(const CPVRRecording& recording)
@@ -1091,9 +1168,8 @@ PVR_ERROR CPVRClient::DeleteAllRecordingsFromTrash()
{
return DoAddonCall(
__func__,
- [](const AddonInstance* addon) {
- return addon->toAddon->DeleteAllRecordingsFromTrash(addon);
- },
+ [](const AddonInstance* addon)
+ { return addon->toAddon->DeleteAllRecordingsFromTrash(addon); },
m_clientCapabilities.SupportsRecordingsUndelete());
}
@@ -1202,22 +1278,23 @@ PVR_ERROR CPVRClient::GetTimersAmount(int& iTimers) const
iTimers = -1;
return DoAddonCall(
__func__,
- [&iTimers](const AddonInstance* addon) {
- return addon->toAddon->GetTimersAmount(addon, &iTimers);
- },
+ [&iTimers](const AddonInstance* addon)
+ { return addon->toAddon->GetTimersAmount(addon, &iTimers); },
m_clientCapabilities.SupportsTimers());
}
PVR_ERROR CPVRClient::GetTimers(CPVRTimersContainer* results) const
{
- return DoAddonCall(__func__,
- [this, results](const AddonInstance* addon) {
- PVR_HANDLE_STRUCT handle = {};
- handle.callerAddress = this;
- handle.dataAddress = results;
- return addon->toAddon->GetTimers(addon, &handle);
- },
- m_clientCapabilities.SupportsTimers());
+ return DoAddonCall(
+ __func__,
+ [this, results](const AddonInstance* addon)
+ {
+ PVR_HANDLE_STRUCT handle = {};
+ handle.callerAddress = this;
+ handle.dataAddress = results;
+ return addon->toAddon->GetTimers(addon, &handle);
+ },
+ m_clientCapabilities.SupportsTimers());
}
PVR_ERROR CPVRClient::AddTimer(const CPVRTimerInfoTag& timer)
@@ -1297,16 +1374,18 @@ PVR_ERROR CPVRClient::UpdateTimerTypes()
types_array = new PVR_TIMER_TYPE*[size];
// manual one time
- (*types_array)[0].iId = 1;
- (*types_array)[0].iAttributes =
+ types_array[0] = new PVR_TIMER_TYPE{};
+ types_array[0]->iId = 1;
+ types_array[0]->iAttributes =
PVR_TIMER_TYPE_IS_MANUAL | PVR_TIMER_TYPE_SUPPORTS_ENABLE_DISABLE |
PVR_TIMER_TYPE_SUPPORTS_CHANNELS | PVR_TIMER_TYPE_SUPPORTS_START_TIME |
PVR_TIMER_TYPE_SUPPORTS_END_TIME | PVR_TIMER_TYPE_SUPPORTS_PRIORITY |
PVR_TIMER_TYPE_SUPPORTS_LIFETIME | PVR_TIMER_TYPE_SUPPORTS_RECORDING_FOLDERS;
// manual timer rule
- (*types_array)[1].iId = 2;
- (*types_array)[1].iAttributes =
+ types_array[1] = new PVR_TIMER_TYPE{};
+ types_array[1]->iId = 2;
+ types_array[1]->iAttributes =
PVR_TIMER_TYPE_IS_MANUAL | PVR_TIMER_TYPE_IS_REPEATING |
PVR_TIMER_TYPE_SUPPORTS_ENABLE_DISABLE | PVR_TIMER_TYPE_SUPPORTS_CHANNELS |
PVR_TIMER_TYPE_SUPPORTS_START_TIME | PVR_TIMER_TYPE_SUPPORTS_END_TIME |
@@ -1317,8 +1396,9 @@ PVR_ERROR CPVRClient::UpdateTimerTypes()
if (m_clientCapabilities.SupportsEPG())
{
// One-shot epg-based
- (*types_array)[2].iId = 3;
- (*types_array)[2].iAttributes =
+ types_array[2] = new PVR_TIMER_TYPE{};
+ types_array[2]->iId = 3;
+ types_array[2]->iAttributes =
PVR_TIMER_TYPE_SUPPORTS_ENABLE_DISABLE | PVR_TIMER_TYPE_REQUIRES_EPG_TAG_ON_CREATE |
PVR_TIMER_TYPE_SUPPORTS_CHANNELS | PVR_TIMER_TYPE_SUPPORTS_START_TIME |
PVR_TIMER_TYPE_SUPPORTS_END_TIME | PVR_TIMER_TYPE_SUPPORTS_PRIORITY |
@@ -1339,7 +1419,8 @@ PVR_ERROR CPVRClient::UpdateTimerTypes()
CLog::LogF(LOGERROR, "Invalid timer type supplied by add-on {}.", GetID());
continue;
}
- timerTypes.emplace_back(std::make_shared<CPVRTimerType>(*types_array[i], m_iClientId));
+ timerTypes.emplace_back(std::make_shared<CPVRTimerType>(
+ *(types_array[i]), m_iClientId, Addon()->GetTypeVersionDll(ADDON_INSTANCE_PVR)));
}
}
@@ -1347,6 +1428,9 @@ PVR_ERROR CPVRClient::UpdateTimerTypes()
if (array_owner)
{
// begin compat section
+ for (unsigned int i = 0; i < size; ++i)
+ delete types_array[i];
+
delete[] types_array;
// end compat section
}
@@ -1370,9 +1454,8 @@ PVR_ERROR CPVRClient::UpdateTimerTypes()
for (const auto& type : timerTypes)
{
const auto it = std::find_if(m_timertypes.cbegin(), m_timertypes.cend(),
- [&type](const std::shared_ptr<const CPVRTimerType>& entry) {
- return entry->GetTypeId() == type->GetTypeId();
- });
+ [&type](const std::shared_ptr<const CPVRTimerType>& entry)
+ { return entry->GetTypeId() == type->GetTypeId(); });
if (it == m_timertypes.cend())
{
newTimerTypes.emplace_back(type);
@@ -1393,74 +1476,89 @@ PVR_ERROR CPVRClient::GetStreamReadChunkSize(int& iChunkSize) const
{
return DoAddonCall(
__func__,
- [&iChunkSize](const AddonInstance* addon) {
- return addon->toAddon->GetStreamReadChunkSize(addon, &iChunkSize);
- },
+ [&iChunkSize](const AddonInstance* addon)
+ { return addon->toAddon->GetStreamReadChunkSize(addon, &iChunkSize); },
m_clientCapabilities.SupportsRecordings() || m_clientCapabilities.HandlesInputStream());
}
PVR_ERROR CPVRClient::ReadLiveStream(void* lpBuf, int64_t uiBufSize, int& iRead)
{
iRead = -1;
- return DoAddonCall(__func__, [&lpBuf, uiBufSize, &iRead](const AddonInstance* addon) {
- iRead = addon->toAddon->ReadLiveStream(addon, static_cast<unsigned char*>(lpBuf),
- static_cast<int>(uiBufSize));
- return (iRead == -1) ? PVR_ERROR_NOT_IMPLEMENTED : PVR_ERROR_NO_ERROR;
- });
+ return DoAddonCall(__func__,
+ [&lpBuf, uiBufSize, &iRead](const AddonInstance* addon)
+ {
+ iRead = addon->toAddon->ReadLiveStream(
+ addon, static_cast<unsigned char*>(lpBuf), static_cast<int>(uiBufSize));
+ return (iRead == -1) ? PVR_ERROR_NOT_IMPLEMENTED : PVR_ERROR_NO_ERROR;
+ });
}
PVR_ERROR CPVRClient::ReadRecordedStream(void* lpBuf, int64_t uiBufSize, int& iRead)
{
iRead = -1;
- return DoAddonCall(__func__, [&lpBuf, uiBufSize, &iRead](const AddonInstance* addon) {
- iRead = addon->toAddon->ReadRecordedStream(addon, static_cast<unsigned char*>(lpBuf),
- static_cast<int>(uiBufSize));
- return (iRead == -1) ? PVR_ERROR_NOT_IMPLEMENTED : PVR_ERROR_NO_ERROR;
- });
+ return DoAddonCall(__func__,
+ [&lpBuf, uiBufSize, &iRead](const AddonInstance* addon)
+ {
+ iRead = addon->toAddon->ReadRecordedStream(
+ addon, static_cast<unsigned char*>(lpBuf), static_cast<int>(uiBufSize));
+ return (iRead == -1) ? PVR_ERROR_NOT_IMPLEMENTED : PVR_ERROR_NO_ERROR;
+ });
}
PVR_ERROR CPVRClient::SeekLiveStream(int64_t iFilePosition, int iWhence, int64_t& iPosition)
{
iPosition = -1;
- return DoAddonCall(__func__, [iFilePosition, iWhence, &iPosition](const AddonInstance* addon) {
- iPosition = addon->toAddon->SeekLiveStream(addon, iFilePosition, iWhence);
- return (iPosition == -1) ? PVR_ERROR_NOT_IMPLEMENTED : PVR_ERROR_NO_ERROR;
- });
+ return DoAddonCall(__func__,
+ [iFilePosition, iWhence, &iPosition](const AddonInstance* addon)
+ {
+ iPosition = addon->toAddon->SeekLiveStream(addon, iFilePosition, iWhence);
+ return (iPosition == -1) ? PVR_ERROR_NOT_IMPLEMENTED : PVR_ERROR_NO_ERROR;
+ });
}
PVR_ERROR CPVRClient::SeekRecordedStream(int64_t iFilePosition, int iWhence, int64_t& iPosition)
{
iPosition = -1;
- return DoAddonCall(__func__, [iFilePosition, iWhence, &iPosition](const AddonInstance* addon) {
- iPosition = addon->toAddon->SeekRecordedStream(addon, iFilePosition, iWhence);
- return (iPosition == -1) ? PVR_ERROR_NOT_IMPLEMENTED : PVR_ERROR_NO_ERROR;
- });
+ return DoAddonCall(__func__,
+ [iFilePosition, iWhence, &iPosition](const AddonInstance* addon)
+ {
+ iPosition =
+ addon->toAddon->SeekRecordedStream(addon, iFilePosition, iWhence);
+ return (iPosition == -1) ? PVR_ERROR_NOT_IMPLEMENTED : PVR_ERROR_NO_ERROR;
+ });
}
PVR_ERROR CPVRClient::SeekTime(double time, bool backwards, double* startpts)
{
- return DoAddonCall(__func__, [time, backwards, &startpts](const AddonInstance* addon) {
- return addon->toAddon->SeekTime(addon, time, backwards, startpts) ? PVR_ERROR_NO_ERROR
- : PVR_ERROR_NOT_IMPLEMENTED;
- });
+ return DoAddonCall(__func__,
+ [time, backwards, &startpts](const AddonInstance* addon)
+ {
+ return addon->toAddon->SeekTime(addon, time, backwards, startpts)
+ ? PVR_ERROR_NO_ERROR
+ : PVR_ERROR_NOT_IMPLEMENTED;
+ });
}
PVR_ERROR CPVRClient::GetLiveStreamLength(int64_t& iLength) const
{
iLength = -1;
- return DoAddonCall(__func__, [&iLength](const AddonInstance* addon) {
- iLength = addon->toAddon->LengthLiveStream(addon);
- return (iLength == -1) ? PVR_ERROR_NOT_IMPLEMENTED : PVR_ERROR_NO_ERROR;
- });
+ return DoAddonCall(__func__,
+ [&iLength](const AddonInstance* addon)
+ {
+ iLength = addon->toAddon->LengthLiveStream(addon);
+ return (iLength == -1) ? PVR_ERROR_NOT_IMPLEMENTED : PVR_ERROR_NO_ERROR;
+ });
}
PVR_ERROR CPVRClient::GetRecordedStreamLength(int64_t& iLength) const
{
iLength = -1;
- return DoAddonCall(__func__, [&iLength](const AddonInstance* addon) {
- iLength = addon->toAddon->LengthRecordedStream(addon);
- return (iLength == -1) ? PVR_ERROR_NOT_IMPLEMENTED : PVR_ERROR_NO_ERROR;
- });
+ return DoAddonCall(__func__,
+ [&iLength](const AddonInstance* addon)
+ {
+ iLength = addon->toAddon->LengthRecordedStream(addon);
+ return (iLength == -1) ? PVR_ERROR_NOT_IMPLEMENTED : PVR_ERROR_NO_ERROR;
+ });
}
PVR_ERROR CPVRClient::SignalQuality(int channelUid, CPVRSignalStatus& qualityinfo) const
@@ -1472,7 +1570,7 @@ PVR_ERROR CPVRClient::SignalQuality(int channelUid, CPVRSignalStatus& qualityinf
const PVR_ERROR error{
addon->toAddon->GetSignalStatus(addon, channelUid, &info)};
if (error == PVR_ERROR_NO_ERROR)
- qualityinfo = info;
+ qualityinfo = CPVRSignalStatus{info};
addon->toAddon->FreeSignalStatus(addon, &info);
return error;
@@ -1488,7 +1586,7 @@ PVR_ERROR CPVRClient::GetDescrambleInfo(int channelUid, CPVRDescrambleInfo& desc
PVR_DESCRAMBLE_INFO info{};
const PVR_ERROR error{addon->toAddon->GetDescrambleInfo(addon, channelUid, &info)};
if (error == PVR_ERROR_NO_ERROR)
- descrambleinfo = info;
+ descrambleinfo = CPVRDescrambleInfo{info};
addon->toAddon->FreeDescrambleInfo(addon, &info);
return error;
@@ -1497,59 +1595,72 @@ PVR_ERROR CPVRClient::GetDescrambleInfo(int channelUid, CPVRDescrambleInfo& desc
}
PVR_ERROR CPVRClient::GetChannelStreamProperties(const std::shared_ptr<const CPVRChannel>& channel,
+ PVR_SOURCE source,
CPVRStreamProperties& props) const
{
- return DoAddonCall(__func__, [this, &channel, &props](const AddonInstance* addon) {
- if (!CanPlayChannel(channel))
- return PVR_ERROR_NO_ERROR; // no error, but no need to obtain the values from the addon
+ return DoAddonCall(
+ __func__,
+ [this, &channel, source, &props](const AddonInstance* addon)
+ {
+ if (!CanPlayChannel(channel))
+ return PVR_ERROR_NO_ERROR; // no error, but no need to obtain the values from the addon
- const CAddonChannel addonChannel{*channel};
+ const CAddonChannel addonChannel{*channel};
- PVR_NAMED_VALUE** property_array{nullptr};
- unsigned int size{0};
- const PVR_ERROR error{
- addon->toAddon->GetChannelStreamProperties(addon, &addonChannel, &property_array, &size)};
- if (error == PVR_ERROR_NO_ERROR)
- WriteStreamProperties(property_array, size, props);
+ PVR_NAMED_VALUE** property_array{nullptr};
+ unsigned int size{0};
+ const PVR_ERROR error{addon->toAddon->GetChannelStreamProperties(
+ addon, &addonChannel, source, &property_array, &size)};
+ if (error == PVR_ERROR_NO_ERROR)
+ WriteStreamProperties(property_array, size, props);
- addon->toAddon->FreeProperties(addon, property_array, size);
- return error;
- });
+ addon->toAddon->FreeProperties(addon, property_array, size);
+ return error;
+ });
}
PVR_ERROR CPVRClient::GetRecordingStreamProperties(
const std::shared_ptr<const CPVRRecording>& recording, CPVRStreamProperties& props) const
{
- return DoAddonCall(__func__, [this, &recording, &props](const AddonInstance* addon) {
- if (!m_clientCapabilities.SupportsRecordings())
- return PVR_ERROR_NO_ERROR; // no error, but no need to obtain the values from the addon
+ return DoAddonCall(
+ __func__,
+ [this, &recording, &props](const AddonInstance* addon)
+ {
+ if (!m_clientCapabilities.SupportsRecordings())
+ return PVR_ERROR_NO_ERROR; // no error, but no need to obtain the values from the addon
- const CAddonRecording addonRecording(*recording);
+ const CAddonRecording addonRecording(*recording);
- PVR_NAMED_VALUE** property_array{nullptr};
- unsigned int size{0};
- const PVR_ERROR error{addon->toAddon->GetRecordingStreamProperties(addon, &addonRecording,
- &property_array, &size)};
- if (error == PVR_ERROR_NO_ERROR)
- WriteStreamProperties(property_array, size, props);
+ PVR_NAMED_VALUE** property_array{nullptr};
+ unsigned int size{0};
+ const PVR_ERROR error{addon->toAddon->GetRecordingStreamProperties(addon, &addonRecording,
+ &property_array, &size)};
+ if (error == PVR_ERROR_NO_ERROR)
+ WriteStreamProperties(property_array, size, props);
- addon->toAddon->FreeProperties(addon, property_array, size);
- return error;
- });
+ addon->toAddon->FreeProperties(addon, property_array, size);
+ return error;
+ });
}
PVR_ERROR CPVRClient::GetStreamProperties(PVR_STREAM_PROPERTIES* props) const
{
- return DoAddonCall(__func__, [&props](const AddonInstance* addon) {
- return addon->toAddon->GetStreamProperties(addon, props);
- });
+ return DoAddonCall(__func__, [&props](const AddonInstance* addon)
+ { return addon->toAddon->GetStreamProperties(addon, props); });
+}
+
+PVR_ERROR CPVRClient::StreamClosed() const
+{
+ return DoAddonCall(__func__, [](const AddonInstance* addon)
+ { return addon->toAddon->StreamClosed(addon); });
}
PVR_ERROR CPVRClient::DemuxReset()
{
return DoAddonCall(
__func__,
- [](const AddonInstance* addon) {
+ [](const AddonInstance* addon)
+ {
addon->toAddon->DemuxReset(addon);
return PVR_ERROR_NO_ERROR;
},
@@ -1560,7 +1671,8 @@ PVR_ERROR CPVRClient::DemuxAbort()
{
return DoAddonCall(
__func__,
- [](const AddonInstance* addon) {
+ [](const AddonInstance* addon)
+ {
addon->toAddon->DemuxAbort(addon);
return PVR_ERROR_NO_ERROR;
},
@@ -1571,7 +1683,8 @@ PVR_ERROR CPVRClient::DemuxFlush()
{
return DoAddonCall(
__func__,
- [](const AddonInstance* addon) {
+ [](const AddonInstance* addon)
+ {
addon->toAddon->DemuxFlush(addon);
return PVR_ERROR_NO_ERROR;
},
@@ -1582,7 +1695,8 @@ PVR_ERROR CPVRClient::DemuxRead(DemuxPacket*& packet)
{
return DoAddonCall(
__func__,
- [&packet](const AddonInstance* addon) {
+ [&packet](const AddonInstance* addon)
+ {
packet = static_cast<DemuxPacket*>(addon->toAddon->DemuxRead(addon));
return packet ? PVR_ERROR_NO_ERROR : PVR_ERROR_NOT_IMPLEMENTED;
},
@@ -1681,23 +1795,27 @@ PVR_ERROR CPVRClient::OpenLiveStream(const std::shared_ptr<const CPVRChannel>& c
if (!channel)
return PVR_ERROR_INVALID_PARAMETERS;
- return DoAddonCall(__func__, [this, channel](const AddonInstance* addon) {
- CloseLiveStream();
-
- if (!CanPlayChannel(channel))
- {
- CLog::LogFC(LOGDEBUG, LOGPVR, "Add-on {} can not play channel '{}'", GetID(),
- channel->ChannelName());
- return PVR_ERROR_SERVER_ERROR;
- }
- else
- {
- CLog::LogFC(LOGDEBUG, LOGPVR, "Opening live stream for channel '{}'", channel->ChannelName());
- const CAddonChannel addonChannel{*channel};
- return addon->toAddon->OpenLiveStream(addon, &addonChannel) ? PVR_ERROR_NO_ERROR
- : PVR_ERROR_NOT_IMPLEMENTED;
- }
- });
+ return DoAddonCall(__func__,
+ [this, channel](const AddonInstance* addon)
+ {
+ CloseLiveStream();
+
+ if (!CanPlayChannel(channel))
+ {
+ CLog::LogFC(LOGDEBUG, LOGPVR, "Add-on {} can not play channel '{}'",
+ GetID(), channel->ChannelName());
+ return PVR_ERROR_SERVER_ERROR;
+ }
+ else
+ {
+ CLog::LogFC(LOGDEBUG, LOGPVR, "Opening live stream for channel '{}'",
+ channel->ChannelName());
+ const CAddonChannel addonChannel{*channel};
+ return addon->toAddon->OpenLiveStream(addon, &addonChannel)
+ ? PVR_ERROR_NO_ERROR
+ : PVR_ERROR_NOT_IMPLEMENTED;
+ }
+ });
}
PVR_ERROR CPVRClient::OpenRecordedStream(const std::shared_ptr<const CPVRRecording>& recording)
@@ -1707,7 +1825,8 @@ PVR_ERROR CPVRClient::OpenRecordedStream(const std::shared_ptr<const CPVRRecordi
return DoAddonCall(
__func__,
- [this, recording](const AddonInstance* addon) {
+ [this, recording](const AddonInstance* addon)
+ {
CloseRecordedStream();
const CAddonRecording tag(*recording);
@@ -1720,102 +1839,115 @@ PVR_ERROR CPVRClient::OpenRecordedStream(const std::shared_ptr<const CPVRRecordi
PVR_ERROR CPVRClient::CloseLiveStream()
{
- return DoAddonCall(__func__, [](const AddonInstance* addon) {
- addon->toAddon->CloseLiveStream(addon);
- return PVR_ERROR_NO_ERROR;
- });
+ return DoAddonCall(__func__,
+ [](const AddonInstance* addon)
+ {
+ addon->toAddon->CloseLiveStream(addon);
+ return PVR_ERROR_NO_ERROR;
+ });
}
PVR_ERROR CPVRClient::CloseRecordedStream()
{
- return DoAddonCall(__func__, [](const AddonInstance* addon) {
- addon->toAddon->CloseRecordedStream(addon);
- return PVR_ERROR_NO_ERROR;
- });
+ return DoAddonCall(__func__,
+ [](const AddonInstance* addon)
+ {
+ addon->toAddon->CloseRecordedStream(addon);
+ return PVR_ERROR_NO_ERROR;
+ });
}
PVR_ERROR CPVRClient::PauseStream(bool bPaused)
{
- return DoAddonCall(__func__, [bPaused](const AddonInstance* addon) {
- addon->toAddon->PauseStream(addon, bPaused);
- return PVR_ERROR_NO_ERROR;
- });
+ return DoAddonCall(__func__,
+ [bPaused](const AddonInstance* addon)
+ {
+ addon->toAddon->PauseStream(addon, bPaused);
+ return PVR_ERROR_NO_ERROR;
+ });
}
PVR_ERROR CPVRClient::SetSpeed(int speed)
{
- return DoAddonCall(__func__, [speed](const AddonInstance* addon) {
- addon->toAddon->SetSpeed(addon, speed);
- return PVR_ERROR_NO_ERROR;
- });
+ return DoAddonCall(__func__,
+ [speed](const AddonInstance* addon)
+ {
+ addon->toAddon->SetSpeed(addon, speed);
+ return PVR_ERROR_NO_ERROR;
+ });
}
PVR_ERROR CPVRClient::FillBuffer(bool mode)
{
- return DoAddonCall(__func__, [mode](const AddonInstance* addon) {
- addon->toAddon->FillBuffer(addon, mode);
- return PVR_ERROR_NO_ERROR;
- });
+ return DoAddonCall(__func__,
+ [mode](const AddonInstance* addon)
+ {
+ addon->toAddon->FillBuffer(addon, mode);
+ return PVR_ERROR_NO_ERROR;
+ });
}
PVR_ERROR CPVRClient::CanPauseStream(bool& bCanPause) const
{
bCanPause = false;
- return DoAddonCall(__func__, [&bCanPause](const AddonInstance* addon) {
- bCanPause = addon->toAddon->CanPauseStream(addon);
- return PVR_ERROR_NO_ERROR;
- });
+ return DoAddonCall(__func__,
+ [&bCanPause](const AddonInstance* addon)
+ {
+ bCanPause = addon->toAddon->CanPauseStream(addon);
+ return PVR_ERROR_NO_ERROR;
+ });
}
PVR_ERROR CPVRClient::CanSeekStream(bool& bCanSeek) const
{
bCanSeek = false;
- return DoAddonCall(__func__, [&bCanSeek](const AddonInstance* addon) {
- bCanSeek = addon->toAddon->CanSeekStream(addon);
- return PVR_ERROR_NO_ERROR;
- });
+ return DoAddonCall(__func__,
+ [&bCanSeek](const AddonInstance* addon)
+ {
+ bCanSeek = addon->toAddon->CanSeekStream(addon);
+ return PVR_ERROR_NO_ERROR;
+ });
}
PVR_ERROR CPVRClient::GetStreamTimes(PVR_STREAM_TIMES* times) const
{
- return DoAddonCall(__func__, [&times](const AddonInstance* addon) {
- return addon->toAddon->GetStreamTimes(addon, times);
- });
+ return DoAddonCall(__func__, [&times](const AddonInstance* addon)
+ { return addon->toAddon->GetStreamTimes(addon, times); });
}
PVR_ERROR CPVRClient::IsRealTimeStream(bool& bRealTime) const
{
bRealTime = false;
- return DoAddonCall(__func__, [&bRealTime](const AddonInstance* addon) {
- bRealTime = addon->toAddon->IsRealTimeStream(addon);
- return PVR_ERROR_NO_ERROR;
- });
+ return DoAddonCall(__func__,
+ [&bRealTime](const AddonInstance* addon)
+ {
+ bRealTime = addon->toAddon->IsRealTimeStream(addon);
+ return PVR_ERROR_NO_ERROR;
+ });
}
PVR_ERROR CPVRClient::OnSystemSleep()
{
- return DoAddonCall(
- __func__, [](const AddonInstance* addon) { return addon->toAddon->OnSystemSleep(addon); });
+ return DoAddonCall(__func__, [](const AddonInstance* addon)
+ { return addon->toAddon->OnSystemSleep(addon); });
}
PVR_ERROR CPVRClient::OnSystemWake()
{
- return DoAddonCall(
- __func__, [](const AddonInstance* addon) { return addon->toAddon->OnSystemWake(addon); });
+ return DoAddonCall(__func__, [](const AddonInstance* addon)
+ { return addon->toAddon->OnSystemWake(addon); });
}
PVR_ERROR CPVRClient::OnPowerSavingActivated()
{
- return DoAddonCall(__func__, [](const AddonInstance* addon) {
- return addon->toAddon->OnPowerSavingActivated(addon);
- });
+ return DoAddonCall(__func__, [](const AddonInstance* addon)
+ { return addon->toAddon->OnPowerSavingActivated(addon); });
}
PVR_ERROR CPVRClient::OnPowerSavingDeactivated()
{
- return DoAddonCall(__func__, [](const AddonInstance* addon) {
- return addon->toAddon->OnPowerSavingDeactivated(addon);
- });
+ return DoAddonCall(__func__, [](const AddonInstance* addon)
+ { return addon->toAddon->OnPowerSavingDeactivated(addon); });
}
std::shared_ptr<CPVRClientMenuHooks> CPVRClient::GetMenuHooks() const
@@ -1829,16 +1961,18 @@ std::shared_ptr<CPVRClientMenuHooks> CPVRClient::GetMenuHooks() const
PVR_ERROR CPVRClient::CallEpgTagMenuHook(const CPVRClientMenuHook& hook,
const std::shared_ptr<const CPVREpgInfoTag>& tag)
{
- return DoAddonCall(__func__, [&hook, &tag](const AddonInstance* addon) {
- CAddonEpgTag addonTag(tag);
+ return DoAddonCall(__func__,
+ [&hook, &tag](const AddonInstance* addon)
+ {
+ CAddonEpgTag addonTag(*tag);
- PVR_MENUHOOK menuHook;
- menuHook.category = PVR_MENUHOOK_EPG;
- menuHook.iHookId = hook.GetId();
- menuHook.iLocalizedStringId = hook.GetLabelId();
+ PVR_MENUHOOK menuHook;
+ menuHook.category = PVR_MENUHOOK_EPG;
+ menuHook.iHookId = hook.GetId();
+ menuHook.iLocalizedStringId = hook.GetLabelId();
- return addon->toAddon->CallEPGMenuHook(addon, &menuHook, &addonTag);
- });
+ return addon->toAddon->CallEPGMenuHook(addon, &menuHook, &addonTag);
+ });
}
PVR_ERROR CPVRClient::CallChannelMenuHook(const CPVRClientMenuHook& hook,
@@ -1896,14 +2030,16 @@ PVR_ERROR CPVRClient::CallTimerMenuHook(const CPVRClientMenuHook& hook,
PVR_ERROR CPVRClient::CallSettingsMenuHook(const CPVRClientMenuHook& hook)
{
- return DoAddonCall(__func__, [&hook](const AddonInstance* addon) {
- PVR_MENUHOOK menuHook;
- menuHook.category = PVR_MENUHOOK_SETTING;
- menuHook.iHookId = hook.GetId();
- menuHook.iLocalizedStringId = hook.GetLabelId();
+ return DoAddonCall(__func__,
+ [&hook](const AddonInstance* addon)
+ {
+ PVR_MENUHOOK menuHook;
+ menuHook.category = PVR_MENUHOOK_SETTING;
+ menuHook.iHookId = hook.GetId();
+ menuHook.iLocalizedStringId = hook.GetLabelId();
- return addon->toAddon->CallSettingsMenuHook(addon, &menuHook);
- });
+ return addon->toAddon->CallSettingsMenuHook(addon, &menuHook);
+ });
}
void CPVRClient::SetPriority(int iPriority)
@@ -1912,7 +2048,7 @@ void CPVRClient::SetPriority(int iPriority)
if (m_priority != iPriority)
{
m_priority = iPriority;
- if (m_iClientId > PVR_INVALID_CLIENT_ID)
+ if (m_iClientId != PVR_CLIENT_INVALID_UID)
{
CServiceBroker::GetPVRManager().GetTVDatabase()->Persist(*this);
}
@@ -1923,13 +2059,37 @@ void CPVRClient::SetPriority(int iPriority)
int CPVRClient::GetPriority() const
{
std::unique_lock<CCriticalSection> lock(m_critSection);
- if (!m_priority.has_value() && m_iClientId > PVR_INVALID_CLIENT_ID)
+ if (!m_priority.has_value() && m_iClientId != PVR_CLIENT_INVALID_UID)
{
m_priority = CServiceBroker::GetPVRManager().GetTVDatabase()->GetPriority(*this);
}
return *m_priority;
}
+const CDateTime& CPVRClient::GetDateTimeFirstChannelsAdded() const
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ if (!m_firstChannelsAdded.has_value() && m_iClientId != PVR_CLIENT_INVALID_UID)
+ {
+ m_firstChannelsAdded =
+ CServiceBroker::GetPVRManager().GetTVDatabase()->GetDateTimeFirstChannelsAdded(*this);
+ }
+ return *m_firstChannelsAdded;
+}
+
+void CPVRClient::SetDateTimeFirstChannelsAdded(const CDateTime& dateTime)
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ if (m_firstChannelsAdded != dateTime)
+ {
+ m_firstChannelsAdded = dateTime;
+ if (m_iClientId != PVR_CLIENT_INVALID_UID)
+ {
+ CServiceBroker::GetPVRManager().GetTVDatabase()->Persist(*this);
+ }
+ }
+}
+
void CPVRClient::HandleAddonCallback(const char* strFunctionName,
void* kodiInstance,
const std::function<void(CPVRClient* client)>& function,
@@ -1958,71 +2118,79 @@ void CPVRClient::cb_transfer_channel_group(void* kodiInstance,
const PVR_HANDLE handle,
const PVR_CHANNEL_GROUP* group)
{
- HandleAddonCallback(__func__, kodiInstance, [&](CPVRClient* client) {
- if (!handle || !group)
- {
- CLog::LogF(LOGERROR, "Invalid callback parameter(s)");
- return;
- }
-
- if (strlen(group->strGroupName) == 0)
- {
- CLog::LogF(LOGERROR, "Empty group name");
- return;
- }
-
- // transfer this entry to the groups container
- CPVRChannelGroups* kodiGroups = static_cast<CPVRChannelGroups*>(handle->dataAddress);
- const auto transferGroup = kodiGroups->GetGroupFactory()->CreateClientGroup(
- *group, client->GetID(), kodiGroups->GetGroupAll());
- kodiGroups->UpdateFromClient(transferGroup);
- });
+ HandleAddonCallback(__func__, kodiInstance,
+ [&](CPVRClient* client)
+ {
+ if (!handle || !group)
+ {
+ CLog::LogF(LOGERROR, "Invalid callback parameter(s)");
+ return;
+ }
+
+ if (strlen(group->strGroupName) == 0)
+ {
+ CLog::LogF(LOGERROR, "Empty group name");
+ return;
+ }
+
+ // transfer this entry to the groups container
+ CPVRChannelGroups* kodiGroups =
+ static_cast<CPVRChannelGroups*>(handle->dataAddress);
+ const auto transferGroup = kodiGroups->GetGroupFactory()->CreateClientGroup(
+ *group, client->GetID(), kodiGroups->GetGroupAll());
+ kodiGroups->UpdateFromClient(transferGroup);
+ });
}
void CPVRClient::cb_transfer_channel_group_member(void* kodiInstance,
const PVR_HANDLE handle,
const PVR_CHANNEL_GROUP_MEMBER* member)
{
- HandleAddonCallback(__func__, kodiInstance, [&](CPVRClient* client) {
- if (!handle || !member)
- {
- CLog::LogF(LOGERROR, "Invalid callback parameter(s)");
- return;
- }
-
- const std::shared_ptr<CPVRChannel> channel =
- CServiceBroker::GetPVRManager().ChannelGroups()->GetByUniqueID(member->iChannelUniqueId,
- client->GetID());
- if (!channel)
- {
- CLog::LogF(LOGERROR, "Cannot find group '{}' or channel '{}'", member->strGroupName,
- member->iChannelUniqueId);
- }
- else
- {
- auto* groupMembers =
- static_cast<std::vector<std::shared_ptr<CPVRChannelGroupMember>>*>(handle->dataAddress);
- groupMembers->emplace_back(std::make_shared<CPVRChannelGroupMember>(
- member->strGroupName, client->GetID(), member->iOrder, channel));
- }
- });
+ HandleAddonCallback(__func__, kodiInstance,
+ [&](CPVRClient* client)
+ {
+ if (!handle || !member)
+ {
+ CLog::LogF(LOGERROR, "Invalid callback parameter(s)");
+ return;
+ }
+
+ const std::shared_ptr<CPVRChannel> channel =
+ CServiceBroker::GetPVRManager().ChannelGroups()->GetByUniqueID(
+ member->iChannelUniqueId, client->GetID());
+ if (!channel)
+ {
+ CLog::LogF(LOGERROR, "Cannot find group '{}' or channel '{}'",
+ member->strGroupName, member->iChannelUniqueId);
+ }
+ else
+ {
+ auto* groupMembers =
+ static_cast<std::vector<std::shared_ptr<CPVRChannelGroupMember>>*>(
+ handle->dataAddress);
+ groupMembers->emplace_back(std::make_shared<CPVRChannelGroupMember>(
+ member->strGroupName, client->GetID(), member->iOrder, channel));
+ }
+ });
}
void CPVRClient::cb_transfer_epg_entry(void* kodiInstance,
const PVR_HANDLE handle,
const EPG_TAG* epgentry)
{
- HandleAddonCallback(__func__, kodiInstance, [&](CPVRClient* client) {
- if (!handle || !epgentry)
- {
- CLog::LogF(LOGERROR, "Invalid callback parameter(s)");
- return;
- }
+ HandleAddonCallback(__func__, kodiInstance,
+ [&](CPVRClient* client)
+ {
+ if (!handle || !epgentry)
+ {
+ CLog::LogF(LOGERROR, "Invalid callback parameter(s)");
+ return;
+ }
- // transfer this entry to the epg
- CPVREpg* epg = static_cast<CPVREpg*>(handle->dataAddress);
- epg->UpdateEntry(epgentry, client->GetID());
- });
+ // transfer this entry to the epg
+ CPVREpg* epg = static_cast<CPVREpg*>(handle->dataAddress);
+ epg->UpdateEntry(epgentry, client->GetID());
+ });
}
void CPVRClient::cb_transfer_provider_entry(void* kodiInstance,
@@ -2053,73 +2221,87 @@ void CPVRClient::cb_transfer_channel_entry(void* kodiInstance,
const PVR_HANDLE handle,
const PVR_CHANNEL* channel)
{
- HandleAddonCallback(__func__, kodiInstance, [&](CPVRClient* client) {
- if (!handle || !channel)
- {
- CLog::LogF(LOGERROR, "Invalid callback parameter(s)");
- return;
- }
+ HandleAddonCallback(
+ __func__, kodiInstance,
+ [&](CPVRClient* client)
+ {
+ if (!handle || !channel)
+ {
+ CLog::LogF(LOGERROR, "Invalid callback parameter(s)");
+ return;
+ }
- auto* channels = static_cast<std::vector<std::shared_ptr<CPVRChannel>>*>(handle->dataAddress);
- channels->emplace_back(std::make_shared<CPVRChannel>(*channel, client->GetID()));
- });
+ auto* channels =
+ static_cast<std::vector<std::shared_ptr<CPVRChannel>>*>(handle->dataAddress);
+ channels->emplace_back(std::make_shared<CPVRChannel>(*channel, client->GetID()));
+ });
}
void CPVRClient::cb_transfer_recording_entry(void* kodiInstance,
const PVR_HANDLE handle,
const PVR_RECORDING* recording)
{
- HandleAddonCallback(__func__, kodiInstance, [&](CPVRClient* client) {
- if (!handle || !recording)
- {
- CLog::LogF(LOGERROR, "Invalid callback parameter(s)");
- return;
- }
-
- // transfer this entry to the recordings container
- const std::shared_ptr<CPVRRecording> transferRecording =
- std::make_shared<CPVRRecording>(*recording, client->GetID());
- CPVRRecordings* recordings = static_cast<CPVRRecordings*>(handle->dataAddress);
- recordings->UpdateFromClient(transferRecording, *client);
- });
+ HandleAddonCallback(__func__, kodiInstance,
+ [&](CPVRClient* client)
+ {
+ if (!handle || !recording)
+ {
+ CLog::LogF(LOGERROR, "Invalid callback parameter(s)");
+ return;
+ }
+
+ // transfer this entry to the recordings container
+ const std::shared_ptr<CPVRRecording> transferRecording =
+ std::make_shared<CPVRRecording>(*recording, client->GetID());
+ CPVRRecordings* recordings =
+ static_cast<CPVRRecordings*>(handle->dataAddress);
+ recordings->UpdateFromClient(transferRecording, *client);
+ });
}
void CPVRClient::cb_transfer_timer_entry(void* kodiInstance,
const PVR_HANDLE handle,
const PVR_TIMER* timer)
{
- HandleAddonCallback(__func__, kodiInstance, [&](CPVRClient* client) {
- if (!handle || !timer)
- {
- CLog::LogF(LOGERROR, "Invalid callback parameter(s)");
- return;
- }
-
- // Note: channel can be nullptr here, for instance for epg-based timer rules
- // ("record on any channel" condition)
- const std::shared_ptr<CPVRChannel> channel =
- CServiceBroker::GetPVRManager().ChannelGroups()->GetByUniqueID(timer->iClientChannelUid,
- client->GetID());
-
- // transfer this entry to the timers container
- const std::shared_ptr<CPVRTimerInfoTag> transferTimer =
- std::make_shared<CPVRTimerInfoTag>(*timer, channel, client->GetID());
- CPVRTimersContainer* timers = static_cast<CPVRTimersContainer*>(handle->dataAddress);
- timers->UpdateFromClient(transferTimer);
- });
+ HandleAddonCallback(__func__, kodiInstance,
+ [&](CPVRClient* client)
+ {
+ if (!handle || !timer)
+ {
+ CLog::LogF(LOGERROR, "Invalid callback parameter(s)");
+ return;
+ }
+
+ // Note: channel can be nullptr here, for instance for epg-based timer rules
+ // ("record on any channel" condition)
+ const std::shared_ptr<CPVRChannel> channel =
+ CServiceBroker::GetPVRManager().ChannelGroups()->GetByUniqueID(
+ timer->iClientChannelUid, client->GetID());
+
+ // transfer this entry to the timers container
+ const std::shared_ptr<CPVRTimerInfoTag> transferTimer =
+ std::make_shared<CPVRTimerInfoTag>(
+ *timer, channel, client->GetID(),
+ client->Addon()->GetTypeVersionDll(ADDON_INSTANCE_PVR));
+ CPVRTimersContainer* timers =
+ static_cast<CPVRTimersContainer*>(handle->dataAddress);
+ timers->UpdateFromClient(transferTimer);
+ });
}
void CPVRClient::cb_add_menu_hook(void* kodiInstance, const PVR_MENUHOOK* hook)
{
- HandleAddonCallback(__func__, kodiInstance, [&](CPVRClient* client) {
- if (!hook)
- {
- CLog::LogF(LOGERROR, "Invalid callback parameter(s)");
- return;
- }
+ HandleAddonCallback(__func__, kodiInstance,
+ [&](CPVRClient* client)
+ {
+ if (!hook)
+ {
+ CLog::LogF(LOGERROR, "Invalid callback parameter(s)");
+ return;
+ }
- client->GetMenuHooks()->AddHook(*hook);
- });
+ client->GetMenuHooks()->AddHook(*hook);
+ });
}
void CPVRClient::cb_recording_notification(void* kodiInstance,
@@ -2127,88 +2309,103 @@ void CPVRClient::cb_recording_notification(void* kodiInstance,
const char* strFileName,
bool bOnOff)
{
- HandleAddonCallback(__func__, kodiInstance, [&](CPVRClient* client) {
- if (!strFileName)
- {
- CLog::LogF(LOGERROR, "Invalid callback parameter(s)");
- return;
- }
+ HandleAddonCallback(
+ __func__, kodiInstance,
+ [&](CPVRClient* client)
+ {
+ if (!strFileName)
+ {
+ CLog::LogF(LOGERROR, "Invalid callback parameter(s)");
+ return;
+ }
- const std::string strLine1 = StringUtils::Format(g_localizeStrings.Get(bOnOff ? 19197 : 19198),
- client->GetFullClientName());
- std::string strLine2;
- if (strName)
- strLine2 = strName;
- else
- strLine2 = strFileName;
+ const std::string strLine1 = StringUtils::Format(
+ g_localizeStrings.Get(bOnOff ? 19197 : 19198), client->GetFullClientName());
+ std::string strLine2;
+ if (strName)
+ strLine2 = strName;
+ else
+ strLine2 = strFileName;
- // display a notification for 5 seconds
- CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Info, strLine1, strLine2, 5000,
- false);
- auto eventLog = CServiceBroker::GetEventLog();
- if (eventLog)
- eventLog->Add(EventPtr(
- new CNotificationEvent(client->GetFullClientName(), strLine1, client->Icon(), strLine2)));
+ // display a notification for 5 seconds
+ CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Info, strLine1, strLine2, 5000,
+ false);
+ auto eventLog = CServiceBroker::GetEventLog();
+ if (eventLog)
+ eventLog->Add(EventPtr(new CNotificationEvent(client->GetFullClientName(), strLine1,
+ client->Icon(), strLine2)));
- CLog::LogFC(LOGDEBUG, LOGPVR, "Recording {} on client {}. name='{}' filename='{}'",
- bOnOff ? "started" : "finished", client->GetID(), strName, strFileName);
- });
+ CLog::LogFC(LOGDEBUG, LOGPVR, "Recording {} on client {}. name='{}' filename='{}'",
+ bOnOff ? "started" : "finished", client->GetID(), strName, strFileName);
+ });
}
void CPVRClient::cb_trigger_channel_update(void* kodiInstance)
{
- HandleAddonCallback(__func__, kodiInstance, [&](CPVRClient* client) {
- // update channels in the next iteration of the pvrmanager's main loop
- CServiceBroker::GetPVRManager().TriggerChannelsUpdate(client->GetID());
- });
+ HandleAddonCallback(__func__, kodiInstance,
+ [&](CPVRClient* client)
+ {
+ // update channels in the next iteration of the pvrmanager's main loop
+ CServiceBroker::GetPVRManager().TriggerChannelsUpdate(client->GetID());
+ });
}
void CPVRClient::cb_trigger_provider_update(void* kodiInstance)
{
- HandleAddonCallback(__func__, kodiInstance, [&](CPVRClient* client) {
- /* update the providers table in the next iteration of the pvrmanager's main loop */
- CServiceBroker::GetPVRManager().TriggerProvidersUpdate(client->GetID());
- });
+ HandleAddonCallback(__func__, kodiInstance,
+ [&](CPVRClient* client)
+ {
+ /* update the providers table in the next iteration of the pvrmanager's main loop */
+ CServiceBroker::GetPVRManager().TriggerProvidersUpdate(client->GetID());
+ });
}
void CPVRClient::cb_trigger_timer_update(void* kodiInstance)
{
- HandleAddonCallback(__func__, kodiInstance, [&](CPVRClient* client) {
- // update timers in the next iteration of the pvrmanager's main loop
- CServiceBroker::GetPVRManager().TriggerTimersUpdate(client->GetID());
- });
+ HandleAddonCallback(__func__, kodiInstance,
+ [&](CPVRClient* client)
+ {
+ // update timers in the next iteration of the pvrmanager's main loop
+ CServiceBroker::GetPVRManager().TriggerTimersUpdate(client->GetID());
+ });
}
void CPVRClient::cb_trigger_recording_update(void* kodiInstance)
{
- HandleAddonCallback(__func__, kodiInstance, [&](CPVRClient* client) {
- // update recordings in the next iteration of the pvrmanager's main loop
- CServiceBroker::GetPVRManager().TriggerRecordingsUpdate(client->GetID());
- });
+ HandleAddonCallback(__func__, kodiInstance,
+ [&](CPVRClient* client)
+ {
+ // update recordings in the next iteration of the pvrmanager's main loop
+ CServiceBroker::GetPVRManager().TriggerRecordingsUpdate(client->GetID());
+ });
}
void CPVRClient::cb_trigger_channel_groups_update(void* kodiInstance)
{
- HandleAddonCallback(__func__, kodiInstance, [&](CPVRClient* client) {
- // update all channel groups in the next iteration of the pvrmanager's main loop
- CServiceBroker::GetPVRManager().TriggerChannelGroupsUpdate(client->GetID());
- });
+ HandleAddonCallback(__func__, kodiInstance,
+ [&](CPVRClient* client)
+ {
+ // update all channel groups in the next iteration of the pvrmanager's main loop
+ CServiceBroker::GetPVRManager().TriggerChannelGroupsUpdate(client->GetID());
+ });
}
void CPVRClient::cb_trigger_epg_update(void* kodiInstance, unsigned int iChannelUid)
{
- HandleAddonCallback(__func__, kodiInstance, [&](CPVRClient* client) {
- CServiceBroker::GetPVRManager().EpgContainer().UpdateRequest(client->GetID(), iChannelUid);
- });
+ HandleAddonCallback(__func__, kodiInstance,
+ [&](CPVRClient* client) {
+ CServiceBroker::GetPVRManager().EpgContainer().UpdateRequest(
+ client->GetID(), iChannelUid);
+ });
}
void CPVRClient::cb_free_demux_packet(void* kodiInstance, DEMUX_PACKET* pPacket)
{
- HandleAddonCallback(__func__, kodiInstance,
- [&](CPVRClient* client) {
- CDVDDemuxUtils::FreeDemuxPacket(static_cast<DemuxPacket*>(pPacket));
- },
- true);
+ HandleAddonCallback(
+ __func__, kodiInstance,
+ [&](CPVRClient* client)
+ { CDVDDemuxUtils::FreeDemuxPacket(static_cast<DemuxPacket*>(pPacket)); },
+ true);
}
DEMUX_PACKET* CPVRClient::cb_allocate_demux_packet(void* kodiInstance, int iDataSize)
@@ -2227,47 +2424,53 @@ void CPVRClient::cb_connection_state_change(void* kodiInstance,
PVR_CONNECTION_STATE newState,
const char* strMessage)
{
- HandleAddonCallback(__func__, kodiInstance, [&](CPVRClient* client) {
- if (!strConnectionString)
- {
- CLog::LogF(LOGERROR, "Invalid callback parameter(s)");
- return;
- }
+ HandleAddonCallback(__func__, kodiInstance,
+ [&](CPVRClient* client)
+ {
+ if (!strConnectionString)
+ {
+ CLog::LogF(LOGERROR, "Invalid callback parameter(s)");
+ return;
+ }
- const PVR_CONNECTION_STATE prevState(client->GetConnectionState());
- if (prevState == newState)
- return;
+ const PVR_CONNECTION_STATE prevState(client->GetConnectionState());
+ if (prevState == newState)
+ return;
- CLog::LogFC(LOGDEBUG, LOGPVR, "Connection state for client {} changed from {} to {}",
- client->GetID(), prevState, newState);
+ CLog::LogFC(LOGDEBUG, LOGPVR,
+ "Connection state for client {} changed from {} to {}",
+ client->GetID(), prevState, newState);
- client->SetConnectionState(newState);
+ client->SetConnectionState(newState);
- std::string msg;
- if (strMessage)
- msg = strMessage;
+ std::string msg;
+ if (strMessage)
+ msg = strMessage;
- CServiceBroker::GetPVRManager().ConnectionStateChange(client, std::string(strConnectionString),
- newState, msg);
- });
+ CServiceBroker::GetPVRManager().ConnectionStateChange(
+ client, std::string(strConnectionString), newState, msg);
+ });
}
void CPVRClient::cb_epg_event_state_change(void* kodiInstance,
EPG_TAG* tag,
EPG_EVENT_STATE newState)
{
- HandleAddonCallback(__func__, kodiInstance, [&](CPVRClient* client) {
- if (!tag)
- {
- CLog::LogF(LOGERROR, "Invalid callback parameter(s)");
- return;
- }
-
- // Note: channel data and epg id may not yet be available. Tag will be fully initialized later.
- const std::shared_ptr<CPVREpgInfoTag> epgTag =
- std::make_shared<CPVREpgInfoTag>(*tag, client->GetID(), nullptr, -1);
- CServiceBroker::GetPVRManager().EpgContainer().UpdateFromClient(epgTag, newState);
- });
+ HandleAddonCallback(__func__, kodiInstance,
+ [&](CPVRClient* client)
+ {
+ if (!tag)
+ {
+ CLog::LogF(LOGERROR, "Invalid callback parameter(s)");
+ return;
+ }
+
+ // Note: channel data and epg id may not yet be available. Tag will be fully initialized later.
+ const std::shared_ptr<CPVREpgInfoTag> epgTag =
+ std::make_shared<CPVREpgInfoTag>(*tag, client->GetID(), nullptr, -1);
+ CServiceBroker::GetPVRManager().EpgContainer().UpdateFromClient(epgTag,
+ newState);
+ });
}
class CCodecIds
diff --git a/xbmc/pvr/addons/PVRClient.h b/xbmc/pvr/addons/PVRClient.h
index 30417dfffb..2d111ebaef 100644
--- a/xbmc/pvr/addons/PVRClient.h
+++ b/xbmc/pvr/addons/PVRClient.h
@@ -8,6 +8,7 @@
#pragma once
+#include "XBDateTime.h"
#include "addons/binary-addons/AddonInstanceHandler.h"
#include "addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr.h"
#include "pvr/addons/PVRClientCapabilities.h"
@@ -49,9 +50,6 @@ class CPVRTimerInfoTag;
class CPVRTimerType;
class CPVRTimersContainer;
-static constexpr int PVR_ANY_CLIENT_ID = -1;
-static constexpr int PVR_INVALID_CLIENT_ID = -2;
-
/*!
* Interface from Kodi to a PVR add-on.
*
@@ -152,6 +150,12 @@ public:
PVR_ERROR GetStreamProperties(PVR_STREAM_PROPERTIES* pProperties) const;
/*!
+ * @brief A stream was closed or has ended
+ * @return PVR_ERROR_NO_ERROR on success, respective error code otherwise.
+ */
+ PVR_ERROR StreamClosed() const;
+
+ /*!
* @return The name reported by the backend.
*/
const std::string& GetBackendName() const;
@@ -591,10 +595,12 @@ public:
/*!
* @brief Fill the given container with the properties required for playback of the given channel. Values are obtained from the PVR backend.
* @param channel The channel.
+ * @param source PVR_SOURCE_EPG_AS_LIVE if this call resulted from PVR_STREAM_PROPERTY_EPGPLAYBACKASLIVE being set from GetEPGTagStreamProperties(), DEFAULT otherwise.
* @param props The container to be filled with the stream properties.
* @return PVR_ERROR_NO_ERROR on success, respective error code otherwise.
*/
PVR_ERROR GetChannelStreamProperties(const std::shared_ptr<const CPVRChannel>& channel,
+ PVR_SOURCE source,
CPVRStreamProperties& props) const;
/*!
@@ -806,18 +812,24 @@ public:
void SetPriority(int iPriority);
/*!
+ * @brief Get the date and time first channels were added for this client.
+ * @return The date and time first channels were added.
+ */
+ const CDateTime& GetDateTimeFirstChannelsAdded() const;
+
+ /*!
+ * @brief Set the date and time first channels were added for this client.
+ * @param dateTime The date and time first channels were added.
+ */
+ void SetDateTimeFirstChannelsAdded(const CDateTime& dateTime);
+
+ /*!
* @brief Obtain the chunk size to use when reading streams.
* @param iChunkSize the chunk size in bytes.
* @return PVR_ERROR_NO_ERROR on success, respective error code otherwise.
*/
PVR_ERROR GetStreamReadChunkSize(int& iChunkSize) const;
- /*!
- * @brief Get the interface table used between addon and Kodi.
- * @todo This function will be removed after old callback library system is removed.
- */
- AddonInstance_PVR* GetInstanceInterface() { return m_ifc.pvr; }
-
private:
/*!
* @brief Resets all class members to their defaults, accept the client id.
@@ -1072,6 +1084,8 @@ private:
std::vector<std::shared_ptr<CPVRTimerType>>
m_timertypes; /*!< timer types supported by this backend */
mutable std::optional<int> m_priority; /*!< priority of the client */
+ mutable std::optional<CDateTime>
+ m_firstChannelsAdded; /*!< date and time the first channels were added for this client */
/* cached data */
std::string m_strBackendName; /*!< the cached backend version */
diff --git a/xbmc/pvr/addons/PVRClientCapabilities.cpp b/xbmc/pvr/addons/PVRClientCapabilities.cpp
index a0b1f6cfca..5b5196f22c 100644
--- a/xbmc/pvr/addons/PVRClientCapabilities.cpp
+++ b/xbmc/pvr/addons/PVRClientCapabilities.cpp
@@ -55,13 +55,13 @@ void CPVRClientCapabilities::InitRecordingsLifetimeValues()
{
const auto& lifetime{m_addonCapabilities->recordingsLifetimeValues[i]};
const int iValue{lifetime.iValue};
- std::string strDescr{lifetime.strDescription ? lifetime.strDescription : ""};
- if (strDescr.empty())
+ std::string description{lifetime.strDescription ? lifetime.strDescription : ""};
+ if (description.empty())
{
// No description given by addon. Create one from value.
- strDescr = std::to_string(iValue);
+ description = std::to_string(iValue);
}
- m_recordingsLifetimeValues.emplace_back(strDescr, iValue);
+ m_recordingsLifetimeValues.emplace_back(description, iValue);
}
}
else if (SupportsRecordingsLifetimeChange())
diff --git a/xbmc/pvr/addons/PVRClientUID.cpp b/xbmc/pvr/addons/PVRClientUID.cpp
index 87883851ea..f15ab87310 100644
--- a/xbmc/pvr/addons/PVRClientUID.cpp
+++ b/xbmc/pvr/addons/PVRClientUID.cpp
@@ -8,26 +8,69 @@
#include "PVRClientUID.h"
+#include "pvr/PVRConstants.h" // PVR_CLIENT_INVALID_UID
+#include "pvr/PVRDatabase.h"
+#include "utils/log.h"
+
#include <functional>
+#include <map>
+#include <utility>
using namespace PVR;
+namespace
+{
+using ClientUIDParts = std::pair<std::string, ADDON::AddonInstanceId>;
+static std::map<ClientUIDParts, int> s_idMap;
+} // unnamed namespace
+
int CPVRClientUID::GetUID() const
{
if (!m_uidCreated)
{
- std::hash<std::string> hasher;
+ const auto it = s_idMap.find({m_addonID, m_instanceID});
+ if (it != s_idMap.cend())
+ {
+ // Cache hit
+ m_uid = (*it).second;
+ }
+ else
+ {
+ // Cache miss. Read from db and cache.
+ CPVRDatabase db;
+ if (!db.Open())
+ {
+ CLog::LogF(LOGERROR, "Unable to open TV database!");
+ return PVR_CLIENT_INVALID_UID;
+ }
- // Note: For database backwards compatibility reasons the hash of the first instance
- // must be calculated just from the addonId, not from addonId and instanceId.
- m_uid = static_cast<int>(hasher(
- (m_instanceID > ADDON::ADDON_FIRST_INSTANCE_ID ? std::to_string(m_instanceID) + "@" : "") +
- m_addonID));
- if (m_uid < 0)
- m_uid = -m_uid;
+ m_uid = db.GetClientID(m_addonID, m_instanceID);
+ if (m_uid == PVR_CLIENT_INVALID_UID)
+ {
+ CLog::LogF(LOGERROR, "Unable to get client id from TV database!");
+ return PVR_CLIENT_INVALID_UID;
+ }
+ s_idMap.insert({{m_addonID, m_instanceID}, m_uid});
+ }
m_uidCreated = true;
}
return m_uid;
}
+
+int CPVRClientUID::GetLegacyUID() const
+{
+ // Note: For database backwards compatibility reasons the hash of the first instance
+ // must be calculated just from the addonId, not from addonId and instanceId.
+ std::string prefix;
+ if (m_instanceID > ADDON::ADDON_FIRST_INSTANCE_ID)
+ prefix = std::to_string(m_instanceID) + "@";
+
+ std::hash<std::string> hasher;
+ int uid{static_cast<int>(hasher(prefix + m_addonID))};
+ if (uid < 0)
+ uid = -uid;
+
+ return uid;
+}
diff --git a/xbmc/pvr/addons/PVRClientUID.h b/xbmc/pvr/addons/PVRClientUID.h
index 5b5d1c0507..a3c5fd31fc 100644
--- a/xbmc/pvr/addons/PVRClientUID.h
+++ b/xbmc/pvr/addons/PVRClientUID.h
@@ -26,10 +26,16 @@ public:
/*!
* @brief Return the numeric UID.
- * @return The numeric UID.
+ * @return The numeric UID, or PVR_CLIENT_INVALID_UID on error.
*/
int GetUID() const;
+ /*!
+ * @brief Return the numeric legacy UID (compatibility/migration purposes only).
+ * @return The numeric legacy UID.
+ */
+ int GetLegacyUID() const;
+
private:
CPVRClientUID() = delete;
diff --git a/xbmc/pvr/addons/PVRClients.cpp b/xbmc/pvr/addons/PVRClients.cpp
index 46cf2f510a..a67fe724ee 100644
--- a/xbmc/pvr/addons/PVRClients.cpp
+++ b/xbmc/pvr/addons/PVRClients.cpp
@@ -15,6 +15,7 @@
#include "addons/addoninfo/AddonType.h"
#include "guilib/LocalizeStrings.h"
#include "messaging/ApplicationMessenger.h"
+#include "pvr/PVRConstants.h" // PVR_CLIENT_INVALID_UID
#include "pvr/PVREventLogJob.h"
#include "pvr/PVRManager.h"
#include "pvr/PVRPlaybackState.h"
@@ -114,11 +115,6 @@ void CPVRClients::UpdateClients(
else
{
client = std::make_shared<CPVRClient>(addon, instanceId, clientId);
- if (!client)
- {
- CLog::LogF(LOGERROR, "Severe error, incorrect add-on type");
- continue;
- }
}
// determine actual enabled state of instance
@@ -126,9 +122,17 @@ void CPVRClients::UpdateClients(
instanceEnabled = client->IsEnabled();
if (instanceEnabled)
+ {
+ CLog::LogF(LOGINFO, "Creating PVR client: addonId={}, instanceId={}, clientId={}",
+ addon->ID(), instanceId, clientId);
clientsToCreate.emplace_back(client);
+ }
else if (isKnownClient)
+ {
+ CLog::LogF(LOGINFO, "Destroying PVR client: addonId={}, instanceId={}, clientId={}",
+ addon->ID(), instanceId, clientId);
clientsToDestroy.emplace_back(clientId);
+ }
}
else if (IsCreatedClient(clientId))
{
@@ -140,9 +144,17 @@ void CPVRClients::UpdateClients(
}
if (instanceEnabled)
+ {
+ CLog::LogF(LOGINFO, "Recreating PVR client: addonId={}, instanceId={}, clientId={}",
+ addon->ID(), instanceId, clientId);
clientsToReCreate.emplace_back(clientId, addon->Name());
+ }
else
+ {
+ CLog::LogF(LOGINFO, "Destroying PVR client: addonId={}, instanceId={}, clientId={}",
+ addon->ID(), instanceId, clientId);
clientsToDestroy.emplace_back(clientId);
+ }
}
}
}
@@ -158,8 +170,9 @@ void CPVRClients::UpdateClients(
unsigned int i = 0;
for (const auto& client : clientsToCreate)
{
- progressHandler->UpdateProgress(client->Name(), i++,
- clientsToCreate.size() + clientsToReCreate.size());
+ progressHandler->UpdateProgress(
+ client->Name(), i++,
+ static_cast<unsigned int>(clientsToCreate.size() + clientsToReCreate.size()));
const ADDON_STATUS status = client->Create();
@@ -179,8 +192,9 @@ void CPVRClients::UpdateClients(
for (const auto& clientInfo : clientsToReCreate)
{
- progressHandler->UpdateProgress(clientInfo.second, i++,
- clientsToCreate.size() + clientsToReCreate.size());
+ progressHandler->UpdateProgress(
+ clientInfo.second, i++,
+ static_cast<unsigned int>(clientsToCreate.size() + clientsToReCreate.size()));
// stop and recreate client
StopClient(clientInfo.first, true /* restart */);
@@ -279,7 +293,7 @@ void CPVRClients::OnAddonEvent(const AddonEvent& event)
std::shared_ptr<CPVRClient> CPVRClients::GetClient(int clientId) const
{
- if (clientId <= PVR_INVALID_CLIENT_ID)
+ if (clientId == PVR_CLIENT_INVALID_UID)
return {};
std::unique_lock<CCriticalSection> lock(m_critSection);
@@ -293,8 +307,9 @@ std::shared_ptr<CPVRClient> CPVRClients::GetClient(int clientId) const
int CPVRClients::CreatedClientAmount() const
{
std::unique_lock<CCriticalSection> lock(m_critSection);
- return std::count_if(m_clientMap.cbegin(), m_clientMap.cend(),
- [](const auto& client) { return client.second->ReadyToUse(); });
+ return static_cast<int>(std::count_if(m_clientMap.cbegin(), m_clientMap.cend(),
+ [](const auto& client)
+ { return client.second->ReadyToUse(); }));
}
bool CPVRClients::HasCreatedClients() const
@@ -358,9 +373,17 @@ std::vector<CVariant> CPVRClients::GetClientProviderInfos() const
for (const auto& instanceId : instanceIds)
{
CVariant clientProviderInfo(CVariant::VariantTypeObject);
- clientProviderInfo["clientid"] = CPVRClientUID(addonInfo->ID(), instanceId).GetUID();
+ const int clientId{CPVRClientUID(addonInfo->ID(), instanceId).GetUID()};
+ clientProviderInfo["clientid"] = clientId;
clientProviderInfo["addonid"] = addonInfo->ID();
clientProviderInfo["instanceid"] = instanceId;
+ std::string fullName;
+ const std::shared_ptr<const CPVRClient> client{GetClient(clientId)};
+ if (client)
+ fullName = client->GetFullClientName();
+ else
+ fullName = addonInfo->Name();
+ clientProviderInfo["fullname"] = fullName;
clientProviderInfo["enabled"] =
!CServiceBroker::GetAddonMgr().IsAddonDisabled(addonInfo->ID());
clientProviderInfo["name"] = addonInfo->Name();
@@ -382,7 +405,7 @@ int CPVRClients::GetFirstCreatedClientID() const
std::unique_lock<CCriticalSection> lock(m_critSection);
const auto it = std::find_if(m_clientMap.cbegin(), m_clientMap.cend(),
[](const auto& client) { return client.second->ReadyToUse(); });
- return it != m_clientMap.cend() ? (*it).second->GetID() : -1;
+ return it != m_clientMap.cend() ? (*it).second->GetID() : PVR_CLIENT_INVALID_UID;
}
PVR_ERROR CPVRClients::GetCallableClients(CPVRClientMap& clientsReady,
@@ -424,9 +447,9 @@ int CPVRClients::EnabledClientAmount() const
}
ADDON::CAddonMgr& addonMgr = CServiceBroker::GetAddonMgr();
- return std::count_if(clientMap.cbegin(), clientMap.cend(), [&addonMgr](const auto& client) {
- return !addonMgr.IsAddonDisabled(client.second->ID());
- });
+ return static_cast<int>(std::count_if(
+ clientMap.cbegin(), clientMap.cend(),
+ [&addonMgr](const auto& client) { return !addonMgr.IsAddonDisabled(client.second->ID()); }));
}
bool CPVRClients::IsEnabledClient(int clientId) const
diff --git a/xbmc/pvr/addons/PVRClients.h b/xbmc/pvr/addons/PVRClients.h
index edc720ffc5..a7724b9640 100644
--- a/xbmc/pvr/addons/PVRClients.h
+++ b/xbmc/pvr/addons/PVRClients.h
@@ -148,7 +148,7 @@ struct SBackend
/*!
* @brief Get the ID of the first created client.
- * @return the ID or -1 if no clients are created;
+ * @return the ID or PVR_CLIENT_INVALID_UID if no clients are created;
*/
int GetFirstCreatedClientID() const;
diff --git a/xbmc/pvr/channels/PVRChannel.h b/xbmc/pvr/channels/PVRChannel.h
index defe9ec9a8..785b3d56ae 100644
--- a/xbmc/pvr/channels/PVRChannel.h
+++ b/xbmc/pvr/channels/PVRChannel.h
@@ -12,6 +12,7 @@
#include "addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_channels.h"
#include "addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_providers.h"
#include "pvr/PVRCachedImage.h"
+#include "pvr/PVRConstants.h" // PVR_CLIENT_INVALID_UID
#include "pvr/channels/PVRChannelNumber.h"
#include "threads/CriticalSection.h"
#include "utils/ISerializable.h"
@@ -445,8 +446,8 @@ public:
int ClientOrder() const { return m_iClientOrder; }
/*!
- * @brief Get the client provider Uid for this channel
- * @return m_iClientProviderUid The provider Uid for this channel
+ * @brief Get the uid of the provider on the client which this channel is from
+ * @return the client uid of the provider or PVR_PROVIDER_INVALID_UID
*/
int ClientProviderUid() const { return m_iClientProviderUid; }
@@ -544,7 +545,8 @@ private:
*/
//@{
int m_iUniqueId = -1; /*!< the unique identifier for this channel */
- int m_iClientId = -1; /*!< the identifier of the client that serves this channel */
+ int m_iClientId =
+ PVR_CLIENT_INVALID_UID; /*!< the identifier of the client that serves this channel */
CPVRChannelNumber m_clientChannelNumber; /*!< the channel number on the client */
std::string m_strClientChannelName; /*!< the name of this channel on the client */
std::string
diff --git a/xbmc/pvr/channels/PVRChannelGroup.cpp b/xbmc/pvr/channels/PVRChannelGroup.cpp
index a7339ed1a9..d4d1176ad3 100644
--- a/xbmc/pvr/channels/PVRChannelGroup.cpp
+++ b/xbmc/pvr/channels/PVRChannelGroup.cpp
@@ -307,6 +307,33 @@ std::shared_ptr<CPVRChannel> CPVRChannelGroup::GetByChannelID(int iChannelID) co
return it != m_members.cend() ? (*it).second->Channel() : std::shared_ptr<CPVRChannel>();
}
+namespace
+{
+bool MatchProvider(const std::shared_ptr<CPVRChannel>& channel, int clientId, int providerId)
+{
+ return channel->ClientID() == clientId &&
+ (providerId == PVR_PROVIDER_INVALID_UID || channel->ClientProviderUid() == providerId);
+}
+} // unnamed namespace
+
+bool CPVRChannelGroup::HasChannelForProvider(int clientId, int providerId) const
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ return std::any_of(m_members.cbegin(), m_members.cend(),
+ [clientId, providerId](const auto& member)
+ { return MatchProvider(member.second->Channel(), clientId, providerId); });
+}
+
+unsigned int CPVRChannelGroup::GetChannelCountByProvider(int clientId, int providerId) const
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ auto channels =
+ std::count_if(m_members.cbegin(), m_members.cend(),
+ [clientId, providerId](const auto& member)
+ { return MatchProvider(member.second->Channel(), clientId, providerId); });
+ return static_cast<unsigned int>(channels);
+}
+
std::shared_ptr<CPVRChannelGroupMember> CPVRChannelGroup::GetLastPlayedChannelGroupMember(
int iCurrentChannel /* = -1 */) const
{
@@ -540,7 +567,7 @@ int CPVRChannelGroup::LoadFromDatabase(const std::vector<std::shared_ptr<CPVRCli
DeleteGroupMembersFromDb(membersToDelete);
- return results.size() - membersToDelete.size();
+ return static_cast<int>(results.size() - membersToDelete.size());
}
void CPVRChannelGroup::DeleteGroupMembersFromDb(
@@ -562,7 +589,7 @@ void CPVRChannelGroup::DeleteGroupMembersFromDb(
for (const auto& member : membersToDelete)
{
- commitPending |= member->QueueDelete();
+ commitPending |= database->QueueDeleteQuery(*member);
size_t queryCount = database->GetDeleteQueriesCount();
if (queryCount > CHANNEL_COMMIT_QUERY_COUNT_LIMIT)
@@ -783,9 +810,12 @@ bool CPVRChannelGroup::RemoveFromGroup(
}
}
- // no need to renumber if nothing was removed
+ // no need to delete and renumber if nothing was removed
if (bReturn)
+ {
+ DeleteGroupMembersFromDb({std::make_shared<CPVRChannelGroupMember>(*groupMember)});
Renumber();
+ }
return bReturn;
}
@@ -878,11 +908,6 @@ void CPVRChannelGroup::Delete()
}
}
-void CPVRChannelGroup::DeleteGroupMember(const std::shared_ptr<CPVRChannelGroupMember>& member)
-{
- DeleteGroupMembersFromDb({member});
-}
-
bool CPVRChannelGroup::Renumber(RenumberMode mode /* = NORMAL */)
{
bool bReturn(false);
diff --git a/xbmc/pvr/channels/PVRChannelGroup.h b/xbmc/pvr/channels/PVRChannelGroup.h
index 0ce1bed4fa..03348db1bd 100644
--- a/xbmc/pvr/channels/PVRChannelGroup.h
+++ b/xbmc/pvr/channels/PVRChannelGroup.h
@@ -377,6 +377,22 @@ public:
std::shared_ptr<CPVRChannelGroupMember> GetByUniqueID(const std::pair<int, int>& id) const;
/*!
+ * @brief Check whether at least one channel of this group is offered by the given provider.
+ * @param clientId The clientId to check.
+ * @param providerId The providerId to check.
+ * @return True, if the group countains at least one channel offered by the provider, false otherwise.
+ */
+ bool HasChannelForProvider(int clientId, int providerId) const;
+
+ /*!
+ * @brief Get the total count of channels of this group offered by the given provider.
+ * @param clientId The clientId of the provider.
+ * @param providerId The providerId.
+ * @return The total count of matching channels.
+ */
+ unsigned int GetChannelCountByProvider(int clientId, int providerId) const;
+
+ /*!
* @brief Set the hidden state of this group.
* @param bHidden True to set hidden state, false to unhide the group.
* @return True if hidden state was changed, false otherwise.
@@ -442,12 +458,6 @@ public:
void Delete();
/*!
- * @brief Remove the given group member from the database.
- * @param member The member to remove from the database.
- */
- void DeleteGroupMember(const std::shared_ptr<CPVRChannelGroupMember>& member);
-
- /*!
* @brief Whether this group is deleted.
* @return True, if deleted, false otherwise.
*/
diff --git a/xbmc/pvr/channels/PVRChannelGroupMember.cpp b/xbmc/pvr/channels/PVRChannelGroupMember.cpp
index de04a968cd..8d513f55a0 100644
--- a/xbmc/pvr/channels/PVRChannelGroupMember.cpp
+++ b/xbmc/pvr/channels/PVRChannelGroupMember.cpp
@@ -9,7 +9,6 @@
#include "PVRChannelGroupMember.h"
#include "ServiceBroker.h"
-#include "pvr/PVRDatabase.h"
#include "pvr/PVRManager.h"
#include "pvr/addons/PVRClient.h"
#include "pvr/channels/PVRChannel.h"
@@ -132,12 +131,3 @@ void CPVRChannelGroupMember::SetOrder(int iOrder)
m_bNeedsSave = true;
}
}
-
-bool CPVRChannelGroupMember::QueueDelete()
-{
- const std::shared_ptr<CPVRDatabase> database = CServiceBroker::GetPVRManager().GetTVDatabase();
- if (!database)
- return false;
-
- return database->QueueDeleteQuery(*this);
-}
diff --git a/xbmc/pvr/channels/PVRChannelGroupMember.h b/xbmc/pvr/channels/PVRChannelGroupMember.h
index f31b793634..9639d7072c 100644
--- a/xbmc/pvr/channels/PVRChannelGroupMember.h
+++ b/xbmc/pvr/channels/PVRChannelGroupMember.h
@@ -8,6 +8,7 @@
#pragma once
+#include "pvr/PVRConstants.h" // PVR_CLIENT_INVALID_UID
#include "pvr/channels/PVRChannelNumber.h"
#include "utils/ISerializable.h"
#include "utils/ISortable.h"
@@ -77,16 +78,10 @@ public:
bool IsRadio() const { return m_bIsRadio; }
- /*!
- * @brief Delete this group member from the database.
- * @return True if it was deleted successfully, false otherwise.
- */
- bool QueueDelete();
-
private:
int m_iGroupID = -1;
- int m_iGroupClientID = -1;
- int m_iChannelClientID = -1;
+ int m_iGroupClientID = PVR_CLIENT_INVALID_UID;
+ int m_iChannelClientID = PVR_CLIENT_INVALID_UID;
int m_iChannelUID = -1;
int m_iChannelDatabaseID = -1;
bool m_bIsRadio = false;
diff --git a/xbmc/pvr/channels/PVRChannelGroups.cpp b/xbmc/pvr/channels/PVRChannelGroups.cpp
index d15c8c530f..55af6dd8da 100644
--- a/xbmc/pvr/channels/PVRChannelGroups.cpp
+++ b/xbmc/pvr/channels/PVRChannelGroups.cpp
@@ -764,8 +764,6 @@ bool CPVRChannelGroups::RemoveFromGroup(const std::shared_ptr<CPVRChannelGroup>&
if (group->RemoveFromGroup(groupMember))
{
- group->DeleteGroupMember(groupMember);
-
// Changes in the all channels group may require resorting/renumbering of other groups.
if (group->IsChannelsOwner())
UpdateChannelNumbersFromAllChannelsGroup();
diff --git a/xbmc/pvr/channels/PVRChannelGroupsContainer.cpp b/xbmc/pvr/channels/PVRChannelGroupsContainer.cpp
index 13f53da56e..1da1ef5acb 100644
--- a/xbmc/pvr/channels/PVRChannelGroupsContainer.cpp
+++ b/xbmc/pvr/channels/PVRChannelGroupsContainer.cpp
@@ -168,6 +168,26 @@ std::shared_ptr<CPVRChannelGroupMember> CPVRChannelGroupsContainer::
return channelTV;
}
+bool CPVRChannelGroupsContainer::HasChannelForProvider(bool isRadio,
+ int clientId,
+ int providerId) const
+{
+ if (isRadio)
+ return m_groupsRadio->GetGroupAll()->HasChannelForProvider(clientId, providerId);
+ else
+ return m_groupsTV->GetGroupAll()->HasChannelForProvider(clientId, providerId);
+}
+
+unsigned int CPVRChannelGroupsContainer::GetChannelCountByProvider(bool isRadio,
+ int clientId,
+ int providerId) const
+{
+ if (isRadio)
+ return m_groupsRadio->GetGroupAll()->GetChannelCountByProvider(clientId, providerId);
+ else
+ return m_groupsTV->GetGroupAll()->GetChannelCountByProvider(clientId, providerId);
+}
+
int CPVRChannelGroupsContainer::CleanupCachedImages()
{
return m_groupsTV->CleanupCachedImages() + m_groupsRadio->CleanupCachedImages();
diff --git a/xbmc/pvr/channels/PVRChannelGroupsContainer.h b/xbmc/pvr/channels/PVRChannelGroupsContainer.h
index f0185f5331..80dd4653c8 100644
--- a/xbmc/pvr/channels/PVRChannelGroupsContainer.h
+++ b/xbmc/pvr/channels/PVRChannelGroupsContainer.h
@@ -153,6 +153,24 @@ public:
std::shared_ptr<CPVRChannelGroupMember> GetLastPlayedChannelGroupMember() const;
/*!
+ * @brief Check whether at least one channel is offered by the given provider.
+ * @param isRadio Check radio or TV channels.
+ * @param clientId The clientId to check.
+ * @param providerId The providerId to check.
+ * @return True, if at least one matching channel is offered by the provider, false otherwise.
+ */
+ bool HasChannelForProvider(bool isRadio, int clientId, int providerId) const;
+
+ /*!
+ * @brief Get the total count of channels offered by the given provider.
+ * @param isRadio Check radio or TV channels.
+ * @param clientId The clientId of the provider.
+ * @param providerId The providerId.
+ * @return The total count of matching channels.
+ */
+ unsigned int GetChannelCountByProvider(bool isRadio, int clientId, int providerId) const;
+
+ /*!
* @brief Erase stale texture db entries and image files.
* @return number of cleaned up images.
*/
diff --git a/xbmc/pvr/channels/PVRChannelsPath.cpp b/xbmc/pvr/channels/PVRChannelsPath.cpp
index 2dfca994a2..681895bda0 100644
--- a/xbmc/pvr/channels/PVRChannelsPath.cpp
+++ b/xbmc/pvr/channels/PVRChannelsPath.cpp
@@ -66,7 +66,7 @@ CPVRChannelsPath::CPVRChannelsPath(const std::string& strPath)
{
m_kind = Kind::GROUP; // pvr://channels/(tv|radio)/<all-channels-wildcard>@-1
m_groupName = segment;
- m_groupClientID = -1; // local
+ m_groupClientID = PVR_CLIENT_INVALID_UID; // local
break;
}
@@ -79,7 +79,7 @@ CPVRChannelsPath::CPVRChannelsPath(const std::string& strPath)
if (groupClientID.find_first_not_of("-0123456789") == std::string::npos)
{
m_groupClientID = std::atoi(groupClientID.c_str());
- if (m_groupClientID >= -1)
+ if (m_groupClientID >= PVR_CLIENT_INVALID_UID)
{
m_kind = Kind::GROUP; // pvr://channels/(tv|radio)/<groupname>@<clientid>
break;
@@ -195,7 +195,8 @@ CPVRChannelsPath::CPVRChannelsPath(bool bRadio,
int iChannelUID)
: m_bRadio(bRadio)
{
- if (!strGroupName.empty() && iGroupClientID >= -1 && !strAddonID.empty() && iChannelUID >= 0)
+ if (!strGroupName.empty() && iGroupClientID >= PVR_CLIENT_INVALID_UID && !strAddonID.empty() &&
+ iChannelUID >= 0)
{
m_kind = Kind::CHANNEL;
m_groupName = strGroupName;
diff --git a/xbmc/pvr/channels/PVRChannelsPath.h b/xbmc/pvr/channels/PVRChannelsPath.h
index d5545148a8..94c624a6f3 100644
--- a/xbmc/pvr/channels/PVRChannelsPath.h
+++ b/xbmc/pvr/channels/PVRChannelsPath.h
@@ -9,6 +9,7 @@
#pragma once
#include "addons/IAddon.h"
+#include "pvr/PVRConstants.h" // PVR_CLIENT_INVALID_UID
class CDateTime;
@@ -71,7 +72,7 @@ namespace PVR
bool m_bRadio = false;;
std::string m_path;
std::string m_groupName;
- int m_groupClientID{-1};
+ int m_groupClientID{PVR_CLIENT_INVALID_UID};
std::string m_addonID;
ADDON::AddonInstanceId m_instanceID{ADDON::ADDON_SINGLETON_INSTANCE_ID};
int m_iChannelUID = -1;
diff --git a/xbmc/pvr/channels/PVRRadioRDSInfoTag.cpp b/xbmc/pvr/channels/PVRRadioRDSInfoTag.cpp
index e1ee649deb..66930d6704 100644
--- a/xbmc/pvr/channels/PVRRadioRDSInfoTag.cpp
+++ b/xbmc/pvr/channels/PVRRadioRDSInfoTag.cpp
@@ -643,14 +643,14 @@ void CPVRRadioRDSInfoTag::SetProgramServiceText(const std::string& strPSText)
for (size_t i = m_strProgramServiceText.MaxSize() / 2 + 1; i < m_strProgramServiceText.MaxSize();
++i)
{
- m_strProgramServiceLine0 += m_strProgramServiceText.GetLine(i);
+ m_strProgramServiceLine0 += m_strProgramServiceText.GetLine(static_cast<unsigned int>(i));
m_strProgramServiceLine0 += ' ';
}
m_strProgramServiceLine1.erase();
for (size_t i = 0; i < m_strProgramServiceText.MaxSize() / 2; ++i)
{
- m_strProgramServiceLine1 += m_strProgramServiceText.GetLine(i);
+ m_strProgramServiceLine1 += m_strProgramServiceText.GetLine(static_cast<unsigned int>(i));
m_strProgramServiceLine1 += ' ';
}
}
diff --git a/xbmc/pvr/dialogs/GUIDialogPVRChannelManager.cpp b/xbmc/pvr/dialogs/GUIDialogPVRChannelManager.cpp
index d785219a78..fccedb6c68 100644
--- a/xbmc/pvr/dialogs/GUIDialogPVRChannelManager.cpp
+++ b/xbmc/pvr/dialogs/GUIDialogPVRChannelManager.cpp
@@ -840,8 +840,6 @@ void CGUIDialogPVRChannelManager::Update()
for (const auto& member : groupMembers)
{
channelFile = std::make_shared<CFileItem>(member);
- if (!channelFile)
- continue;
const std::shared_ptr<const CPVRChannel> channel(channelFile->GetPVRChannelInfoTag());
channelFile->SetProperty(PROPERTY_CHANNEL_ENABLED, !channel->IsHidden());
diff --git a/xbmc/pvr/dialogs/GUIDialogPVRGroupManager.cpp b/xbmc/pvr/dialogs/GUIDialogPVRGroupManager.cpp
index 32bc313d67..be65ed1ef5 100644
--- a/xbmc/pvr/dialogs/GUIDialogPVRGroupManager.cpp
+++ b/xbmc/pvr/dialogs/GUIDialogPVRGroupManager.cpp
@@ -264,6 +264,9 @@ bool CGUIDialogPVRGroupManager::ActionButtonUngroupedChannels(const CGUIMessage&
if (m_viewUngroupedChannels.HasControl(iControl)) // list/thumb control
{
+ if (!m_selectedGroup)
+ return bReturn;
+
m_iSelectedUngroupedChannel = m_viewUngroupedChannels.GetSelectedItem();
if (m_selectedGroup->SupportsMemberAdd())
{
@@ -304,6 +307,9 @@ bool CGUIDialogPVRGroupManager::ActionButtonGroupMembers(const CGUIMessage& mess
if (m_viewGroupMembers.HasControl(iControl)) // list/thumb control
{
+ if (!m_selectedGroup)
+ return bReturn;
+
m_iSelectedGroupMember = m_viewGroupMembers.GetSelectedItem();
if (m_selectedGroup->SupportsMemberRemove())
{
@@ -311,7 +317,7 @@ bool CGUIDialogPVRGroupManager::ActionButtonGroupMembers(const CGUIMessage& mess
if (actionID == ACTION_SELECT_ITEM || actionID == ACTION_MOUSE_LEFT_CLICK)
{
- if (m_selectedGroup && m_groupMembers->GetFileCount() > 0)
+ if (m_groupMembers->GetFileCount() > 0)
{
const auto itemChannel = m_groupMembers->Get(m_iSelectedGroupMember);
ClearGroupThumbnails(*itemChannel);
diff --git a/xbmc/pvr/dialogs/GUIDialogPVRGuideSearch.cpp b/xbmc/pvr/dialogs/GUIDialogPVRGuideSearch.cpp
index 5af1b8d789..b5cb4cf64a 100644
--- a/xbmc/pvr/dialogs/GUIDialogPVRGuideSearch.cpp
+++ b/xbmc/pvr/dialogs/GUIDialogPVRGuideSearch.cpp
@@ -51,6 +51,8 @@ using namespace PVR;
static constexpr int CONTROL_BTN_SAVE = 29;
static constexpr int CONTROL_BTN_IGNORE_FINISHED = 30;
static constexpr int CONTROL_BTN_IGNORE_FUTURE = 31;
+static constexpr int CONTROL_BTN_START_ANY_TIME = 32;
+static constexpr int CONTROL_BTN_END_ANY_TIME = 33;
CGUIDialogPVRGuideSearch::CGUIDialogPVRGuideSearch()
: CGUIDialog(WINDOW_DIALOG_PVR_GUIDE_SEARCH, "DialogPVRGuideSearch.xml")
@@ -225,6 +227,12 @@ bool CGUIDialogPVRGuideSearch::OnMessage(CGUIMessage& message)
UpdateChannelSpin();
return true;
}
+ else if (iControl == CONTROL_BTN_START_ANY_TIME || iControl == CONTROL_BTN_END_ANY_TIME)
+ {
+ UpdateSearchFilter();
+ Update();
+ return true;
+ }
}
break;
}
@@ -297,7 +305,8 @@ void CGUIDialogPVRGuideSearch::UpdateSearchFilter()
m_searchFilter->SetMaximumDuration(GetSpinValue(CONTROL_SPIN_MAX_DURATION));
auto it = m_channelsMap.find(GetSpinValue(CONTROL_SPIN_CHANNELS));
- m_searchFilter->SetClientID(it == m_channelsMap.end() ? -1 : (*it).second->ChannelClientID());
+ m_searchFilter->SetClientID(it == m_channelsMap.end() ? PVR_CLIENT_INVALID_UID
+ : (*it).second->ChannelClientID());
m_searchFilter->SetChannelUID(it == m_channelsMap.end() ? -1 : (*it).second->ChannelUID());
m_searchFilter->SetChannelGroupID(GetSpinValue(CONTROL_SPIN_GROUPS));
@@ -315,6 +324,9 @@ void CGUIDialogPVRGuideSearch::UpdateSearchFilter()
m_searchFilter->SetEndDateTime(end);
m_endDateTime = end;
}
+
+ m_searchFilter->SetStartAnyTime(IsRadioSelected(CONTROL_BTN_START_ANY_TIME));
+ m_searchFilter->SetEndAnyTime(IsRadioSelected(CONTROL_BTN_END_ANY_TIME));
}
void CGUIDialogPVRGuideSearch::Update()
@@ -339,6 +351,10 @@ void CGUIDialogPVRGuideSearch::Update()
m_searchFilter->ShouldIgnoreFinishedBroadcasts());
SET_CONTROL_SELECTED(GetID(), CONTROL_BTN_IGNORE_FUTURE,
m_searchFilter->ShouldIgnoreFutureBroadcasts());
+ SET_CONTROL_SELECTED(GetID(), CONTROL_BTN_START_ANY_TIME, m_searchFilter->IsStartAnyTime());
+ SET_CONTROL_SELECTED(GetID(), CONTROL_BTN_END_ANY_TIME, m_searchFilter->IsEndAnyTime());
+ CONTROL_ENABLE_ON_CONDITION(CONTROL_EDIT_START_TIME, !m_searchFilter->IsStartAnyTime());
+ CONTROL_ENABLE_ON_CONDITION(CONTROL_EDIT_STOP_TIME, !m_searchFilter->IsEndAnyTime());
// Set start/end datetime fields
m_startDateTime = m_searchFilter->GetStartDateTime();
diff --git a/xbmc/pvr/dialogs/GUIDialogPVRRadioRDSInfo.cpp b/xbmc/pvr/dialogs/GUIDialogPVRRadioRDSInfo.cpp
index 6d73450e4e..48eb10b9c7 100644
--- a/xbmc/pvr/dialogs/GUIDialogPVRRadioRDSInfo.cpp
+++ b/xbmc/pvr/dialogs/GUIDialogPVRRadioRDSInfo.cpp
@@ -81,37 +81,41 @@ bool CGUIDialogPVRRadioRDSInfo::OnMessage(CGUIMessage& message)
if (!textbox)
return false;
+ std::string text;
switch (spin->GetValue())
{
case INFO_NEWS:
- textbox->SetInfo(currentRDS->GetInfoNews());
+ text = currentRDS->GetInfoNews();
break;
case INFO_NEWS_LOCAL:
- textbox->SetInfo(currentRDS->GetInfoNewsLocal());
+ text = currentRDS->GetInfoNewsLocal();
break;
case INFO_SPORT:
- textbox->SetInfo(currentRDS->GetInfoSport());
+ text = currentRDS->GetInfoSport();
break;
case INFO_WEATHER:
- textbox->SetInfo(currentRDS->GetInfoWeather());
+ text = currentRDS->GetInfoWeather();
break;
case INFO_LOTTERY:
- textbox->SetInfo(currentRDS->GetInfoLottery());
+ text = currentRDS->GetInfoLottery();
break;
case INFO_STOCK:
- textbox->SetInfo(currentRDS->GetInfoStock());
+ text = currentRDS->GetInfoStock();
break;
case INFO_OTHER:
- textbox->SetInfo(currentRDS->GetInfoOther());
+ text = currentRDS->GetInfoOther();
break;
case INFO_CINEMA:
- textbox->SetInfo(currentRDS->GetInfoCinema());
+ text = currentRDS->GetInfoCinema();
break;
case INFO_HOROSCOPE:
- textbox->SetInfo(currentRDS->GetInfoHoroscope());
+ text = currentRDS->GetInfoHoroscope();
break;
}
+ if (!text.empty())
+ textbox->SetInfo(KODI::GUILIB::GUIINFO::CGUIInfoLabel{text});
+
SET_CONTROL_VISIBLE(CONTROL_INFO_LIST);
}
}
@@ -210,7 +214,7 @@ bool CGUIDialogPVRRadioRDSInfo::InfoControl::Update(const std::string& textboxVa
{
m_spinControl->SetValue(m_iSpinControlId);
m_textboxValue = textboxValue;
- m_textbox->SetInfo(textboxValue);
+ m_textbox->SetInfo(KODI::GUILIB::GUIINFO::CGUIInfoLabel{textboxValue});
return true;
}
}
diff --git a/xbmc/pvr/dialogs/GUIDialogPVRTimerSettings.cpp b/xbmc/pvr/dialogs/GUIDialogPVRTimerSettings.cpp
index 9f9ebf268a..33a9d83266 100644
--- a/xbmc/pvr/dialogs/GUIDialogPVRTimerSettings.cpp
+++ b/xbmc/pvr/dialogs/GUIDialogPVRTimerSettings.cpp
@@ -13,6 +13,7 @@
#include "guilib/GUIMessage.h"
#include "guilib/LocalizeStrings.h"
#include "messaging/helpers/DialogOKHelper.h"
+#include "pvr/PVRConstants.h" // PVR_CLIENT_INVALID_UID
#include "pvr/PVRManager.h"
#include "pvr/addons/PVRClient.h"
#include "pvr/addons/PVRClients.h"
@@ -21,6 +22,7 @@
#include "pvr/channels/PVRChannelGroupMember.h"
#include "pvr/channels/PVRChannelGroupsContainer.h"
#include "pvr/epg/EpgInfoTag.h"
+#include "pvr/settings/PVRCustomTimerSettings.h"
#include "pvr/timers/PVRTimerInfoTag.h"
#include "pvr/timers/PVRTimerType.h"
#include "settings/SettingUtils.h"
@@ -147,6 +149,9 @@ void CGUIDialogPVRTimerSettings::SetTimer(const std::shared_ptr<CPVRTimerInfoTag
InitializeChannelsList();
InitializeTypesList();
+ m_customTimerSettings = std::make_unique<CPVRCustomTimerSettings>(
+ *m_timerType, m_timerInfoTag->m_customProps, m_typeEntries);
+
// Channel
m_channel = ChannelDescriptor();
@@ -399,6 +404,55 @@ void CGUIDialogPVRTimerSettings::InitializeSettings()
RecordingGroupFiller, 811);
AddTypeDependentVisibilityCondition(setting, SETTING_TMR_REC_GROUP);
AddTypeDependentEnableCondition(setting, SETTING_TMR_REC_GROUP);
+
+ // Add-on supplied custom settings
+ m_customTimerSettings->AddSettings(*this, group);
+}
+
+void CGUIDialogPVRTimerSettings::AddMultiIntSetting(const std::shared_ptr<CSettingGroup>& group,
+ const std::string& settingName,
+ int settingValue)
+{
+ const std::shared_ptr<CSetting> setting{AddList(group, settingName, 16028, SettingLevel::Basic,
+ settingValue, CustomIntSettingDefinitionsFiller,
+ 16028)};
+ AddTypeDependentVisibilityCondition(setting, settingName);
+ AddTypeDependentEnableCondition(setting, settingName);
+}
+
+void CGUIDialogPVRTimerSettings::AddSingleIntSetting(const std::shared_ptr<CSettingGroup>& group,
+ const std::string& settingName,
+ int settingValue,
+ int minValue,
+ int step,
+ int maxValue)
+{
+ const std::shared_ptr<CSetting> setting{AddEdit(group, settingName, 16028, SettingLevel::Basic,
+ settingValue, minValue, step, maxValue)};
+ AddTypeDependentVisibilityCondition(setting, settingName);
+ AddTypeDependentEnableCondition(setting, settingName);
+}
+
+void CGUIDialogPVRTimerSettings::AddMultiStringSetting(const std::shared_ptr<CSettingGroup>& group,
+ const std::string& settingName,
+ const std::string& settingValue)
+{
+ const std::shared_ptr<CSetting> setting{AddList(group, settingName, 16028, SettingLevel::Basic,
+ settingValue,
+ CustomStringSettingDefinitionsFiller, 16028)};
+ AddTypeDependentVisibilityCondition(setting, settingName);
+ AddTypeDependentEnableCondition(setting, settingName);
+}
+
+void CGUIDialogPVRTimerSettings::AddSingleStringSetting(const std::shared_ptr<CSettingGroup>& group,
+ const std::string& settingName,
+ const std::string& settingValue,
+ bool allowEmptyValue)
+{
+ const std::shared_ptr<CSetting> setting{
+ AddEdit(group, settingName, 16028, SettingLevel::Basic, settingValue, allowEmptyValue)};
+ AddTypeDependentVisibilityCondition(setting, settingName);
+ AddTypeDependentEnableCondition(setting, settingName);
}
int CGUIDialogPVRTimerSettings::GetWeekdaysFromSetting(const SettingConstPtr& setting)
@@ -444,6 +498,7 @@ void CGUIDialogPVRTimerSettings::OnSettingChanged(const std::shared_ptr<const CS
if (it != m_typeEntries.end())
{
m_timerType = it->second;
+ m_customTimerSettings->SetTimerType(*m_timerType);
// reset certain settings to the defaults of the new timer type
@@ -558,6 +613,14 @@ void CGUIDialogPVRTimerSettings::OnSettingChanged(const std::shared_ptr<const CS
{
m_iRecordingGroup = std::static_pointer_cast<const CSettingInt>(setting)->GetValue();
}
+ else if (m_customTimerSettings->IsCustomIntSetting(settingId))
+ {
+ m_customTimerSettings->UpdateIntProperty(setting);
+ }
+ else if (m_customTimerSettings->IsCustomStringSetting(settingId))
+ {
+ m_customTimerSettings->UpdateStringProperty(setting);
+ }
}
void CGUIDialogPVRTimerSettings::OnSettingAction(const std::shared_ptr<const CSetting>& setting)
@@ -628,6 +691,16 @@ bool CGUIDialogPVRTimerSettings::Validate()
return true;
}
+std::string CGUIDialogPVRTimerSettings::GetSettingsLabel(const std::shared_ptr<ISetting>& setting)
+{
+ // Special handling for add-on supplied custom settings.
+ const std::string label{m_customTimerSettings->GetSettingsLabel(setting->GetId())};
+ if (!label.empty())
+ return label;
+
+ return CGUIDialogSettingsManualBase::GetSettingsLabel(setting);
+}
+
bool CGUIDialogPVRTimerSettings::Save()
{
if (!Validate())
@@ -739,6 +812,9 @@ bool CGUIDialogPVRTimerSettings::Save()
// Recording group
m_timerInfoTag->m_iRecordingGroup = m_iRecordingGroup;
+ // Custom properties
+ m_timerInfoTag->m_customProps = m_customTimerSettings->GetProperties();
+
// Set the timer's title to the channel name if it's empty or 'New Timer'
if (m_strTitle.empty() || m_strTitle == g_localizeStrings.Get(19056))
{
@@ -888,9 +964,10 @@ void CGUIDialogPVRTimerSettings::InitializeChannelsList()
// and for reminder rules another one representing any channel from any client.
const CPVRClientMap clients = CServiceBroker::GetPVRManager().Clients()->GetCreatedClients();
if (clients.size() > 1)
- m_channelEntries.insert({index++, ChannelDescriptor(PVR_CHANNEL_INVALID_UID, PVR_ANY_CLIENT_ID,
- // Any channel from any client
- g_localizeStrings.Get(854))});
+ m_channelEntries.insert(
+ {index++, ChannelDescriptor(PVR_CHANNEL_INVALID_UID, PVR_CLIENT_INVALID_UID,
+ // Any channel from any client
+ g_localizeStrings.Get(854))});
for (const auto& client : clients)
{
@@ -977,7 +1054,7 @@ void CGUIDialogPVRTimerSettings::ChannelsFiller(const SettingConstPtr& setting,
for (const auto& channelEntry : pThis->m_channelEntries)
{
// Only include channels for the currently selected timer type or all channels if type is client-independent.
- if (pThis->m_timerType->GetClientId() == PVR_ANY_CLIENT_ID || // client-independent
+ if (pThis->m_timerType->GetClientId() == PVR_CLIENT_INVALID_UID || // client-independent
pThis->m_timerType->GetClientId() == channelEntry.second.clientId)
{
// Do not add "any channel" entry if not supported by selected timer type.
@@ -987,7 +1064,7 @@ void CGUIDialogPVRTimerSettings::ChannelsFiller(const SettingConstPtr& setting,
// Do not add "any channel from any client" entry for reminder rules.
if (channelEntry.second.channelUid == PVR_CHANNEL_INVALID_UID &&
- channelEntry.second.clientId == PVR_ANY_CLIENT_ID &&
+ channelEntry.second.clientId == PVR_CLIENT_INVALID_UID &&
!pThis->m_timerType->IsReminder() && !pThis->m_timerType->IsTimerRule())
continue;
@@ -1089,8 +1166,8 @@ void CGUIDialogPVRTimerSettings::DupEpisodesFiller(const SettingConstPtr& settin
{
list.clear();
- std::vector<std::pair<std::string, int>> values;
- pThis->m_timerType->GetPreventDuplicateEpisodesValues(values);
+ const std::vector<SettingIntValue>& values{
+ pThis->m_timerType->GetPreventDuplicateEpisodesValues()};
std::transform(values.cbegin(), values.cend(), std::back_inserter(list), [](const auto& value) {
return IntegerSettingOption(value.first, value.second);
});
@@ -1134,8 +1211,7 @@ void CGUIDialogPVRTimerSettings::PrioritiesFiller(const SettingConstPtr& setting
{
list.clear();
- std::vector<std::pair<std::string, int>> values;
- pThis->m_timerType->GetPriorityValues(values);
+ const std::vector<SettingIntValue>& values{pThis->m_timerType->GetPriorityValues()};
std::transform(values.cbegin(), values.cend(), std::back_inserter(list), [](const auto& value) {
return IntegerSettingOption(value.first, value.second);
});
@@ -1171,8 +1247,7 @@ void CGUIDialogPVRTimerSettings::LifetimesFiller(const SettingConstPtr& setting,
{
list.clear();
- std::vector<std::pair<std::string, int>> values;
- pThis->m_timerType->GetLifetimeValues(values);
+ const std::vector<SettingIntValue>& values{pThis->m_timerType->GetLifetimeValues()};
std::transform(values.cbegin(), values.cend(), std::back_inserter(list), [](const auto& value) {
return IntegerSettingOption(value.first, value.second);
});
@@ -1210,8 +1285,7 @@ void CGUIDialogPVRTimerSettings::MaxRecordingsFiller(const SettingConstPtr& sett
{
list.clear();
- std::vector<std::pair<std::string, int>> values;
- pThis->m_timerType->GetMaxRecordingsValues(values);
+ const std::vector<SettingIntValue>& values{pThis->m_timerType->GetMaxRecordingsValues()};
std::transform(values.cbegin(), values.cend(), std::back_inserter(list), [](const auto& value) {
return IntegerSettingOption(value.first, value.second);
});
@@ -1247,8 +1321,7 @@ void CGUIDialogPVRTimerSettings::RecordingGroupFiller(const SettingConstPtr& set
{
list.clear();
- std::vector<std::pair<std::string, int>> values;
- pThis->m_timerType->GetRecordingGroupValues(values);
+ const std::vector<SettingIntValue>& values{pThis->m_timerType->GetRecordingGroupValues()};
std::transform(values.cbegin(), values.cend(), std::back_inserter(list), [](const auto& value) {
return IntegerSettingOption(value.first, value.second);
});
@@ -1305,6 +1378,44 @@ void CGUIDialogPVRTimerSettings::MarginTimeFiller(const SettingConstPtr& setting
CLog::LogF(LOGERROR, "No dialog");
}
+void CGUIDialogPVRTimerSettings::CustomIntSettingDefinitionsFiller(
+ const std::shared_ptr<const CSetting>& setting,
+ std::vector<IntegerSettingOption>& list,
+ int& current,
+ void* data)
+{
+ CGUIDialogPVRTimerSettings* pThis = static_cast<CGUIDialogPVRTimerSettings*>(data);
+ if (pThis)
+ {
+ list.clear();
+
+ const std::string settingId{setting->GetId()};
+ if (pThis->m_customTimerSettings->IsCustomIntSetting(settingId))
+ pThis->m_customTimerSettings->IntSettingDefinitionsFiller(settingId, list, current);
+ }
+ else
+ CLog::LogF(LOGERROR, "No dialog");
+}
+
+void CGUIDialogPVRTimerSettings::CustomStringSettingDefinitionsFiller(
+ const std::shared_ptr<const CSetting>& setting,
+ std::vector<StringSettingOption>& list,
+ std::string& current,
+ void* data)
+{
+ CGUIDialogPVRTimerSettings* pThis = static_cast<CGUIDialogPVRTimerSettings*>(data);
+ if (pThis)
+ {
+ list.clear();
+
+ const std::string settingId{setting->GetId()};
+ if (pThis->m_customTimerSettings->IsCustomStringSetting(settingId))
+ pThis->m_customTimerSettings->StringSettingDefinitionsFiller(settingId, list, current);
+ }
+ else
+ CLog::LogF(LOGERROR, "No dialog");
+}
+
std::string CGUIDialogPVRTimerSettings::WeekdaysValueFormatter(const SettingConstPtr& setting)
{
return CPVRTimerInfoTag::GetWeekdaysString(GetWeekdaysFromSetting(setting), true, true);
@@ -1363,6 +1474,21 @@ bool CGUIDialogPVRTimerSettings::TypeReadOnlyCondition(const std::string& condit
return true;
}
+ /* Handle recordings in progress. */
+ if (pThis->m_timerInfoTag->State() == PVR_TIMER_STATE_RECORDING)
+ {
+ if (cond == SETTING_TMR_TYPE || cond == SETTING_TMR_CHANNEL || cond == SETTING_TMR_BEGIN_PRE ||
+ cond == SETTING_TMR_START_DAY || cond == SETTING_TMR_BEGIN ||
+ cond == SETTING_TMR_PRIORITY || cond == SETTING_TMR_DIR)
+ return false;
+ }
+
+ if (pThis->m_customTimerSettings->IsCustomSetting(cond))
+ {
+ return !pThis->m_customTimerSettings->IsSettingReadonlyForTimerState(
+ cond, pThis->m_timerInfoTag->State());
+ }
+
// Let the PVR client decide...
int idx = std::static_pointer_cast<const CSettingInt>(setting)->GetValue();
const auto entry = pThis->m_typeEntries.find(idx);
@@ -1449,6 +1575,8 @@ bool CGUIDialogPVRTimerSettings::TypeSupportsCondition(const std::string& condit
return entry->second->SupportsRecordingFolders();
else if (cond == SETTING_TMR_REC_GROUP)
return entry->second->SupportsRecordingGroup();
+ else if (pThis->m_customTimerSettings->IsCustomSetting(cond))
+ return pThis->m_customTimerSettings->IsSettingSupportedForTimerType(cond, *entry->second);
else
CLog::LogF(LOGERROR, "Unknown condition");
}
diff --git a/xbmc/pvr/dialogs/GUIDialogPVRTimerSettings.h b/xbmc/pvr/dialogs/GUIDialogPVRTimerSettings.h
index d3e9e3eadf..d0a3f100ea 100644
--- a/xbmc/pvr/dialogs/GUIDialogPVRTimerSettings.h
+++ b/xbmc/pvr/dialogs/GUIDialogPVRTimerSettings.h
@@ -10,6 +10,8 @@
#include "XBDateTime.h"
#include "addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_channels.h" // PVR_CHANNEL_INVALID_UID
+#include "pvr/PVRConstants.h" // PVR_CLIENT_INVALID_UID
+#include "pvr/settings/IPVRSettingsContainer.h"
#include "settings/SettingConditions.h"
#include "settings/dialogs/GUIDialogSettingsManualBase.h"
#include "settings/lib/SettingDependency.h"
@@ -22,13 +24,15 @@
class CSetting;
struct IntegerSettingOption;
+struct StringSettingOption;
namespace PVR
{
+class CPVRCustomTimerSettings;
class CPVRTimerInfoTag;
class CPVRTimerType;
-class CGUIDialogPVRTimerSettings : public CGUIDialogSettingsManualBase
+class CGUIDialogPVRTimerSettings : public CGUIDialogSettingsManualBase, public IPVRSettingsContainer
{
public:
CGUIDialogPVRTimerSettings();
@@ -38,6 +42,24 @@ public:
void SetTimer(const std::shared_ptr<CPVRTimerInfoTag>& timer);
+ // implementation of IPVRSettingsContainer
+ void AddMultiIntSetting(const std::shared_ptr<CSettingGroup>& group,
+ const std::string& settingName,
+ int settingValue) override;
+ void AddSingleIntSetting(const std::shared_ptr<CSettingGroup>& group,
+ const std::string& settingName,
+ int settingValue,
+ int minValue,
+ int step,
+ int maxValue) override;
+ void AddMultiStringSetting(const std::shared_ptr<CSettingGroup>& group,
+ const std::string& settingName,
+ const std::string& settingValue) override;
+ void AddSingleStringSetting(const std::shared_ptr<CSettingGroup>& group,
+ const std::string& settingName,
+ const std::string& settingValue,
+ bool allowEmptyValue) override;
+
protected:
// implementation of ISettingCallback
void OnSettingChanged(const std::shared_ptr<const CSetting>& setting) override;
@@ -45,6 +67,7 @@ protected:
// specialization of CGUIDialogSettingsBase
bool AllowResettingSettings() const override { return false; }
+ std::string GetSettingsLabel(const std::shared_ptr<ISetting>& setting) override;
bool Save() override;
void SetupView() override;
@@ -103,6 +126,14 @@ private:
std::vector<IntegerSettingOption>& list,
int& current,
void* data);
+ static void CustomIntSettingDefinitionsFiller(const std::shared_ptr<const CSetting>& setting,
+ std::vector<IntegerSettingOption>& list,
+ int& current,
+ void* data);
+ static void CustomStringSettingDefinitionsFiller(const std::shared_ptr<const CSetting>& setting,
+ std::vector<StringSettingOption>& list,
+ std::string& current,
+ void* data);
static std::string WeekdaysValueFormatter(const std::shared_ptr<const CSetting>& setting);
@@ -148,7 +179,7 @@ private:
std::string description;
ChannelDescriptor(int _channelUid = PVR_CHANNEL_INVALID_UID,
- int _clientId = -1,
+ int _clientId = PVR_CLIENT_INVALID_UID,
const std::string& _description = "")
: channelUid(_channelUid), clientId(_clientId), description(_description)
{
@@ -164,6 +195,8 @@ private:
typedef std::map<int, ChannelDescriptor> ChannelEntriesMap;
+ std::unique_ptr<CPVRCustomTimerSettings> m_customTimerSettings;
+
std::shared_ptr<CPVRTimerInfoTag> m_timerInfoTag;
TypeEntriesMap m_typeEntries;
ChannelEntriesMap m_channelEntries;
diff --git a/xbmc/pvr/epg/CMakeLists.txt b/xbmc/pvr/epg/CMakeLists.txt
index 0ea75b702a..ee5e7f4015 100644
--- a/xbmc/pvr/epg/CMakeLists.txt
+++ b/xbmc/pvr/epg/CMakeLists.txt
@@ -2,6 +2,7 @@ set(SOURCES EpgContainer.cpp
Epg.cpp
EpgDatabase.cpp
EpgInfoTag.cpp
+ EpgSearch.cpp
EpgSearchFilter.cpp
EpgSearchPath.cpp
EpgChannelData.cpp
@@ -12,6 +13,7 @@ set(HEADERS Epg.h
EpgContainer.h
EpgDatabase.h
EpgInfoTag.h
+ EpgSearch.h
EpgSearchData.h
EpgSearchFilter.h
EpgSearchPath.h
diff --git a/xbmc/pvr/epg/Epg.cpp b/xbmc/pvr/epg/Epg.cpp
index 5b895ad042..8bc6045d20 100644
--- a/xbmc/pvr/epg/Epg.cpp
+++ b/xbmc/pvr/epg/Epg.cpp
@@ -223,7 +223,8 @@ bool CPVREpg::UpdateEntry(const std::shared_ptr<CPVREpgInfoTag>& tag, EPG_EVENT_
}
else
{
- if (IsTagExpired(existingTag))
+ // Delete future events and past events if they are older than epg linger time setting
+ if ((existingTag->StartAsUTC() > CDateTime::GetUTCDateTime()) || IsTagExpired(existingTag))
{
m_tags.DeleteEntry(existingTag);
}
@@ -533,7 +534,8 @@ bool CPVREpg::IsValid() const
{
std::unique_lock<CCriticalSection> lock(m_critSection);
if (ScraperName() == "client")
- return m_channelData->ClientId() != -1 && m_channelData->UniqueClientChannelId() != PVR_CHANNEL_INVALID_UID;
+ return m_channelData->ClientId() != PVR_CLIENT_INVALID_UID &&
+ m_channelData->UniqueClientChannelId() != PVR_CHANNEL_INVALID_UID;
return true;
}
diff --git a/xbmc/pvr/epg/EpgChannelData.h b/xbmc/pvr/epg/EpgChannelData.h
index a2a63ec9ed..237bc2ff55 100644
--- a/xbmc/pvr/epg/EpgChannelData.h
+++ b/xbmc/pvr/epg/EpgChannelData.h
@@ -8,6 +8,8 @@
#pragma once
+#include "pvr/PVRConstants.h" // PVR_CLIENT_INVALID_UID
+
#include <ctime>
#include <string>
@@ -46,7 +48,7 @@ public:
private:
const bool m_bIsRadio = false;
- const int m_iClientId = -1;
+ const int m_iClientId = PVR_CLIENT_INVALID_UID;
const int m_iUniqueClientChannelId = -1;
bool m_bIsHidden = false;
diff --git a/xbmc/pvr/epg/EpgContainer.cpp b/xbmc/pvr/epg/EpgContainer.cpp
index 4e20f790b1..d2d744401b 100644
--- a/xbmc/pvr/epg/EpgContainer.cpp
+++ b/xbmc/pvr/epg/EpgContainer.cpp
@@ -40,7 +40,7 @@ namespace PVR
class CEpgUpdateRequest
{
public:
- CEpgUpdateRequest() : CEpgUpdateRequest(-1, PVR_CHANNEL_INVALID_UID) {}
+ CEpgUpdateRequest() : CEpgUpdateRequest(PVR_CLIENT_INVALID_UID, PVR_CHANNEL_INVALID_UID) {}
CEpgUpdateRequest(int iClientID, int iUniqueChannelID) : m_iClientID(iClientID), m_iUniqueChannelID(iUniqueChannelID) {}
void Deliver(const std::shared_ptr<CPVREpg>& epg);
@@ -759,7 +759,7 @@ bool CPVREpgContainer::UpdateEPG(bool bOnlyPending /* = false */)
if (progressHandler)
progressHandler->UpdateProgress(epg->GetChannelData()->ChannelName(), ++iCounter,
- epgsToUpdate.size());
+ static_cast<unsigned int>(epgsToUpdate.size()));
if ((!bOnlyPending || epg->UpdatePending()) &&
epg->Update(start,
@@ -953,7 +953,8 @@ int CPVREpgContainer::CleanupCachedImages()
});
}
-std::vector<std::shared_ptr<CPVREpgSearchFilter>> CPVREpgContainer::GetSavedSearches(bool bRadio)
+std::vector<std::shared_ptr<CPVREpgSearchFilter>> CPVREpgContainer::GetSavedSearches(
+ bool bRadio) const
{
const std::shared_ptr<const CPVREpgDatabase> database = GetEpgDatabase();
if (!database)
@@ -965,7 +966,8 @@ std::vector<std::shared_ptr<CPVREpgSearchFilter>> CPVREpgContainer::GetSavedSear
return database->GetSavedSearches(bRadio);
}
-std::shared_ptr<CPVREpgSearchFilter> CPVREpgContainer::GetSavedSearchById(bool bRadio, int iId)
+std::shared_ptr<CPVREpgSearchFilter> CPVREpgContainer::GetSavedSearchById(bool bRadio,
+ int iId) const
{
const std::shared_ptr<const CPVREpgDatabase> database = GetEpgDatabase();
if (!database)
diff --git a/xbmc/pvr/epg/EpgContainer.h b/xbmc/pvr/epg/EpgContainer.h
index 0b6c426b61..dbfb5acf6b 100644
--- a/xbmc/pvr/epg/EpgContainer.h
+++ b/xbmc/pvr/epg/EpgContainer.h
@@ -224,7 +224,7 @@ namespace PVR
* @param bRadio Whether to fetch saved searches for radio or TV.
* @return The searches.
*/
- std::vector<std::shared_ptr<CPVREpgSearchFilter>> GetSavedSearches(bool bRadio);
+ std::vector<std::shared_ptr<CPVREpgSearchFilter>> GetSavedSearches(bool bRadio) const;
/*!
* @brief Get the saved search matching the given id.
@@ -232,7 +232,7 @@ namespace PVR
* @param iId The id.
* @return The saved search or nullptr if not found.
*/
- std::shared_ptr<CPVREpgSearchFilter> GetSavedSearchById(bool bRadio, int iId);
+ std::shared_ptr<CPVREpgSearchFilter> GetSavedSearchById(bool bRadio, int iId) const;
/*!
* @brief Persist a saved search in the database.
diff --git a/xbmc/pvr/epg/EpgDatabase.cpp b/xbmc/pvr/epg/EpgDatabase.cpp
index dbe3a49c3f..17c4d4354d 100644
--- a/xbmc/pvr/epg/EpgDatabase.cpp
+++ b/xbmc/pvr/epg/EpgDatabase.cpp
@@ -66,38 +66,38 @@ void CPVREpgDatabase::CreateTables()
);
CLog::LogFC(LOGDEBUG, LOGEPG, "Creating table 'epgtags'");
- m_pDS->exec(
- "CREATE TABLE epgtags ("
- "idBroadcast integer primary key, "
- "iBroadcastUid integer, "
- "idEpg integer, "
- "sTitle varchar(128), "
- "sPlotOutline text, "
- "sPlot text, "
- "sOriginalTitle varchar(128), "
- "sCast varchar(255), "
- "sDirector varchar(255), "
- "sWriter varchar(255), "
- "iYear integer, "
- "sIMDBNumber varchar(50), "
- "sIconPath varchar(255), "
- "iStartTime integer, "
- "iEndTime integer, "
- "iGenreType integer, "
- "iGenreSubType integer, "
- "sGenre varchar(128), "
- "sFirstAired varchar(32), "
- "iParentalRating integer, "
- "iStarRating integer, "
- "iSeriesId integer, "
- "iEpisodeId integer, "
- "iEpisodePart integer, "
- "sEpisodeName varchar(128), "
- "iFlags integer, "
- "sSeriesLink varchar(255), "
- "sParentalRatingCode varchar(64)"
- ")"
- );
+ m_pDS->exec("CREATE TABLE epgtags ("
+ "idBroadcast integer primary key, "
+ "iBroadcastUid integer, "
+ "idEpg integer, "
+ "sTitle varchar(128), "
+ "sPlotOutline text, "
+ "sPlot text, "
+ "sOriginalTitle varchar(128), "
+ "sCast varchar(255), "
+ "sDirector varchar(255), "
+ "sWriter varchar(255), "
+ "iYear integer, "
+ "sIMDBNumber varchar(50), "
+ "sIconPath varchar(255), "
+ "iStartTime integer, "
+ "iEndTime integer, "
+ "iGenreType integer, "
+ "iGenreSubType integer, "
+ "sGenre varchar(128), "
+ "sFirstAired varchar(32), "
+ "iParentalRating integer, "
+ "iStarRating integer, "
+ "iSeriesId integer, "
+ "iEpisodeId integer, "
+ "iEpisodePart integer, "
+ "sEpisodeName varchar(128), "
+ "iFlags integer, "
+ "sSeriesLink varchar(255), "
+ "sParentalRatingCode varchar(64),"
+ "sParentalRatingIcon varchar(512),"
+ "sParentalRatingSource varchar(128)"
+ ")");
CLog::LogFC(LOGDEBUG, LOGEPG, "Creating table 'lastepgscan'");
m_pDS->exec("CREATE TABLE lastepgscan ("
@@ -128,8 +128,11 @@ void CPVREpgDatabase::CreateTables()
"bIgnoreFutureBroadcasts bool, "
"bFreeToAirOnly bool, "
"bIgnorePresentTimers bool, "
- "bIgnorePresentRecordings bool,"
- "iChannelGroup integer"
+ "bIgnorePresentRecordings bool, "
+ "iChannelGroup integer, "
+ "sIconPath varchar(255), "
+ "bStartAnyTime bool, "
+ "bEndAnyTime bool"
")");
}
@@ -324,6 +327,26 @@ void CPVREpgDatabase::UpdateTables(int iVersion)
m_pDS->exec("ALTER TABLE savedsearches ADD iChannelGroup integer;");
m_pDS->exec("UPDATE savedsearches SET iChannelGroup = -1");
}
+
+ if (iVersion < 17)
+ {
+ m_pDS->exec("ALTER TABLE savedsearches ADD sIconPath varchar(255);");
+ m_pDS->exec("UPDATE savedsearches SET sIconPath = ''");
+ }
+
+ if (iVersion < 18)
+ {
+ m_pDS->exec("ALTER TABLE epgtags ADD sParentalRatingIcon varchar(512);");
+ m_pDS->exec("ALTER TABLE epgtags ADD sParentalRatingSource varchar(128);");
+ }
+
+ if (iVersion < 19)
+ {
+ m_pDS->exec("ALTER TABLE savedsearches ADD bStartAnyTime bool;");
+ m_pDS->exec("ALTER TABLE savedsearches ADD bEndAnyTime bool;");
+ m_pDS->exec("UPDATE savedsearches SET bStartAnyTime = 1;");
+ m_pDS->exec("UPDATE savedsearches SET bEndAnyTime = 1;");
+ }
}
bool CPVREpgDatabase::DeleteEpg()
@@ -442,7 +465,7 @@ std::shared_ptr<CPVREpgInfoTag> CPVREpgDatabase::CreateEpgTag(
newTag->m_writers = newTag->Tokenize(m_pDS->fv("sWriter").get_asString());
newTag->m_iYear = m_pDS->fv("iYear").get_asInt();
newTag->m_strIMDBNumber = m_pDS->fv("sIMDBNumber").get_asString();
- newTag->m_iParentalRating = m_pDS->fv("iParentalRating").get_asInt();
+ newTag->m_parentalRating = m_pDS->fv("iParentalRating").get_asInt();
newTag->m_iStarRating = m_pDS->fv("iStarRating").get_asInt();
newTag->m_iEpisodeNumber = m_pDS->fv("iEpisodeId").get_asInt();
newTag->m_iEpisodePart = m_pDS->fv("iEpisodePart").get_asInt();
@@ -450,7 +473,9 @@ std::shared_ptr<CPVREpgInfoTag> CPVREpgDatabase::CreateEpgTag(
newTag->m_iSeriesNumber = m_pDS->fv("iSeriesId").get_asInt();
newTag->m_iFlags = m_pDS->fv("iFlags").get_asInt();
newTag->m_strSeriesLink = m_pDS->fv("sSeriesLink").get_asString();
- newTag->m_strParentalRatingCode = m_pDS->fv("sParentalRatingCode").get_asString();
+ newTag->m_parentalRatingCode = m_pDS->fv("sParentalRatingCode").get_asString();
+ newTag->m_parentalRatingIcon = m_pDS->fv("sParentalRatingIcon").get_asString();
+ newTag->m_parentalRatingSource = m_pDS->fv("sParentalRatingSource").get_asString();
newTag->m_iGenreType = m_pDS->fv("iGenreType").get_asInt();
newTag->m_iGenreSubType = m_pDS->fv("iGenreSubType").get_asInt();
newTag->m_strGenreDescription = m_pDS->fv("sGenre").get_asString();
@@ -547,6 +572,8 @@ class CSearchTermConverter
public:
explicit CSearchTermConverter(const std::string& strSearchTerm) { Parse(strSearchTerm); }
+ bool HasSearchTerm() const { return !m_fragments.empty(); }
+
std::string ToSQL(const std::string& strFieldName) const
{
std::string result = "(";
@@ -677,11 +704,25 @@ std::vector<std::shared_ptr<CPVREpgInfoTag>> CPVREpgDatabase::GetEpgTags(
// min start datetime
/////////////////////////////////////////////////////////////////////////////////////////////
+ static constexpr unsigned int ONE_DAY{60 * 60 * 24};
+
if (searchData.m_startDateTime.IsValid())
{
time_t minStart;
searchData.m_startDateTime.GetAsTime(minStart);
- filter.AppendWhere(PrepareSQL("iStartTime >= %u", static_cast<unsigned int>(minStart)));
+
+ if (searchData.m_startAnyTime)
+ {
+ filter.AppendWhere(PrepareSQL("iStartTime >= %u", static_cast<unsigned int>(minStart)));
+ }
+ else
+ {
+ const unsigned int startDate{static_cast<unsigned int>(minStart) / ONE_DAY};
+ filter.AppendWhere(PrepareSQL("(iStartTime / %u) >= %u", ONE_DAY, startDate));
+
+ const unsigned int startTime{static_cast<unsigned int>(minStart) % ONE_DAY};
+ filter.AppendWhere(PrepareSQL("(iStartTime %% %u) >= %u", ONE_DAY, startTime));
+ }
}
/////////////////////////////////////////////////////////////////////////////////////////////
@@ -692,7 +733,19 @@ std::vector<std::shared_ptr<CPVREpgInfoTag>> CPVREpgDatabase::GetEpgTags(
{
time_t maxEnd;
searchData.m_endDateTime.GetAsTime(maxEnd);
- filter.AppendWhere(PrepareSQL("iEndTime <= %u", static_cast<unsigned int>(maxEnd)));
+
+ if (searchData.m_endAnyTime)
+ {
+ filter.AppendWhere(PrepareSQL("iEndTime <= %u", static_cast<unsigned int>(maxEnd)));
+ }
+ else
+ {
+ const unsigned int endDate{static_cast<unsigned int>(maxEnd) / ONE_DAY};
+ filter.AppendWhere(PrepareSQL("(iEndTime / %u) <= %u", ONE_DAY, endDate));
+
+ const unsigned int endTime{static_cast<unsigned int>(maxEnd) % ONE_DAY};
+ filter.AppendWhere(PrepareSQL("(iEndTime %% %u) <= %u", ONE_DAY, endTime));
+ }
}
/////////////////////////////////////////////////////////////////////////////////////////////
@@ -739,10 +792,9 @@ std::vector<std::shared_ptr<CPVREpgInfoTag>> CPVREpgDatabase::GetEpgTags(
// search term
/////////////////////////////////////////////////////////////////////////////////////////////
- if (!searchData.m_strSearchTerm.empty())
+ const CSearchTermConverter conv{searchData.m_strSearchTerm};
+ if (conv.HasSearchTerm())
{
- const CSearchTermConverter conv(searchData.m_strSearchTerm);
-
// title
std::string strWhere = conv.ToSQL("sTitle");
@@ -1230,9 +1282,9 @@ bool CPVREpgDatabase::QueuePersistQuery(const CPVREpgInfoTag& tag)
"sIconPath, iGenreType, iGenreSubType, sGenre, sFirstAired, iParentalRating, iStarRating, "
"iSeriesId, "
"iEpisodeId, iEpisodePart, sEpisodeName, iFlags, sSeriesLink, sParentalRatingCode, "
- "iBroadcastUid) "
+ "iBroadcastUid, sParentalRatingIcon, sParentalRatingSource) "
"VALUES (%u, %u, %u, '%s', '%s', '%s', '%s', '%s', '%s', '%s', %i, '%s', '%s', %i, %i, "
- "'%s', '%s', %i, %i, %i, %i, %i, '%s', %i, '%s', '%s', %i);",
+ "'%s', '%s', %i, %i, %i, %i, %i, '%s', %i, '%s', '%s', %i, '%s', '%s');",
tag.EpgID(), static_cast<unsigned int>(iStartTime), static_cast<unsigned int>(iEndTime),
tag.Title().c_str(), tag.PlotOutline().c_str(), tag.Plot().c_str(),
tag.OriginalTitle().c_str(), tag.DeTokenize(tag.Cast()).c_str(),
@@ -1241,7 +1293,8 @@ bool CPVREpgDatabase::QueuePersistQuery(const CPVREpgInfoTag& tag)
tag.GenreDescription().c_str(), sFirstAired.c_str(), tag.ParentalRating(), tag.StarRating(),
tag.SeriesNumber(), tag.EpisodeNumber(), tag.EpisodePart(), tag.EpisodeName().c_str(),
tag.Flags(), tag.SeriesLink().c_str(), tag.ParentalRatingCode().c_str(),
- tag.UniqueBroadcastID());
+ tag.UniqueBroadcastID(), tag.ParentalRatingIcon().c_str(),
+ tag.ParentalRatingSource().c_str());
}
else
{
@@ -1252,9 +1305,9 @@ bool CPVREpgDatabase::QueuePersistQuery(const CPVREpgInfoTag& tag)
"sIconPath, iGenreType, iGenreSubType, sGenre, sFirstAired, iParentalRating, iStarRating, "
"iSeriesId, "
"iEpisodeId, iEpisodePart, sEpisodeName, iFlags, sSeriesLink, sParentalRatingCode, "
- "iBroadcastUid, idBroadcast) "
+ "iBroadcastUid, idBroadcast, sParentalRatingIcon, sParentalRatingSource) "
"VALUES (%u, %u, %u, '%s', '%s', '%s', '%s', '%s', '%s', '%s', %i, '%s', '%s', %i, %i, "
- "'%s', '%s', %i, %i, %i, %i, %i, '%s', %i, '%s', '%s', %i, %i);",
+ "'%s', '%s', %i, %i, %i, %i, %i, '%s', %i, '%s', '%s', %i, %i, '%s', '%s');",
tag.EpgID(), static_cast<unsigned int>(iStartTime), static_cast<unsigned int>(iEndTime),
tag.Title().c_str(), tag.PlotOutline().c_str(), tag.Plot().c_str(),
tag.OriginalTitle().c_str(), tag.DeTokenize(tag.Cast()).c_str(),
@@ -1263,7 +1316,8 @@ bool CPVREpgDatabase::QueuePersistQuery(const CPVREpgInfoTag& tag)
tag.GenreDescription().c_str(), sFirstAired.c_str(), tag.ParentalRating(), tag.StarRating(),
tag.SeriesNumber(), tag.EpisodeNumber(), tag.EpisodePart(), tag.EpisodeName().c_str(),
tag.Flags(), tag.SeriesLink().c_str(), tag.ParentalRatingCode().c_str(),
- tag.UniqueBroadcastID(), iBroadcastId);
+ tag.UniqueBroadcastID(), iBroadcastId, tag.ParentalRatingIcon().c_str(),
+ tag.ParentalRatingSource().c_str());
}
QueueInsertQuery(strQuery);
@@ -1321,6 +1375,9 @@ std::shared_ptr<CPVREpgSearchFilter> CPVREpgDatabase::CreateEpgSearchFilter(
newSearch->SetIgnorePresentTimers(m_pDS->fv("bIgnorePresentTimers").get_asBool());
newSearch->SetIgnorePresentRecordings(m_pDS->fv("bIgnorePresentRecordings").get_asBool());
newSearch->SetChannelGroupID(m_pDS->fv("iChannelGroup").get_asInt());
+ newSearch->SetIconPath(m_pDS->fv("sIconPath").get_asString());
+ newSearch->SetStartAnyTime(m_pDS->fv("bStartAnyTime").get_asBool());
+ newSearch->SetEndAnyTime(m_pDS->fv("bEndAnyTime").get_asBool());
newSearch->SetChanged(false);
@@ -1385,16 +1442,16 @@ bool CPVREpgDatabase::Persist(CPVREpgSearchFilter& epgSearch)
// Insert a new entry if this is a new search, replace the existing otherwise
std::string strQuery;
- if (epgSearch.GetDatabaseId() == -1)
+ if (epgSearch.GetDatabaseId() == PVR_EPG_SEARCH_INVALID_DATABASE_ID)
strQuery = PrepareSQL(
"INSERT INTO savedsearches "
"(sTitle, sLastExecutedDateTime, sSearchTerm, bSearchInDescription, bIsCaseSensitive, "
"iGenreType, bIncludeUnknownGenres, sStartDateTime, sEndDateTime, iMinimumDuration, "
"iMaximumDuration, bIsRadio, iClientId, iChannelUid, bRemoveDuplicates, "
"bIgnoreFinishedBroadcasts, bIgnoreFutureBroadcasts, bFreeToAirOnly, bIgnorePresentTimers, "
- "bIgnorePresentRecordings, iChannelGroup) "
+ "bIgnorePresentRecordings, iChannelGroup, sIconPath, bStartAnyTime, bEndAnyTime) "
"VALUES ('%s', '%s', '%s', %i, %i, %i, %i, '%s', '%s', %i, %i, %i, %i, %i, %i, %i, %i, "
- "%i, %i, %i, %i);",
+ "%i, %i, %i, %i, '%s', %i, %i);",
epgSearch.GetTitle().c_str(),
epgSearch.GetLastExecutedDateTime().IsValid()
? epgSearch.GetLastExecutedDateTime().GetAsDBDateTime().c_str()
@@ -1413,7 +1470,9 @@ bool CPVREpgDatabase::Persist(CPVREpgSearchFilter& epgSearch)
epgSearch.ShouldIgnoreFinishedBroadcasts() ? 1 : 0,
epgSearch.ShouldIgnoreFutureBroadcasts() ? 1 : 0, epgSearch.IsFreeToAirOnly() ? 1 : 0,
epgSearch.ShouldIgnorePresentTimers() ? 1 : 0,
- epgSearch.ShouldIgnorePresentRecordings() ? 1 : 0, epgSearch.GetChannelGroupID());
+ epgSearch.ShouldIgnorePresentRecordings() ? 1 : 0, epgSearch.GetChannelGroupID(),
+ epgSearch.GetIconPath().c_str(), epgSearch.IsStartAnyTime() ? 1 : 0,
+ epgSearch.IsEndAnyTime() ? 1 : 0);
else
strQuery = PrepareSQL(
"REPLACE INTO savedsearches "
@@ -1421,9 +1480,9 @@ bool CPVREpgDatabase::Persist(CPVREpgSearchFilter& epgSearch)
"bIsCaseSensitive, iGenreType, bIncludeUnknownGenres, sStartDateTime, sEndDateTime, "
"iMinimumDuration, iMaximumDuration, bIsRadio, iClientId, iChannelUid, bRemoveDuplicates, "
"bIgnoreFinishedBroadcasts, bIgnoreFutureBroadcasts, bFreeToAirOnly, bIgnorePresentTimers, "
- "bIgnorePresentRecordings, iChannelGroup) "
+ "bIgnorePresentRecordings, iChannelGroup, sIconPath, bStartAnyTime, bEndAnyTime) "
"VALUES (%i, '%s', '%s', '%s', %i, %i, %i, %i, '%s', '%s', %i, %i, %i, %i, %i, %i, %i, %i, "
- "%i, %i, %i, %i);",
+ "%i, %i, %i, %i, '%s', %i, %i);",
epgSearch.GetDatabaseId(), epgSearch.GetTitle().c_str(),
epgSearch.GetLastExecutedDateTime().IsValid()
? epgSearch.GetLastExecutedDateTime().GetAsDBDateTime().c_str()
@@ -1442,14 +1501,16 @@ bool CPVREpgDatabase::Persist(CPVREpgSearchFilter& epgSearch)
epgSearch.ShouldIgnoreFinishedBroadcasts() ? 1 : 0,
epgSearch.ShouldIgnoreFutureBroadcasts() ? 1 : 0, epgSearch.IsFreeToAirOnly() ? 1 : 0,
epgSearch.ShouldIgnorePresentTimers() ? 1 : 0,
- epgSearch.ShouldIgnorePresentRecordings() ? 1 : 0, epgSearch.GetChannelGroupID());
+ epgSearch.ShouldIgnorePresentRecordings() ? 1 : 0, epgSearch.GetChannelGroupID(),
+ epgSearch.GetIconPath().c_str(), epgSearch.IsStartAnyTime() ? 1 : 0,
+ epgSearch.IsEndAnyTime() ? 1 : 0);
bool bReturn = ExecuteQuery(strQuery);
if (bReturn)
{
// Set the database id for searches persisted for the first time
- if (epgSearch.GetDatabaseId() == -1)
+ if (epgSearch.GetDatabaseId() == PVR_EPG_SEARCH_INVALID_DATABASE_ID)
epgSearch.SetDatabaseId(static_cast<int>(m_pDS->lastinsertid()));
epgSearch.SetChanged(false);
@@ -1460,7 +1521,7 @@ bool CPVREpgDatabase::Persist(CPVREpgSearchFilter& epgSearch)
bool CPVREpgDatabase::UpdateSavedSearchLastExecuted(const CPVREpgSearchFilter& epgSearch)
{
- if (epgSearch.GetDatabaseId() == -1)
+ if (epgSearch.GetDatabaseId() == PVR_EPG_SEARCH_INVALID_DATABASE_ID)
return false;
std::unique_lock<CCriticalSection> lock(m_critSection);
@@ -1473,7 +1534,7 @@ bool CPVREpgDatabase::UpdateSavedSearchLastExecuted(const CPVREpgSearchFilter& e
bool CPVREpgDatabase::Delete(const CPVREpgSearchFilter& epgSearch)
{
- if (epgSearch.GetDatabaseId() == -1)
+ if (epgSearch.GetDatabaseId() == PVR_EPG_SEARCH_INVALID_DATABASE_ID)
return false;
CLog::LogFC(LOGDEBUG, LOGEPG, "Deleting saved search '{}' from the database",
diff --git a/xbmc/pvr/epg/EpgDatabase.h b/xbmc/pvr/epg/EpgDatabase.h
index b3cb2bb265..8ea846bf42 100644
--- a/xbmc/pvr/epg/EpgDatabase.h
+++ b/xbmc/pvr/epg/EpgDatabase.h
@@ -66,7 +66,7 @@ namespace PVR
* @brief Get the minimal database version that is required to operate correctly.
* @return The minimal database version.
*/
- int GetSchemaVersion() const override { return 16; }
+ int GetSchemaVersion() const override { return 19; }
/*!
* @brief Get the default sqlite database filename.
diff --git a/xbmc/pvr/epg/EpgInfoTag.cpp b/xbmc/pvr/epg/EpgInfoTag.cpp
index 873fd27b0c..b731f685c7 100644
--- a/xbmc/pvr/epg/EpgInfoTag.cpp
+++ b/xbmc/pvr/epg/EpgInfoTag.cpp
@@ -22,6 +22,7 @@
#include "utils/Variant.h"
#include "utils/log.h"
+#include <chrono>
#include <memory>
#include <mutex>
#include <string>
@@ -68,7 +69,7 @@ CPVREpgInfoTag::CPVREpgInfoTag(const EPG_TAG& data,
int iEpgID)
: m_iGenreType(data.iGenreType),
m_iGenreSubType(data.iGenreSubType),
- m_iParentalRating(data.iParentalRating),
+ m_parentalRating(data.iParentalRating),
m_iStarRating(data.iStarRating),
m_iSeriesNumber(data.iSeriesNumber),
m_iEpisodeNumber(data.iEpisodeNumber),
@@ -130,7 +131,11 @@ CPVREpgInfoTag::CPVREpgInfoTag(const EPG_TAG& data,
if (data.strSeriesLink)
m_strSeriesLink = data.strSeriesLink;
if (data.strParentalRatingCode)
- m_strParentalRatingCode = data.strParentalRatingCode;
+ m_parentalRatingCode = data.strParentalRatingCode;
+ if (data.strParentalRatingIcon)
+ m_parentalRatingIcon = data.strParentalRatingIcon;
+ if (data.strParentalRatingSource)
+ m_parentalRatingSource = data.strParentalRatingSource;
}
void CPVREpgInfoTag::SetChannelData(const std::shared_ptr<CPVREpgChannelData>& data)
@@ -167,8 +172,10 @@ void CPVREpgInfoTag::Serialize(CVariant& value) const
std::unique_lock<CCriticalSection> lock(m_critSection);
value["broadcastid"] = m_iDatabaseID; // Use DB id here as it is unique across PVR clients
value["channeluid"] = m_channelData->UniqueClientChannelId();
- value["parentalrating"] = m_iParentalRating;
- value["parentalratingcode"] = m_strParentalRatingCode;
+ value["parentalrating"] = m_parentalRating;
+ value["parentalratingcode"] = m_parentalRatingCode;
+ value["parentalratingicon"] = m_parentalRatingIcon;
+ value["parentalratingsource"] = m_parentalRatingSource;
value["rating"] = m_iStarRating;
value["title"] = m_strTitle;
value["plotoutline"] = m_strPlotOutline;
@@ -239,27 +246,36 @@ float CPVREpgInfoTag::ProgressPercentage() const
CDateTime::GetCurrentDateTime().GetAsUTCDateTime().GetAsTime(currentTime);
m_startTime.GetAsTime(startTime);
m_endTime.GetAsTime(endTime);
- int iDuration = endTime - startTime > 0 ? endTime - startTime : 3600;
if (currentTime >= startTime && currentTime <= endTime)
- fReturn = static_cast<float>(currentTime - startTime) * 100.0f / iDuration;
+ {
+ const std::chrono::duration<float> current{currentTime - startTime};
+ const std::chrono::duration<float> total{endTime - startTime > 0 ? endTime - startTime
+ : 3600.0f};
+ fReturn = current.count() * 100.0f / total.count();
+ }
else if (currentTime > endTime)
+ {
fReturn = 100.0f;
-
+ }
return fReturn;
}
-int CPVREpgInfoTag::Progress() const
+unsigned int CPVREpgInfoTag::Progress() const
{
time_t currentTime, startTime;
CDateTime::GetCurrentDateTime().GetAsUTCDateTime().GetAsTime(currentTime);
m_startTime.GetAsTime(startTime);
- int iDuration = currentTime - startTime;
- if (iDuration <= 0)
+ if (currentTime > startTime)
+ {
+ const std::chrono::duration<unsigned int> duration{currentTime - startTime};
+ return duration.count();
+ }
+ else
+ {
return 0;
-
- return iDuration;
+ }
}
void CPVREpgInfoTag::SetUniqueBroadcastID(unsigned int iUniqueBroadcastID)
@@ -318,12 +334,21 @@ void CPVREpgInfoTag::SetEndFromUTC(const CDateTime& end)
m_endTime = end;
}
-int CPVREpgInfoTag::GetDuration() const
+unsigned int CPVREpgInfoTag::GetDuration() const
{
time_t start, end;
m_startTime.GetAsTime(start);
m_endTime.GetAsTime(end);
- return end - start > 0 ? end - start : 3600;
+
+ if (end > start)
+ {
+ const std::chrono::duration<unsigned int> duration{end - start};
+ return duration.count();
+ }
+ else
+ {
+ return 3600;
+ }
}
const std::string CPVREpgInfoTag::GetCastLabel() const
@@ -398,9 +423,9 @@ CDateTime CPVREpgInfoTag::FirstAired() const
return m_firstAired;
}
-int CPVREpgInfoTag::ParentalRating() const
+unsigned int CPVREpgInfoTag::ParentalRating() const
{
- return m_iParentalRating;
+ return m_parentalRating;
}
int CPVREpgInfoTag::StarRating() const
@@ -449,11 +474,12 @@ bool CPVREpgInfoTag::Update(const CPVREpgInfoTag& tag, bool bUpdateBroadcastId /
m_startTime != tag.m_startTime || m_endTime != tag.m_endTime ||
m_iGenreType != tag.m_iGenreType || m_iGenreSubType != tag.m_iGenreSubType ||
m_strGenreDescription != tag.m_strGenreDescription || m_firstAired != tag.m_firstAired ||
- m_iParentalRating != tag.m_iParentalRating ||
- m_strParentalRatingCode != tag.m_strParentalRatingCode ||
- m_iStarRating != tag.m_iStarRating || m_iEpisodeNumber != tag.m_iEpisodeNumber ||
- m_iEpisodePart != tag.m_iEpisodePart || m_iSeriesNumber != tag.m_iSeriesNumber ||
- m_strEpisodeName != tag.m_strEpisodeName ||
+ m_parentalRating != tag.m_parentalRating ||
+ m_parentalRatingCode != tag.m_parentalRatingCode ||
+ m_parentalRatingIcon != tag.m_parentalRatingIcon ||
+ m_parentalRatingSource != tag.m_parentalRatingSource || m_iStarRating != tag.m_iStarRating ||
+ m_iEpisodeNumber != tag.m_iEpisodeNumber || m_iEpisodePart != tag.m_iEpisodePart ||
+ m_iSeriesNumber != tag.m_iSeriesNumber || m_strEpisodeName != tag.m_strEpisodeName ||
m_iUniqueBroadcastID != tag.m_iUniqueBroadcastID || m_iEpgID != tag.m_iEpgID ||
m_genre != tag.m_genre || m_iconPath != tag.m_iconPath || m_iFlags != tag.m_iFlags ||
m_strSeriesLink != tag.m_strSeriesLink || m_channelData != tag.m_channelData);
@@ -485,8 +511,10 @@ bool CPVREpgInfoTag::Update(const CPVREpgInfoTag& tag, bool bUpdateBroadcastId /
m_iFlags = tag.m_iFlags;
m_strSeriesLink = tag.m_strSeriesLink;
m_firstAired = tag.m_firstAired;
- m_iParentalRating = tag.m_iParentalRating;
- m_strParentalRatingCode = tag.m_strParentalRatingCode;
+ m_parentalRating = tag.m_parentalRating;
+ m_parentalRatingCode = tag.m_parentalRatingCode;
+ m_parentalRatingIcon = tag.m_parentalRatingIcon;
+ m_parentalRatingSource = tag.m_parentalRatingSource;
m_iStarRating = tag.m_iStarRating;
m_iEpisodeNumber = tag.m_iEpisodeNumber;
m_iEpisodePart = tag.m_iEpisodePart;
diff --git a/xbmc/pvr/epg/EpgInfoTag.h b/xbmc/pvr/epg/EpgInfoTag.h
index 4e3a5ee540..d5ae0518ec 100644
--- a/xbmc/pvr/epg/EpgInfoTag.h
+++ b/xbmc/pvr/epg/EpgInfoTag.h
@@ -109,7 +109,7 @@ public:
* @brief Get the progress of this tag in seconds.
* @return The current progress of this tag in seconds.
*/
- int Progress() const;
+ unsigned int Progress() const;
/*!
* @brief Get EPG ID of this tag.
@@ -187,7 +187,7 @@ public:
* @brief Get the duration of this event in seconds.
* @return The duration.
*/
- int GetDuration() const;
+ unsigned int GetDuration() const;
/*!
* @brief Get the title of this event.
@@ -301,15 +301,26 @@ public:
* @brief Get the parental rating of this event.
* @return The parental rating.
*/
- int ParentalRating() const;
+ unsigned int ParentalRating() const;
/*!
* @brief Get the parental rating code of this event.
* @return The parental rating code.
*/
- const std::string& ParentalRatingCode() const { return m_strParentalRatingCode; }
+ const std::string& ParentalRatingCode() const { return m_parentalRatingCode; }
/*!
+ * @brief Get the parental rating icon path of this event.
+ * @return Path to the parental rating icon.
+ */
+ const std::string& ParentalRatingIcon() const { return m_parentalRatingIcon; }
+
+ /*!
+ * @brief Get the parental rating source of this event.
+ * @return The parental rating source.
+ */
+ const std::string& ParentalRatingSource() const { return m_parentalRatingSource; }
+ /*!
* @brief Get the star rating of this event.
* @return The star rating.
*/
@@ -484,8 +495,10 @@ private:
int m_iGenreType = 0; /*!< genre type */
int m_iGenreSubType = 0; /*!< genre subtype */
std::string m_strGenreDescription; /*!< genre description */
- int m_iParentalRating = 0; /*!< parental rating */
- std::string m_strParentalRatingCode; /*!< parental rating code */
+ unsigned int m_parentalRating = 0; /*!< parental rating */
+ std::string m_parentalRatingCode; /*!< Parental rating code */
+ std::string m_parentalRatingIcon; /*!< parental rating icon path */
+ std::string m_parentalRatingSource; /*!< parental rating source */
int m_iStarRating = 0; /*!< star rating */
int m_iSeriesNumber = -1; /*!< series number */
int m_iEpisodeNumber = -1; /*!< episode number */
diff --git a/xbmc/pvr/epg/EpgSearch.cpp b/xbmc/pvr/epg/EpgSearch.cpp
new file mode 100644
index 0000000000..5981b87a47
--- /dev/null
+++ b/xbmc/pvr/epg/EpgSearch.cpp
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2023 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "EpgSearch.h"
+
+#include "ServiceBroker.h"
+#include "pvr/PVRManager.h"
+#include "pvr/epg/EpgContainer.h"
+#include "pvr/epg/EpgSearchFilter.h"
+
+#include <algorithm>
+#include <mutex>
+
+using namespace PVR;
+
+void CPVREpgSearch::Execute()
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+
+ std::vector<std::shared_ptr<CPVREpgInfoTag>> tags{
+ CServiceBroker::GetPVRManager().EpgContainer().GetTags(m_filter.GetEpgSearchData())};
+ m_filter.SetEpgSearchDataFiltered();
+
+ // Tags can still contain false positives, for search criteria that cannot be handled via
+ // database. So, run extended search filters on what we got from the database.
+ for (auto it = tags.cbegin(); it != tags.cend();)
+ {
+ it = tags.erase(std::remove_if(tags.begin(), tags.end(),
+ [this](const std::shared_ptr<const CPVREpgInfoTag>& entry)
+ { return !m_filter.FilterEntry(entry); }),
+ tags.cend());
+ }
+
+ if (m_filter.ShouldRemoveDuplicates())
+ m_filter.RemoveDuplicates(tags);
+
+ m_filter.SetLastExecutedDateTime(CDateTime::GetUTCDateTime());
+
+ m_results = tags;
+}
+
+const std::vector<std::shared_ptr<CPVREpgInfoTag>>& CPVREpgSearch::GetResults() const
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ return m_results;
+}
diff --git a/xbmc/pvr/epg/EpgSearch.h b/xbmc/pvr/epg/EpgSearch.h
new file mode 100644
index 0000000000..6dbbf89b29
--- /dev/null
+++ b/xbmc/pvr/epg/EpgSearch.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2023 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "threads/CriticalSection.h"
+
+#include <memory>
+#include <vector>
+
+namespace PVR
+{
+class CPVREpgInfoTag;
+class CPVREpgSearchFilter;
+
+class CPVREpgSearch
+{
+public:
+ CPVREpgSearch() = delete;
+
+ /*!
+ * @brief ctor.
+ * @param filter The filter defining the search criteria.
+ */
+ explicit CPVREpgSearch(CPVREpgSearchFilter& filter) : m_filter(filter) {}
+
+ /*!
+ * @brief Execute the search.
+ */
+ void Execute();
+
+ /*!
+ * @brief Get the last search results.
+ * @return the results.
+ */
+ const std::vector<std::shared_ptr<CPVREpgInfoTag>>& GetResults() const;
+
+private:
+ mutable CCriticalSection m_critSection;
+ CPVREpgSearchFilter& m_filter;
+ std::vector<std::shared_ptr<CPVREpgInfoTag>> m_results;
+};
+} // namespace PVR
diff --git a/xbmc/pvr/epg/EpgSearchData.h b/xbmc/pvr/epg/EpgSearchData.h
index e5e6ef7057..c0ab72069a 100644
--- a/xbmc/pvr/epg/EpgSearchData.h
+++ b/xbmc/pvr/epg/EpgSearchData.h
@@ -25,8 +25,10 @@ struct PVREpgSearchData
int m_iGenreType = EPG_SEARCH_UNSET; /*!< The genre type for an entry */
bool m_bIgnoreFinishedBroadcasts; /*!< True to ignore finished broadcasts, false if not */
bool m_bIgnoreFutureBroadcasts; /*!< True to ignore future broadcasts, false if not */
- CDateTime m_startDateTime; /*!< The minimum start time for an entry */
- CDateTime m_endDateTime; /*!< The maximum end time for an entry */
+ CDateTime m_startDateTime; /*!< The minimum start date and time for an entry */
+ CDateTime m_endDateTime; /*!< The maximum end date and time for an entry */
+ bool m_startAnyTime{true}; /*!< Match any start time */
+ bool m_endAnyTime{true}; /*!< Match any end time */
void Reset()
{
@@ -38,6 +40,8 @@ struct PVREpgSearchData
m_bIgnoreFutureBroadcasts = false;
m_startDateTime.SetValid(false);
m_endDateTime.SetValid(false);
+ m_startAnyTime = true;
+ m_endAnyTime = true;
}
};
diff --git a/xbmc/pvr/epg/EpgSearchFilter.cpp b/xbmc/pvr/epg/EpgSearchFilter.cpp
index 4d96993a17..b7533ce4c5 100644
--- a/xbmc/pvr/epg/EpgSearchFilter.cpp
+++ b/xbmc/pvr/epg/EpgSearchFilter.cpp
@@ -28,8 +28,7 @@
using namespace PVR;
-CPVREpgSearchFilter::CPVREpgSearchFilter(bool bRadio)
-: m_bIsRadio(bRadio)
+CPVREpgSearchFilter::CPVREpgSearchFilter(bool bRadio) : m_bIsRadio(bRadio)
{
Reset();
}
@@ -45,15 +44,13 @@ void CPVREpgSearchFilter::Reset()
m_bRemoveDuplicates = false;
/* pvr specific filters */
- m_iClientID = -1;
+ m_iClientID = PVR_CLIENT_INVALID_UID;
m_iChannelGroupID = -1;
m_iChannelUID = -1;
m_bFreeToAirOnly = false;
m_bIgnorePresentTimers = true;
m_bIgnorePresentRecordings = true;
- m_groupIdMatches.reset();
-
m_iDatabaseId = -1;
m_title.clear();
m_lastExecutedDateTime.SetValid(false);
@@ -151,6 +148,15 @@ void CPVREpgSearchFilter::SetStartDateTime(const CDateTime& startDateTime)
}
}
+void CPVREpgSearchFilter::SetStartAnyTime(bool startAnyTime)
+{
+ if (m_searchData.m_startAnyTime != startAnyTime)
+ {
+ m_searchData.m_startAnyTime = startAnyTime;
+ m_bChanged = true;
+ }
+}
+
void CPVREpgSearchFilter::SetEndDateTime(const CDateTime& endDateTime)
{
if (m_searchData.m_endDateTime != endDateTime)
@@ -160,6 +166,15 @@ void CPVREpgSearchFilter::SetEndDateTime(const CDateTime& endDateTime)
}
}
+void CPVREpgSearchFilter::SetEndAnyTime(bool endAnyTime)
+{
+ if (m_searchData.m_endAnyTime != endAnyTime)
+ {
+ m_searchData.m_endAnyTime = endAnyTime;
+ m_bChanged = true;
+ }
+}
+
void CPVREpgSearchFilter::SetIncludeUnknownGenres(bool bIncludeUnknownGenres)
{
if (m_searchData.m_bIncludeUnknownGenres != bIncludeUnknownGenres)
@@ -192,7 +207,6 @@ void CPVREpgSearchFilter::SetChannelGroupID(int iChannelGroupID)
if (m_iChannelGroupID != iChannelGroupID)
{
m_iChannelGroupID = iChannelGroupID;
- m_groupIdMatches.reset();
m_bChanged = true;
}
}
@@ -251,6 +265,15 @@ void CPVREpgSearchFilter::SetTitle(const std::string& title)
}
}
+void CPVREpgSearchFilter::SetIconPath(const std::string& iconPath)
+{
+ if (m_iconPath != iconPath)
+ {
+ m_iconPath = iconPath;
+ m_bChanged = true;
+ }
+}
+
void CPVREpgSearchFilter::SetLastExecutedDateTime(const CDateTime& lastExecutedDateTime)
{
// Note: No need to set m_bChanged here
@@ -287,10 +310,10 @@ bool CPVREpgSearchFilter::MatchDuration(const std::shared_ptr<const CPVREpgInfoT
bool bReturn(true);
if (m_iMinimumDuration != EPG_SEARCH_UNSET)
- bReturn = (tag->GetDuration() > m_iMinimumDuration * 60);
+ bReturn = (tag->GetDuration() > static_cast<unsigned int>(m_iMinimumDuration) * 60);
if (bReturn && m_iMaximumDuration != EPG_SEARCH_UNSET)
- bReturn = (tag->GetDuration() < m_iMaximumDuration * 60);
+ bReturn = (tag->GetDuration() < static_cast<unsigned int>(m_iMaximumDuration) * 60);
return bReturn;
}
@@ -342,7 +365,8 @@ void CPVREpgSearchFilter::RemoveDuplicates(std::vector<std::shared_ptr<CPVREpgIn
for (auto it = results.begin(); it != results.end();)
{
it = results.erase(std::remove_if(results.begin(), results.end(),
- [&it](const std::shared_ptr<const CPVREpgInfoTag>& entry) {
+ [&it](const std::shared_ptr<const CPVREpgInfoTag>& entry)
+ {
return *it != entry && (*it)->Title() == entry->Title() &&
(*it)->Plot() == entry->Plot() &&
(*it)->PlotOutline() == entry->PlotOutline();
@@ -354,7 +378,7 @@ void CPVREpgSearchFilter::RemoveDuplicates(std::vector<std::shared_ptr<CPVREpgIn
bool CPVREpgSearchFilter::MatchChannel(const std::shared_ptr<const CPVREpgInfoTag>& tag) const
{
return tag && (tag->IsRadio() == m_bIsRadio) &&
- (m_iClientID == -1 || tag->ClientID() == m_iClientID) &&
+ (m_iClientID == PVR_CLIENT_INVALID_UID || tag->ClientID() == m_iClientID) &&
(m_iChannelUID == -1 || tag->UniqueChannelID() == m_iChannelUID) &&
CServiceBroker::GetPVRManager().Clients()->IsCreatedClient(tag->ClientID());
}
@@ -363,17 +387,14 @@ bool CPVREpgSearchFilter::MatchChannelGroup(const std::shared_ptr<const CPVREpgI
{
if (m_iChannelGroupID != -1)
{
- if (!m_groupIdMatches.has_value())
+ const std::shared_ptr<const CPVRChannelGroupsContainer> groups{
+ CServiceBroker::GetPVRManager().ChannelGroups()};
+ if (groups)
{
- const std::shared_ptr<const CPVRChannelGroup> group = CServiceBroker::GetPVRManager()
- .ChannelGroups()
- ->Get(m_bIsRadio)
- ->GetById(m_iChannelGroupID);
- m_groupIdMatches =
- group && (group->GetByUniqueID({tag->ClientID(), tag->UniqueChannelID()}) != nullptr);
+ const std::shared_ptr<const CPVRChannelGroup> group{
+ groups->Get(m_bIsRadio)->GetById(m_iChannelGroupID)};
+ return group && (group->GetByUniqueID({tag->ClientID(), tag->UniqueChannelID()}) != nullptr);
}
-
- return *m_groupIdMatches;
}
return true;
@@ -393,10 +414,12 @@ bool CPVREpgSearchFilter::MatchFreeToAir(const std::shared_ptr<const CPVREpgInfo
bool CPVREpgSearchFilter::MatchTimers(const std::shared_ptr<const CPVREpgInfoTag>& tag) const
{
- return (!m_bIgnorePresentTimers || !CServiceBroker::GetPVRManager().Timers()->GetTimerForEpgTag(tag));
+ return (!m_bIgnorePresentTimers ||
+ !CServiceBroker::GetPVRManager().Timers()->GetTimerForEpgTag(tag));
}
bool CPVREpgSearchFilter::MatchRecordings(const std::shared_ptr<const CPVREpgInfoTag>& tag) const
{
- return (!m_bIgnorePresentRecordings || !CServiceBroker::GetPVRManager().Recordings()->GetRecordingForEpgTag(tag));
+ return (!m_bIgnorePresentRecordings ||
+ !CServiceBroker::GetPVRManager().Recordings()->GetRecordingForEpgTag(tag));
}
diff --git a/xbmc/pvr/epg/EpgSearchFilter.h b/xbmc/pvr/epg/EpgSearchFilter.h
index 5966f6b057..960d89f472 100644
--- a/xbmc/pvr/epg/EpgSearchFilter.h
+++ b/xbmc/pvr/epg/EpgSearchFilter.h
@@ -9,163 +9,173 @@
#pragma once
#include "XBDateTime.h"
+#include "pvr/PVRConstants.h" // PVR_CLIENT_INVALID_UID
#include "pvr/epg/EpgSearchData.h"
#include <memory>
-#include <optional>
#include <string>
#include <vector>
namespace PVR
{
- class CPVREpgInfoTag;
+static constexpr int PVR_EPG_SEARCH_INVALID_DATABASE_ID{-1};
- class CPVREpgSearchFilter
- {
- public:
- CPVREpgSearchFilter() = delete;
+class CPVREpgInfoTag;
- /*!
- * @brief ctor.
- * @param bRadio the type of channels to search - if true, 'radio'. 'tv', otherwise.
- */
- explicit CPVREpgSearchFilter(bool bRadio);
+class CPVREpgSearchFilter
+{
+public:
+ CPVREpgSearchFilter() = delete;
+
+ /*!
+ * @brief ctor.
+ * @param bRadio the type of channels to search - if true, 'radio'. 'tv', otherwise.
+ */
+ explicit CPVREpgSearchFilter(bool bRadio);
+
+ /*!
+ * @brief Clear this filter.
+ */
+ void Reset();
+
+ /*!
+ * @brief Return the path for this filter.
+ * @return the path.
+ */
+ std::string GetPath() const;
- /*!
- * @brief Clear this filter.
- */
- void Reset();
+ /*!
+ * @brief Check if a tag will be filtered or not.
+ * @param tag The tag to check.
+ * @return True if this tag matches the filter, false if not.
+ */
+ bool FilterEntry(const std::shared_ptr<const CPVREpgInfoTag>& tag) const;
- /*!
- * @brief Return the path for this filter.
- * @return the path.
- */
- std::string GetPath() const;
+ /*!
+ * @brief remove duplicates from a list of epg tags.
+ * @param results The list of epg tags.
+ */
+ static void RemoveDuplicates(std::vector<std::shared_ptr<CPVREpgInfoTag>>& results);
- /*!
- * @brief Check if a tag will be filtered or not.
- * @param tag The tag to check.
- * @return True if this tag matches the filter, false if not.
- */
- bool FilterEntry(const std::shared_ptr<const CPVREpgInfoTag>& tag) const;
+ /*!
+ * @brief Get the type of channels to search.
+ * @return true, if 'radio'. false, otherwise.
+ */
+ bool IsRadio() const { return m_bIsRadio; }
- /*!
- * @brief remove duplicates from a list of epg tags.
- * @param results The list of epg tags.
- */
- static void RemoveDuplicates(std::vector<std::shared_ptr<CPVREpgInfoTag>>& results);
+ const std::string& GetSearchTerm() const { return m_searchData.m_strSearchTerm; }
+ void SetSearchTerm(const std::string& strSearchTerm);
- /*!
- * @brief Get the type of channels to search.
- * @return true, if 'radio'. false, otherwise.
- */
- bool IsRadio() const { return m_bIsRadio; }
+ void SetSearchPhrase(const std::string& strSearchPhrase);
- const std::string& GetSearchTerm() const { return m_searchData.m_strSearchTerm; }
- void SetSearchTerm(const std::string& strSearchTerm);
+ bool IsCaseSensitive() const { return m_bIsCaseSensitive; }
+ void SetCaseSensitive(bool bIsCaseSensitive);
- void SetSearchPhrase(const std::string& strSearchPhrase);
+ bool ShouldSearchInDescription() const { return m_searchData.m_bSearchInDescription; }
+ void SetSearchInDescription(bool bSearchInDescription);
- bool IsCaseSensitive() const { return m_bIsCaseSensitive; }
- void SetCaseSensitive(bool bIsCaseSensitive);
+ int GetGenreType() const { return m_searchData.m_iGenreType; }
+ void SetGenreType(int iGenreType);
- bool ShouldSearchInDescription() const { return m_searchData.m_bSearchInDescription; }
- void SetSearchInDescription(bool bSearchInDescription);
+ int GetMinimumDuration() const { return m_iMinimumDuration; }
+ void SetMinimumDuration(int iMinimumDuration);
- int GetGenreType() const { return m_searchData.m_iGenreType; }
- void SetGenreType(int iGenreType);
+ int GetMaximumDuration() const { return m_iMaximumDuration; }
+ void SetMaximumDuration(int iMaximumDuration);
- int GetMinimumDuration() const { return m_iMinimumDuration; }
- void SetMinimumDuration(int iMinimumDuration);
+ bool ShouldIgnoreFinishedBroadcasts() const { return m_searchData.m_bIgnoreFinishedBroadcasts; }
+ void SetIgnoreFinishedBroadcasts(bool bIgnoreFinishedBroadcasts);
- int GetMaximumDuration() const { return m_iMaximumDuration; }
- void SetMaximumDuration(int iMaximumDuration);
+ bool ShouldIgnoreFutureBroadcasts() const { return m_searchData.m_bIgnoreFutureBroadcasts; }
+ void SetIgnoreFutureBroadcasts(bool bIgnoreFutureBroadcasts);
- bool ShouldIgnoreFinishedBroadcasts() const { return m_searchData.m_bIgnoreFinishedBroadcasts; }
- void SetIgnoreFinishedBroadcasts(bool bIgnoreFinishedBroadcasts);
+ const CDateTime& GetStartDateTime() const { return m_searchData.m_startDateTime; }
+ void SetStartDateTime(const CDateTime& startDateTime);
- bool ShouldIgnoreFutureBroadcasts() const { return m_searchData.m_bIgnoreFutureBroadcasts; }
- void SetIgnoreFutureBroadcasts(bool bIgnoreFutureBroadcasts);
+ bool IsStartAnyTime() const { return m_searchData.m_startAnyTime; }
+ void SetStartAnyTime(bool startAnyTime);
- const CDateTime& GetStartDateTime() const { return m_searchData.m_startDateTime; }
- void SetStartDateTime(const CDateTime& startDateTime);
+ const CDateTime& GetEndDateTime() const { return m_searchData.m_endDateTime; }
+ void SetEndDateTime(const CDateTime& endDateTime);
- const CDateTime& GetEndDateTime() const { return m_searchData.m_endDateTime; }
- void SetEndDateTime(const CDateTime& endDateTime);
+ bool IsEndAnyTime() const { return m_searchData.m_endAnyTime; }
+ void SetEndAnyTime(bool endAnyTime);
- bool ShouldIncludeUnknownGenres() const { return m_searchData.m_bIncludeUnknownGenres; }
- void SetIncludeUnknownGenres(bool bIncludeUnknownGenres);
+ bool ShouldIncludeUnknownGenres() const { return m_searchData.m_bIncludeUnknownGenres; }
+ void SetIncludeUnknownGenres(bool bIncludeUnknownGenres);
- bool ShouldRemoveDuplicates() const { return m_bRemoveDuplicates; }
- void SetRemoveDuplicates(bool bRemoveDuplicates);
+ bool ShouldRemoveDuplicates() const { return m_bRemoveDuplicates; }
+ void SetRemoveDuplicates(bool bRemoveDuplicates);
- int GetClientID() const { return m_iClientID; }
- void SetClientID(int iClientID);
+ int GetClientID() const { return m_iClientID; }
+ void SetClientID(int iClientID);
- int GetChannelGroupID() const { return m_iChannelGroupID; }
- void SetChannelGroupID(int iChannelGroupID);
+ int GetChannelGroupID() const { return m_iChannelGroupID; }
+ void SetChannelGroupID(int iChannelGroupID);
- int GetChannelUID() const { return m_iChannelUID; }
- void SetChannelUID(int iChannelUID);
+ int GetChannelUID() const { return m_iChannelUID; }
+ void SetChannelUID(int iChannelUID);
- bool IsFreeToAirOnly() const { return m_bFreeToAirOnly; }
- void SetFreeToAirOnly(bool bFreeToAirOnly);
+ bool IsFreeToAirOnly() const { return m_bFreeToAirOnly; }
+ void SetFreeToAirOnly(bool bFreeToAirOnly);
- bool ShouldIgnorePresentTimers() const { return m_bIgnorePresentTimers; }
- void SetIgnorePresentTimers(bool bIgnorePresentTimers);
+ bool ShouldIgnorePresentTimers() const { return m_bIgnorePresentTimers; }
+ void SetIgnorePresentTimers(bool bIgnorePresentTimers);
- bool ShouldIgnorePresentRecordings() const { return m_bIgnorePresentRecordings; }
- void SetIgnorePresentRecordings(bool bIgnorePresentRecordings);
+ bool ShouldIgnorePresentRecordings() const { return m_bIgnorePresentRecordings; }
+ void SetIgnorePresentRecordings(bool bIgnorePresentRecordings);
- int GetDatabaseId() const { return m_iDatabaseId; }
- void SetDatabaseId(int iDatabaseId);
+ int GetDatabaseId() const { return m_iDatabaseId; }
+ void SetDatabaseId(int iDatabaseId);
- const std::string& GetTitle() const { return m_title; }
- void SetTitle(const std::string& title);
+ const std::string& GetTitle() const { return m_title; }
+ void SetTitle(const std::string& title);
- const CDateTime& GetLastExecutedDateTime() const { return m_lastExecutedDateTime; }
- void SetLastExecutedDateTime(const CDateTime& lastExecutedDateTime);
+ const std::string& GetIconPath() const { return m_iconPath; }
+ void SetIconPath(const std::string& iconPath);
- const PVREpgSearchData& GetEpgSearchData() const { return m_searchData; }
- void SetEpgSearchDataFiltered() { m_bEpgSearchDataFiltered = true; }
+ const CDateTime& GetLastExecutedDateTime() const { return m_lastExecutedDateTime; }
+ void SetLastExecutedDateTime(const CDateTime& lastExecutedDateTime);
- bool IsChanged() const { return m_bChanged; }
- void SetChanged(bool bChanged) { m_bChanged = bChanged; }
+ const PVREpgSearchData& GetEpgSearchData() const { return m_searchData; }
+ void SetEpgSearchDataFiltered() { m_bEpgSearchDataFiltered = true; }
- private:
- bool MatchGenre(const std::shared_ptr<const CPVREpgInfoTag>& tag) const;
- bool MatchDuration(const std::shared_ptr<const CPVREpgInfoTag>& tag) const;
- bool MatchStartAndEndTimes(const std::shared_ptr<const CPVREpgInfoTag>& tag) const;
- bool MatchSearchTerm(const std::shared_ptr<const CPVREpgInfoTag>& tag) const;
- bool MatchChannel(const std::shared_ptr<const CPVREpgInfoTag>& tag) const;
- bool MatchChannelGroup(const std::shared_ptr<const CPVREpgInfoTag>& tag) const;
- bool MatchFreeToAir(const std::shared_ptr<const CPVREpgInfoTag>& tag) const;
- bool MatchTimers(const std::shared_ptr<const CPVREpgInfoTag>& tag) const;
- bool MatchRecordings(const std::shared_ptr<const CPVREpgInfoTag>& tag) const;
+ bool IsChanged() const { return m_bChanged; }
+ void SetChanged(bool bChanged) { m_bChanged = bChanged; }
- bool m_bChanged = false;
+private:
+ bool MatchGenre(const std::shared_ptr<const CPVREpgInfoTag>& tag) const;
+ bool MatchDuration(const std::shared_ptr<const CPVREpgInfoTag>& tag) const;
+ bool MatchStartAndEndTimes(const std::shared_ptr<const CPVREpgInfoTag>& tag) const;
+ bool MatchSearchTerm(const std::shared_ptr<const CPVREpgInfoTag>& tag) const;
+ bool MatchChannel(const std::shared_ptr<const CPVREpgInfoTag>& tag) const;
+ bool MatchChannelGroup(const std::shared_ptr<const CPVREpgInfoTag>& tag) const;
+ bool MatchFreeToAir(const std::shared_ptr<const CPVREpgInfoTag>& tag) const;
+ bool MatchTimers(const std::shared_ptr<const CPVREpgInfoTag>& tag) const;
+ bool MatchRecordings(const std::shared_ptr<const CPVREpgInfoTag>& tag) const;
- PVREpgSearchData m_searchData;
- bool m_bEpgSearchDataFiltered = false;
+ bool m_bChanged = false;
- bool m_bIsCaseSensitive; /*!< Do a case sensitive search */
- int m_iMinimumDuration; /*!< The minimum duration for an entry */
- int m_iMaximumDuration; /*!< The maximum duration for an entry */
- bool m_bRemoveDuplicates; /*!< True to remove duplicate events, false if not */
+ PVREpgSearchData m_searchData;
+ bool m_bEpgSearchDataFiltered = false;
- // PVR specific filters
- bool m_bIsRadio; /*!< True to filter radio channels only, false to tv only */
- int m_iClientID = -1; /*!< The client id */
- int m_iChannelGroupID{-1}; /*! The channel group id */
- int m_iChannelUID = -1; /*!< The channel uid */
- bool m_bFreeToAirOnly; /*!< Include free to air channels only */
- bool m_bIgnorePresentTimers; /*!< True to ignore currently present timers (future recordings), false if not */
- bool m_bIgnorePresentRecordings; /*!< True to ignore currently active recordings, false if not */
+ bool m_bIsCaseSensitive; /*!< Do a case sensitive search */
+ int m_iMinimumDuration; /*!< The minimum duration for an entry */
+ int m_iMaximumDuration; /*!< The maximum duration for an entry */
+ bool m_bRemoveDuplicates; /*!< True to remove duplicate events, false if not */
- mutable std::optional<bool> m_groupIdMatches;
+ // PVR specific filters
+ bool m_bIsRadio; /*!< True to filter radio channels only, false to tv only */
+ int m_iClientID = PVR_CLIENT_INVALID_UID; /*!< The client id */
+ int m_iChannelGroupID{-1}; /*!< The channel group id */
+ int m_iChannelUID = -1; /*!< The channel uid */
+ bool m_bFreeToAirOnly; /*!< Include free to air channels only */
+ bool m_bIgnorePresentTimers; /*!< True to ignore currently present timers, false if not */
+ bool m_bIgnorePresentRecordings; /*!< True to ignore currently active recordings, false if not */
- int m_iDatabaseId = -1;
- std::string m_title;
- CDateTime m_lastExecutedDateTime;
- };
-}
+ int m_iDatabaseId{PVR_EPG_SEARCH_INVALID_DATABASE_ID};
+ std::string m_title;
+ std::string m_iconPath;
+ CDateTime m_lastExecutedDateTime;
+};
+} // namespace PVR
diff --git a/xbmc/pvr/filesystem/PVRGUIDirectory.cpp b/xbmc/pvr/filesystem/PVRGUIDirectory.cpp
index 63341bd707..1fadc5d6d2 100644
--- a/xbmc/pvr/filesystem/PVRGUIDirectory.cpp
+++ b/xbmc/pvr/filesystem/PVRGUIDirectory.cpp
@@ -14,8 +14,10 @@
#include "guilib/LocalizeStrings.h"
#include "guilib/WindowIDs.h"
#include "input/WindowTranslator.h"
+#include "pvr/PVRConstants.h" // PVR_CLIENT_INVALID_UID
#include "pvr/PVRManager.h"
-#include "pvr/addons/PVRClient.h" // PVR_ANY_CLIENT_ID
+#include "pvr/PVRPathUtils.h"
+#include "pvr/addons/PVRClient.h"
#include "pvr/addons/PVRClients.h"
#include "pvr/channels/PVRChannel.h"
#include "pvr/channels/PVRChannelGroupMember.h"
@@ -23,8 +25,12 @@
#include "pvr/channels/PVRChannelGroupsContainer.h"
#include "pvr/channels/PVRChannelsPath.h"
#include "pvr/epg/EpgContainer.h"
+#include "pvr/epg/EpgSearch.h"
#include "pvr/epg/EpgSearchFilter.h"
#include "pvr/epg/EpgSearchPath.h"
+#include "pvr/providers/PVRProvider.h"
+#include "pvr/providers/PVRProviders.h"
+#include "pvr/providers/PVRProvidersPath.h"
#include "pvr/recordings/PVRRecording.h"
#include "pvr/recordings/PVRRecordings.h"
#include "pvr/recordings/PVRRecordingsPath.h"
@@ -105,6 +111,19 @@ bool GetRootDirectory(bool bRadio, CFileItemList& results)
results.Add(item);
}
+ // Providers
+ if (CServiceBroker::GetPVRManager().Providers()->GetNumProviders() > 1)
+ {
+ item = std::make_shared<CFileItem>(bRadio ? CPVRProvidersPath::PATH_RADIO_PROVIDERS
+ : CPVRProvidersPath::PATH_TV_PROVIDERS,
+ true);
+ item->SetLabel(g_localizeStrings.Get(19334)); // Providers
+ item->SetProperty("node.target", CWindowTranslator::TranslateWindow(
+ bRadio ? WINDOW_RADIO_PROVIDERS : WINDOW_TV_PROVIDERS));
+ item->SetArt("icon", "DefaultPVRProviders.png");
+ results.Add(std::move(item));
+ }
+
// Timers/Timer rules
// - always present, because Reminders are always available, no client support needed for this
item = std::make_shared<CFileItem>(
@@ -208,6 +227,14 @@ bool CPVRGUIDirectory::GetDirectory(CFileItemList& results) const
}
return true;
}
+ else if (StringUtils::StartsWith(fileName, "providers"))
+ {
+ if (CServiceBroker::GetPVRManager().IsStarted())
+ {
+ return GetProvidersDirectory(results);
+ }
+ return true;
+ }
else if (StringUtils::StartsWith(fileName, "timers"))
{
if (CServiceBroker::GetPVRManager().IsStarted())
@@ -224,6 +251,8 @@ bool CPVRGUIDirectory::GetDirectory(CFileItemList& results) const
{
if (path.IsSavedSearchesRoot())
return GetSavedSearchesDirectory(path.IsRadio(), results);
+ else if (path.IsSavedSearch())
+ return GetSavedSearchResults(path.IsRadio(), path.GetId(), results);
}
return true;
}
@@ -282,9 +311,39 @@ bool IsDirectoryMember(const std::string& strDirectory,
return StringUtils::StartsWithNoCase(strUseEntryDirectory, strUseDirectory);
}
-void GetSubDirectories(const CPVRRecordingsPath& recParentPath,
- const std::vector<std::shared_ptr<CPVRRecording>>& recordings,
- CFileItemList& results)
+template<class T>
+class CByClientAndProviderFilter
+{
+public:
+ explicit CByClientAndProviderFilter(const CURL& url)
+ : m_applyFilter(UTILS::GetClientAndProviderFromPath(url, m_clientId, m_providerId))
+ {
+ }
+
+ bool Filter(const std::shared_ptr<T>& item) const
+ {
+ if (m_applyFilter)
+ {
+ if (item->ClientID() != m_clientId)
+ return true;
+
+ if ((m_providerId != PVR_PROVIDER_INVALID_UID) && (item->ClientProviderUid() != m_providerId))
+ return true;
+ }
+ return false;
+ }
+
+private:
+ int m_clientId{PVR_CLIENT_INVALID_UID};
+ int m_providerId{PVR_PROVIDER_INVALID_UID};
+ bool m_applyFilter{false};
+};
+
+template<typename T>
+void GetGetRecordingsSubDirectories(const CPVRRecordingsPath& recParentPath,
+ const std::vector<std::shared_ptr<CPVRRecording>>& recordings,
+ const CByClientAndProviderFilter<T>& byClientAndProviderFilter,
+ CFileItemList& results)
{
// Only active recordings are fetched to provide sub directories.
// Not applicable for deleted view which is supposed to be flattened.
@@ -293,6 +352,9 @@ void GetSubDirectories(const CPVRRecordingsPath& recParentPath,
for (const auto& recording : recordings)
{
+ if (byClientAndProviderFilter.Filter(recording))
+ continue;
+
if (recording->IsDeleted())
continue;
@@ -367,6 +429,54 @@ void GetSubDirectories(const CPVRRecordingsPath& recParentPath,
} // unnamed namespace
+bool CPVRGUIDirectory::GetRecordingsDirectoryInfo(CFileItem& item)
+{
+ CFileItemList results;
+ const CPVRGUIDirectory dir{item.GetPath()};
+ if (dir.GetRecordingsDirectory(results))
+ {
+ item.SetLabelPreformatted(true);
+ item.SetProperty("totalepisodes", 0);
+ item.SetProperty("watchedepisodes", 0);
+ item.SetProperty("unwatchedepisodes", 0);
+ item.SetProperty("inprogressepisodes", 0);
+
+ int64_t sizeInBytes{0};
+
+ for (const auto& result : results.GetList())
+ {
+ const auto recording{result->GetPVRRecordingInfoTag()};
+ if (!recording)
+ continue;
+
+ if (item.m_dateTime.IsValid() || (item.m_dateTime < recording->RecordingTimeAsLocalTime()))
+ item.m_dateTime = recording->RecordingTimeAsLocalTime();
+
+ item.IncrementProperty("totalepisodes", 1);
+
+ if (recording->GetPlayCount() == 0)
+ item.IncrementProperty("unwatchedepisodes", 1);
+ else
+ item.IncrementProperty("watchedepisodes", 1);
+
+ if (recording->GetResumePoint().IsPartWay())
+ item.IncrementProperty("inprogressepisodes", 1);
+
+ sizeInBytes += recording->GetSizeInBytes();
+ }
+
+ item.SetProperty("recordingsize", StringUtils::SizeToString(sizeInBytes));
+
+ if (item.GetProperty("unwatchedepisodes").asInteger() > 0)
+ item.SetOverlayImage(CGUIListItem::ICON_OVERLAY_UNWATCHED);
+ else
+ item.SetOverlayImage(CGUIListItem::ICON_OVERLAY_WATCHED);
+
+ return true;
+ }
+ return false;
+}
+
bool CPVRGUIDirectory::GetRecordingsDirectory(CFileItemList& results) const
{
results.SetContent("recordings");
@@ -397,17 +507,24 @@ bool CPVRGUIDirectory::GetRecordingsDirectory(CFileItemList& results) const
const CPVRRecordingsPath recPath(m_url.GetWithoutOptions());
if (recPath.IsValid())
{
+ // Filter by client id/provider id ?
+ const CByClientAndProviderFilter<CPVRRecording> byClientAndProviderFilter{m_url};
+
// Get the directory structure if in non-flatten mode
// Deleted view is always flatten. So only for an active view
const std::string strDirectory = recPath.GetUnescapedDirectoryPath();
if (!recPath.IsDeleted() && bGrouped)
- GetSubDirectories(recPath, recordings, results);
+ GetGetRecordingsSubDirectories(recPath, recordings, byClientAndProviderFilter, results);
// get all files of the current directory or recursively all files starting at the current directory if in flatten mode
std::shared_ptr<CFileItem> item;
for (const auto& recording : recordings)
{
// Omit recordings not matching criteria
+
+ if (byClientAndProviderFilter.Filter(recording))
+ continue;
+
if (recording->IsDeleted() != recPath.IsDeleted() ||
recording->IsRadio() != recPath.IsRadio() ||
!IsDirectoryMember(strDirectory, recording->Directory(), bGrouped))
@@ -435,6 +552,24 @@ bool CPVRGUIDirectory::GetSavedSearchesDirectory(bool bRadio, CFileItemList& res
return true;
}
+bool CPVRGUIDirectory::GetSavedSearchResults(bool isRadio, int id, CFileItemList& results) const
+{
+ auto& epgContainer{CServiceBroker::GetPVRManager().EpgContainer()};
+ const std::shared_ptr<CPVREpgSearchFilter> filter{epgContainer.GetSavedSearchById(isRadio, id)};
+ if (filter)
+ {
+ CPVREpgSearch search(*filter);
+ search.Execute();
+ const auto tags{search.GetResults()};
+ for (const auto& tag : tags)
+ {
+ results.Add(std::make_shared<CFileItem>(tag));
+ }
+ return true;
+ }
+ return false;
+}
+
bool CPVRGUIDirectory::GetChannelGroupsDirectory(bool bRadio,
bool bExcludeHidden,
CFileItemList& results)
@@ -590,6 +725,7 @@ bool CPVRGUIDirectory::GetChannelsDirectory(CFileItemList& results) const
}
else if (path.IsChannelGroup())
{
+ const CByClientAndProviderFilter<const CPVRChannel> byClientAndProviderFilter{m_url};
const bool playedOnly{(m_url.HasOption("view") && (m_url.GetOption("view") == "lastplayed"))};
const bool dateAdded{(m_url.HasOption("view") && (m_url.GetOption("view") == "dateadded"))};
const bool showHiddenChannels{path.IsHiddenChannelGroup()};
@@ -597,16 +733,38 @@ bool CPVRGUIDirectory::GetChannelsDirectory(CFileItemList& results) const
GetChannelGroupMembers(path)};
for (const auto& groupMember : groupMembers)
{
- if (showHiddenChannels != groupMember->Channel()->IsHidden())
+ const std::shared_ptr<const CPVRChannel> channel{groupMember->Channel()};
+
+ if (byClientAndProviderFilter.Filter(channel))
continue;
- if (playedOnly && !groupMember->Channel()->LastWatched())
+ if (showHiddenChannels != channel->IsHidden())
continue;
- if (dateAdded && (!groupMember->Channel()->DateTimeAdded().IsValid() ||
- groupMember->Channel()->LastWatched()))
+ if (playedOnly && !channel->LastWatched())
continue;
+ if (dateAdded)
+ {
+ if (channel->LastWatched())
+ continue;
+
+ const CDateTime dtChannelAdded{channel->DateTimeAdded()};
+ if (!dtChannelAdded.IsValid())
+ continue;
+
+ const std::shared_ptr<const CPVRClient> client{
+ CServiceBroker::GetPVRManager().GetClient(groupMember->ChannelClientID())};
+ if (client)
+ {
+ const CDateTime& dtFirstChannelsAdded{client->GetDateTimeFirstChannelsAdded()};
+ if (dtFirstChannelsAdded.IsValid() && (dtChannelAdded <= dtFirstChannelsAdded))
+ {
+ continue; // Ignore channels added on very first GetChannels call for the client.
+ }
+ }
+ }
+
results.Add(std::make_shared<CFileItem>(groupMember));
}
return true;
@@ -655,7 +813,7 @@ bool GetTimersSubDirectory(const CPVRTimersPath& path,
for (const auto& timer : timers)
{
if ((timer->IsRadio() == bRadio) && timer->HasParent() &&
- (iClientId == PVR_ANY_CLIENT_ID || timer->ClientID() == iClientId) &&
+ (iClientId == PVR_CLIENT_INVALID_UID || timer->ClientID() == iClientId) &&
(timer->ParentClientIndex() == iParentId) && (!bHideDisabled || !timer->IsDisabled()))
{
item = std::make_shared<CFileItem>(timer);
@@ -711,3 +869,117 @@ bool CPVRGUIDirectory::GetTimersDirectory(CFileItemList& results) const
return false;
}
+
+bool CPVRGUIDirectory::GetProvidersDirectory(CFileItemList& results) const
+{
+ const CPVRProvidersPath path(m_url.GetWithoutOptions());
+ if (path.IsValid())
+ {
+ if (path.IsProvidersRoot())
+ {
+ const std::shared_ptr<const CPVRChannelGroupsContainer> groups{
+ CServiceBroker::GetPVRManager().ChannelGroups()};
+ const std::shared_ptr<const CPVRRecordings> recordings{
+ CServiceBroker::GetPVRManager().Recordings()};
+ const std::vector<std::shared_ptr<CPVRProvider>> providers{
+ CServiceBroker::GetPVRManager().Providers()->GetProviders()};
+ for (const auto& provider : providers)
+ {
+ if (!groups->HasChannelForProvider(path.IsRadio(), provider->GetClientId(),
+ provider->GetUniqueId()) &&
+ !recordings->HasRecordingForProvider(path.IsRadio(), provider->GetClientId(),
+ provider->GetUniqueId()))
+ continue;
+
+ const CPVRProvidersPath providerPath{path.GetKind(), provider->GetClientId(),
+ provider->GetUniqueId()};
+ results.Add(std::make_shared<CFileItem>(providerPath, provider));
+ }
+ return true;
+ }
+ else if (path.IsProvider())
+ {
+ // Add items for channels and recordings, if at least one matching is available.
+
+ const std::shared_ptr<const CPVRChannelGroupsContainer> groups{
+ CServiceBroker::GetPVRManager().ChannelGroups()};
+ const unsigned int channelCount{groups->GetChannelCountByProvider(
+ path.IsRadio(), path.GetClientId(), path.GetProviderUid())};
+ if (channelCount > 0)
+ {
+ const CPVRProvidersPath channelsPath{path.GetKind(), path.GetClientId(),
+ path.GetProviderUid(), CPVRProvidersPath::CHANNELS};
+ auto channelsItem{std::make_shared<CFileItem>(channelsPath, true)};
+ channelsItem->SetLabel(g_localizeStrings.Get(19019)); // Channels
+ channelsItem->SetArt("icon", "DefaultPVRChannels.png");
+ channelsItem->SetProperty("totalcount", channelCount);
+ results.Add(std::move(channelsItem));
+ }
+
+ const std::shared_ptr<const CPVRRecordings> recordings{
+ CServiceBroker::GetPVRManager().Recordings()};
+ const unsigned int recordingCount{recordings->GetRecordingCountByProvider(
+ path.IsRadio(), path.GetClientId(), path.GetProviderUid())};
+ if (recordingCount > 0)
+ {
+ const CPVRProvidersPath recordingsPath{path.GetKind(), path.GetClientId(),
+ path.GetProviderUid(),
+ CPVRProvidersPath::RECORDINGS};
+ auto recordingsItem{std::make_shared<CFileItem>(recordingsPath, true)};
+ recordingsItem->SetLabel(g_localizeStrings.Get(19017)); // Recordings
+ recordingsItem->SetArt("icon", "DefaultPVRRecordings.png");
+ recordingsItem->SetProperty("totalcount", recordingCount);
+ results.Add(std::move(recordingsItem));
+ }
+
+ return true;
+ }
+ else if (path.IsChannels())
+ {
+ // Add all channels served by this provider.
+ const std::shared_ptr<const CPVRChannelGroup> group{
+ CServiceBroker::GetPVRManager().ChannelGroups()->GetGroupAll(path.IsRadio())};
+ if (group)
+ {
+ const bool checkUid{path.GetProviderUid() != PVR_PROVIDER_INVALID_UID};
+ const std::vector<std::shared_ptr<CPVRChannelGroupMember>> allGroupMembers{
+ group->GetMembers(CPVRChannelGroup::Include::ONLY_VISIBLE)};
+ for (const auto& allGroupMember : allGroupMembers)
+ {
+ const std::shared_ptr<const CPVRChannel> channel{allGroupMember->Channel()};
+
+ if (channel->ClientID() != path.GetClientId())
+ continue;
+
+ if (checkUid && channel->ClientProviderUid() != path.GetProviderUid())
+ continue;
+
+ results.Add(std::make_shared<CFileItem>(allGroupMember));
+ }
+ return true;
+ }
+ }
+ else if (path.IsRecordings())
+ {
+ // Add all recordings served by this provider.
+ const bool checkUid{path.GetProviderUid() != PVR_PROVIDER_INVALID_UID};
+ const std::vector<std::shared_ptr<CPVRRecording>> recordings{
+ CServiceBroker::GetPVRManager().Recordings()->GetAll()};
+ for (const auto& recording : recordings)
+ {
+ if (recording->IsRadio() != path.IsRadio())
+ continue;
+
+ if (recording->ClientID() != path.GetClientId())
+ continue;
+
+ if (checkUid && recording->ClientProviderUid() != path.GetProviderUid())
+ continue;
+
+ results.Add(std::make_shared<CFileItem>(recording));
+ }
+ return true;
+ }
+ }
+ return false;
+}
diff --git a/xbmc/pvr/filesystem/PVRGUIDirectory.h b/xbmc/pvr/filesystem/PVRGUIDirectory.h
index 462c55360c..925d97d0b3 100644
--- a/xbmc/pvr/filesystem/PVRGUIDirectory.h
+++ b/xbmc/pvr/filesystem/PVRGUIDirectory.h
@@ -12,6 +12,7 @@
#include <string>
+class CFileItem;
class CFileItemList;
namespace PVR
@@ -96,10 +97,25 @@ public:
*/
bool GetChannelsDirectory(CFileItemList& results) const;
+ /*!
+ * @brief Get the list of providers.
+ * @param results The file list to store the results in.
+ * @return True on success, false otherwise..
+ */
+ bool GetProvidersDirectory(CFileItemList& results) const;
+
+ /*!
+ * @brief Get info for a recording folder.
+ * @param item The folder.
+ * @return True on success, false otherwise..
+ */
+ static bool GetRecordingsDirectoryInfo(CFileItem& item);
+
private:
bool GetTimersDirectory(CFileItemList& results) const;
bool GetRecordingsDirectory(CFileItemList& results) const;
bool GetSavedSearchesDirectory(bool bRadio, CFileItemList& results) const;
+ bool GetSavedSearchResults(bool isRadio, int id, CFileItemList& results) const;
const CURL m_url;
};
diff --git a/xbmc/pvr/guilib/PVRGUIActionsChannels.cpp b/xbmc/pvr/guilib/PVRGUIActionsChannels.cpp
index 033ab0102b..5074e38a5a 100644
--- a/xbmc/pvr/guilib/PVRGUIActionsChannels.cpp
+++ b/xbmc/pvr/guilib/PVRGUIActionsChannels.cpp
@@ -19,6 +19,7 @@
#include "input/actions/ActionIDs.h"
#include "messaging/ApplicationMessenger.h"
#include "messaging/helpers/DialogOKHelper.h"
+#include "pvr/PVRConstants.h" // PVR_CLIENT_INVALID_UID
#include "pvr/PVRItem.h"
#include "pvr/PVRManager.h"
#include "pvr/PVRPlaybackState.h"
@@ -233,7 +234,7 @@ bool CPVRGUIActionsChannels::HideChannel(const CFileItem& item) const
bool CPVRGUIActionsChannels::StartChannelScan()
{
- return StartChannelScan(PVR_INVALID_CLIENT_ID);
+ return StartChannelScan(PVR_CLIENT_INVALID_UID);
}
bool CPVRGUIActionsChannels::StartChannelScan(int clientId)
@@ -246,7 +247,7 @@ bool CPVRGUIActionsChannels::StartChannelScan(int clientId)
CServiceBroker::GetPVRManager().Clients()->GetClientsSupportingChannelScan();
m_bChannelScanRunning = true;
- if (clientId != PVR_INVALID_CLIENT_ID)
+ if (clientId != PVR_CLIENT_INVALID_UID)
{
const auto it =
std::find_if(possibleScanClients.cbegin(), possibleScanClients.cend(),
diff --git a/xbmc/pvr/guilib/PVRGUIActionsEPG.cpp b/xbmc/pvr/guilib/PVRGUIActionsEPG.cpp
index 8dcf7113eb..866c3987e8 100644
--- a/xbmc/pvr/guilib/PVRGUIActionsEPG.cpp
+++ b/xbmc/pvr/guilib/PVRGUIActionsEPG.cpp
@@ -9,7 +9,9 @@
#include "PVRGUIActionsEPG.h"
#include "FileItem.h"
+#include "FileItemList.h"
#include "ServiceBroker.h"
+#include "dialogs/GUIDialogFileBrowser.h"
#include "dialogs/GUIDialogYesNo.h"
#include "guilib/GUIComponent.h"
#include "guilib/GUIKeyboardFactory.h"
@@ -27,6 +29,8 @@
#include "pvr/windows/GUIWindowPVRSearch.h"
#include "settings/Settings.h"
#include "settings/SettingsComponent.h"
+#include "storage/MediaManager.h"
+#include "utils/StringUtils.h"
#include "utils/Variant.h"
#include "utils/log.h"
@@ -200,6 +204,69 @@ bool CPVRGUIActionsEPG::RenameSavedSearch(const CFileItem& item)
return false;
}
+bool CPVRGUIActionsEPG::ChooseIconForSavedSearch(const CFileItem& item)
+{
+ const auto searchFilter{item.GetEPGSearchFilter()};
+
+ if (!searchFilter)
+ {
+ CLog::LogF(LOGERROR, "Wrong item type. No EPG search filter present.");
+ return false;
+ }
+
+ // setup our icon list
+ CFileItemList items;
+
+ // Add the current icon, if available.
+ const std::string iconPath{searchFilter->GetIconPath()};
+ auto current{std::make_shared<CFileItem>("icon://Current", false)};
+ current->SetArt("icon", iconPath.empty() ? "DefaultPVRSearch.png" : iconPath);
+ current->SetLabel(g_localizeStrings.Get(19282)); // Current icon
+ items.Add(std::move(current));
+
+ // And add a "No icon" entry as well.
+ auto nothumb{std::make_shared<CFileItem>("icon://None", false)};
+ nothumb->SetArt("icon", "DefaultPVRSearch.png");
+ nothumb->SetLabel(g_localizeStrings.Get(19283)); // No icon
+ items.Add(std::move(nothumb));
+
+ std::string icon;
+ VECSOURCES sources;
+ CServiceBroker::GetMediaManager().GetLocalDrives(sources);
+ if (!CGUIDialogFileBrowser::ShowAndGetImage(items, sources,
+ g_localizeStrings.Get(19285), // Browse for icon
+ icon))
+ return false;
+
+ if (icon == "icon://Current")
+ return true;
+
+ if (icon == "icon://None")
+ icon.clear();
+
+ searchFilter->SetIconPath(icon);
+ CServiceBroker::GetPVRManager().EpgContainer().PersistSavedSearch(*searchFilter);
+ return true;
+}
+
+bool CPVRGUIActionsEPG::DuplicateSavedSearch(const CFileItem& item)
+{
+ const auto searchFilter{item.GetEPGSearchFilter()};
+
+ if (!searchFilter)
+ {
+ CLog::LogF(LOGERROR, "Wrong item type. No EPG search filter present.");
+ return false;
+ }
+
+ const auto dupedSearchFilter{std::make_shared<CPVREpgSearchFilter>(*searchFilter)};
+ dupedSearchFilter->SetDatabaseId(PVR_EPG_SEARCH_INVALID_DATABASE_ID); // force new db entry
+ dupedSearchFilter->SetTitle(StringUtils::Format(g_localizeStrings.Get(19356), // Copy of '<title>'
+ searchFilter->GetTitle()));
+ CServiceBroker::GetPVRManager().EpgContainer().PersistSavedSearch(*dupedSearchFilter);
+ return true;
+}
+
bool CPVRGUIActionsEPG::DeleteSavedSearch(const CFileItem& item)
{
const auto searchFilter = item.GetEPGSearchFilter();
diff --git a/xbmc/pvr/guilib/PVRGUIActionsEPG.h b/xbmc/pvr/guilib/PVRGUIActionsEPG.h
index c66740e985..3f2ce6a1b5 100644
--- a/xbmc/pvr/guilib/PVRGUIActionsEPG.h
+++ b/xbmc/pvr/guilib/PVRGUIActionsEPG.h
@@ -69,6 +69,20 @@ public:
bool RenameSavedSearch(const CFileItem& item);
/*!
+ * @brief Choose an icon for a saved search. Opens an art chooser dialog.
+ * @param item The item containing a search filter.
+ * @return True on success, false otherwise.
+ */
+ bool ChooseIconForSavedSearch(const CFileItem& item);
+
+ /*!
+ * @brief Duplicate a saved search.
+ * @param item The item containing a search filter.
+ * @return True on success, false otherwise.
+ */
+ bool DuplicateSavedSearch(const CFileItem& item);
+
+ /*!
* @brief Delete a saved search. Opens confirmation dialog before deleting.
* @param item The item containing a search filter.
* @return True on success, false otherwise.
diff --git a/xbmc/pvr/guilib/PVRGUIActionsPlayback.cpp b/xbmc/pvr/guilib/PVRGUIActionsPlayback.cpp
index 954b46eb5e..a41feec3e7 100644
--- a/xbmc/pvr/guilib/PVRGUIActionsPlayback.cpp
+++ b/xbmc/pvr/guilib/PVRGUIActionsPlayback.cpp
@@ -162,9 +162,10 @@ bool CPVRGUIActionsPlayback::PlayRecording(const CFileItem& item, bool bCheckRes
}
else
{
- CFileItem* itemToPlay = new CFileItem(recording);
+ std::unique_ptr<CFileItem> itemToPlay{std::make_unique<CFileItem>(recording)};
itemToPlay->SetStartOffset(item.GetStartOffset());
- CServiceBroker::GetPVRManager().PlaybackState()->StartPlayback(itemToPlay);
+ CServiceBroker::GetPVRManager().PlaybackState()->StartPlayback(
+ itemToPlay, ContentUtils::PlayMode::CHECK_AUTO_PLAY_NEXT_ITEM, PVR_SOURCE::DEFAULT);
}
CheckAndSwitchToFullscreen(true);
}
@@ -213,9 +214,15 @@ bool CPVRGUIActionsPlayback::PlayEpgTag(
return false;
CPVRStreamProperties props;
+ PVR_ERROR retVal = client->StreamClosed();
+ if (retVal != PVR_ERROR_NO_ERROR)
+ CLog::LogFC(LOGERROR, LOGPVR, "Client error on call to StreamClosed(): {}",
+ CPVRClient::ToString(retVal));
+
client->GetEpgTagStreamProperties(epgTag, props);
- CFileItem* itemToPlay = nullptr;
+ std::unique_ptr<CFileItem> itemToPlay;
+ PVR_SOURCE source = DEFAULT;
if (props.EPGPlaybackAsLive())
{
const std::shared_ptr<CPVRChannelGroupMember> groupMember =
@@ -223,14 +230,15 @@ bool CPVRGUIActionsPlayback::PlayEpgTag(
if (!groupMember)
return false;
- itemToPlay = new CFileItem(groupMember);
+ source = PVR_SOURCE_EPG_AS_LIVE;
+ itemToPlay = std::make_unique<CFileItem>(groupMember);
}
else
{
- itemToPlay = new CFileItem(epgTag);
+ itemToPlay = std::make_unique<CFileItem>(epgTag);
}
- CServiceBroker::GetPVRManager().PlaybackState()->StartPlayback(itemToPlay, mode);
+ CServiceBroker::GetPVRManager().PlaybackState()->StartPlayback(itemToPlay, mode, source);
CheckAndSwitchToFullscreen(true);
return true;
}
@@ -313,7 +321,9 @@ bool CPVRGUIActionsPlayback::SwitchToChannel(const CFileItem& item, bool bCheckR
if (!groupMember)
return false;
- CServiceBroker::GetPVRManager().PlaybackState()->StartPlayback(new CFileItem(groupMember));
+ std::unique_ptr<CFileItem> itemToPlay{std::make_unique<CFileItem>(groupMember)};
+ CServiceBroker::GetPVRManager().PlaybackState()->StartPlayback(
+ itemToPlay, ContentUtils::PlayMode::CHECK_AUTO_PLAY_NEXT_ITEM, PVR_SOURCE::DEFAULT);
CheckAndSwitchToFullscreen(bFullscreen);
return true;
}
diff --git a/xbmc/pvr/guilib/PVRGUIActionsRecordings.cpp b/xbmc/pvr/guilib/PVRGUIActionsRecordings.cpp
index 316b8a896b..0366556764 100644
--- a/xbmc/pvr/guilib/PVRGUIActionsRecordings.cpp
+++ b/xbmc/pvr/guilib/PVRGUIActionsRecordings.cpp
@@ -17,8 +17,11 @@
#include "filesystem/IDirectory.h"
#include "guilib/GUIComponent.h"
#include "guilib/GUIWindowManager.h"
+#include "guilib/LocalizeStrings.h"
#include "guilib/WindowIDs.h"
+#include "messaging/helpers/DialogHelper.h"
#include "messaging/helpers/DialogOKHelper.h"
+#include "pvr/PVREventLogJob.h"
#include "pvr/PVRItem.h"
#include "pvr/PVRManager.h"
#include "pvr/addons/PVRClient.h"
@@ -26,8 +29,10 @@
#include "pvr/dialogs/GUIDialogPVRRecordingInfo.h"
#include "pvr/dialogs/GUIDialogPVRRecordingSettings.h"
#include "pvr/recordings/PVRRecording.h"
+#include "pvr/recordings/PVRRecordings.h"
#include "settings/Settings.h"
#include "threads/IRunnable.h"
+#include "utils/StringUtils.h"
#include "utils/Variant.h"
#include "utils/log.h"
@@ -40,6 +45,14 @@ using namespace KODI::MESSAGING;
namespace
{
+enum PVRRECORD_DELETE_AFTER_WATCH
+{
+ // Values must match those defined in settings.xml -> pvrrecord.deleteafterwatch
+ NO = 0,
+ ASK = 1,
+ YES = 2,
+};
+
class AsyncRecordingAction : private IRunnable
{
public:
@@ -182,6 +195,11 @@ private:
} // unnamed namespace
+CPVRGUIActionsRecordings::CPVRGUIActionsRecordings()
+ : m_settings({CSettings::SETTING_PVRRECORD_DELETEAFTERWATCH})
+{
+}
+
bool CPVRGUIActionsRecordings::ShowRecordingInfo(const CFileItem& item) const
{
if (!item.IsPVRRecording())
@@ -351,3 +369,84 @@ bool CPVRGUIActionsRecordings::ShowRecordingSettings(
return pDlgInfo->IsConfirmed();
}
+
+bool CPVRGUIActionsRecordings::ProcessDeleteAfterWatch(const CFileItem& item) const
+{
+ bool deleteRecording{false};
+
+ const int action{m_settings.GetIntValue(CSettings::SETTING_PVRRECORD_DELETEAFTERWATCH)};
+ switch (action)
+ {
+ case PVRRECORD_DELETE_AFTER_WATCH::NO:
+ deleteRecording = false;
+ break;
+
+ case PVRRECORD_DELETE_AFTER_WATCH::ASK:
+ deleteRecording = (HELPERS::ShowYesNoDialogLines(
+ CVariant{860}, // "Delete after watching"
+ CVariant{865}, // "Do you want to delete this recording?"
+ CVariant{""}, CVariant{item.GetPVRRecordingInfoTag()->GetTitle()}) ==
+ HELPERS::DialogResponse::CHOICE_YES);
+ break;
+
+ case PVRRECORD_DELETE_AFTER_WATCH::YES:
+ deleteRecording = true;
+ break;
+
+ default:
+ CLog::LogF(LOGERROR, "Unhandled delete after watch action! Defaulting to 'no delete'.");
+ deleteRecording = false;
+ break;
+ }
+
+ if (deleteRecording)
+ {
+ if (AsyncDeleteRecording().Execute(item))
+ {
+ CPVREventLogJob* job = new CPVREventLogJob;
+ job->AddEvent(true, // display a toast, and log event
+ EventLevel::Information, // info, no error
+ g_localizeStrings.Get(860), // "Delete after watching"
+ StringUtils::Format(g_localizeStrings.Get(866), // Recording deleted: <title>
+ item.GetPVRRecordingInfoTag()->GetTitle()),
+ item.GetPVRRecordingInfoTag()->IconPath());
+ CServiceBroker::GetJobManager()->AddJob(job, nullptr);
+ }
+ else
+ {
+ HELPERS::ShowOKDialogText(CVariant{257}, // "Error"
+ CVariant{19111}); // "PVR backend error."
+ return false;
+ }
+ }
+ return true;
+}
+
+bool CPVRGUIActionsRecordings::IncrementPlayCount(const CFileItem& item) const
+{
+ if (!item.IsPVRRecording())
+ return false;
+
+ if (item.GetPVRRecordingInfoTag()->IncrementPlayCount())
+ {
+ // Item must now be watched (because play count > 0).
+ return ProcessDeleteAfterWatch(item);
+ }
+ return false;
+}
+
+bool CPVRGUIActionsRecordings::MarkWatched(const CFileItem& item, bool watched) const
+{
+ if (!item.IsPVRRecording())
+ return false;
+
+ if (CServiceBroker::GetPVRManager().Recordings()->MarkWatched(item.GetPVRRecordingInfoTag(),
+ watched))
+ {
+ if (watched)
+ return ProcessDeleteAfterWatch(item);
+
+ return true;
+ }
+ return false;
+}
diff --git a/xbmc/pvr/guilib/PVRGUIActionsRecordings.h b/xbmc/pvr/guilib/PVRGUIActionsRecordings.h
index 795a2c73b3..cc2e0f5d5b 100644
--- a/xbmc/pvr/guilib/PVRGUIActionsRecordings.h
+++ b/xbmc/pvr/guilib/PVRGUIActionsRecordings.h
@@ -9,6 +9,7 @@
#pragma once
#include "pvr/IPVRComponent.h"
+#include "pvr/settings/PVRSettings.h"
#include <memory>
@@ -21,7 +22,7 @@ class CPVRRecording;
class CPVRGUIActionsRecordings : public IPVRComponent
{
public:
- CPVRGUIActionsRecordings() = default;
+ CPVRGUIActionsRecordings();
~CPVRGUIActionsRecordings() override = default;
/*!
@@ -73,6 +74,21 @@ public:
*/
bool UndeleteRecording(const CFileItem& item) const;
+ /*!
+ * @brief Increment the play count of a recording. Process "Delete after watching" action.
+ * @param item containing a recording for which the play count shall be incremented.
+ * @return true, if the recording's play count was incremented successfully, false otherwise.
+ */
+ bool IncrementPlayCount(const CFileItem& item) const;
+
+ /*!
+ * @brief Mark a recording watched or unwatched. Process "Delete after watching" action.
+ * @param item containing a recording to be marked watched or unwatched.
+ * @param watched Whether to mark the recording watched or unwatched.
+ * @return true, if the recording's watched state was changed successfully, false otherwise.
+ */
+ bool MarkWatched(const CFileItem& item, bool watched) const;
+
private:
CPVRGUIActionsRecordings(const CPVRGUIActionsRecordings&) = delete;
CPVRGUIActionsRecordings const& operator=(CPVRGUIActionsRecordings const&) = delete;
@@ -103,6 +119,15 @@ private:
* @return true, if the dialog was ended successfully, false otherwise.
*/
bool ShowRecordingSettings(const std::shared_ptr<CPVRRecording>& recording) const;
+
+ /*!
+ * @brief Process action according to "Delete after watching" setting value.
+ * @param item The watched recording.
+ * @return true on success, false otherwise.
+ */
+ bool ProcessDeleteAfterWatch(const CFileItem& item) const;
+
+ CPVRSettings m_settings;
};
namespace GUI
diff --git a/xbmc/pvr/guilib/PVRGUIChannelIconUpdater.cpp b/xbmc/pvr/guilib/PVRGUIChannelIconUpdater.cpp
index 11045c98d8..20f6114387 100644
--- a/xbmc/pvr/guilib/PVRGUIChannelIconUpdater.cpp
+++ b/xbmc/pvr/guilib/PVRGUIChannelIconUpdater.cpp
@@ -70,7 +70,8 @@ void CPVRGUIChannelIconUpdater::SearchAndUpdateMissingChannelIcons() const
{
const std::shared_ptr<CPVRChannel> channel = member->Channel();
- progressHandler->UpdateProgress(channel->ChannelName(), channelIndex++, members.size());
+ progressHandler->UpdateProgress(channel->ChannelName(), channelIndex++,
+ static_cast<unsigned int>(members.size()));
// skip if an icon is already set and exists
if (CFileUtils::Exists(channel->IconPath()))
diff --git a/xbmc/pvr/guilib/PVRGUIChannelNavigator.cpp b/xbmc/pvr/guilib/PVRGUIChannelNavigator.cpp
index cb3054371d..95ca4923e6 100644
--- a/xbmc/pvr/guilib/PVRGUIChannelNavigator.cpp
+++ b/xbmc/pvr/guilib/PVRGUIChannelNavigator.cpp
@@ -246,8 +246,7 @@ void CPVRGUIChannelNavigator::SwitchToCurrentChannel()
item = std::make_unique<CFileItem>(m_currentChannel);
}
- if (item)
- CServiceBroker::GetPVRManager().Get<PVR::GUI::Playback>().SwitchToChannel(*item, false);
+ CServiceBroker::GetPVRManager().Get<PVR::GUI::Playback>().SwitchToChannel(*item, false);
}
bool CPVRGUIChannelNavigator::IsPreview() const
diff --git a/xbmc/pvr/guilib/PVRGUIProgressHandler.cpp b/xbmc/pvr/guilib/PVRGUIProgressHandler.cpp
index f8765227dd..791a613bfc 100644
--- a/xbmc/pvr/guilib/PVRGUIProgressHandler.cpp
+++ b/xbmc/pvr/guilib/PVRGUIProgressHandler.cpp
@@ -43,7 +43,9 @@ void CPVRGUIProgressHandler::UpdateProgress(const std::string& strText, float fP
}
}
-void CPVRGUIProgressHandler::UpdateProgress(const std::string& strText, int iCurrent, int iMax)
+void CPVRGUIProgressHandler::UpdateProgress(const std::string& strText,
+ unsigned int iCurrent,
+ unsigned int iMax)
{
float fPercentage = (iCurrent * 100.0f) / iMax;
if (!std::isnan(fPercentage))
diff --git a/xbmc/pvr/guilib/PVRGUIProgressHandler.h b/xbmc/pvr/guilib/PVRGUIProgressHandler.h
index 6e7b72f20e..5c3aea1ecd 100644
--- a/xbmc/pvr/guilib/PVRGUIProgressHandler.h
+++ b/xbmc/pvr/guilib/PVRGUIProgressHandler.h
@@ -41,7 +41,7 @@ namespace PVR
* @param iCurrent The new current progress value, must be less or equal iMax.
* @param iMax The new maximum progress value, must be greater or equal iCurrent.
*/
- void UpdateProgress(const std::string& strText, int iCurrent, int iMax);
+ void UpdateProgress(const std::string& strText, unsigned int iCurrent, unsigned int iMax);
protected:
// CThread implementation
diff --git a/xbmc/pvr/guilib/guiinfo/PVRGUIInfo.cpp b/xbmc/pvr/guilib/guiinfo/PVRGUIInfo.cpp
index 9049cf64c3..2ec4b22947 100644
--- a/xbmc/pvr/guilib/guiinfo/PVRGUIInfo.cpp
+++ b/xbmc/pvr/guilib/guiinfo/PVRGUIInfo.cpp
@@ -425,9 +425,15 @@ bool CPVRGUIInfo::GetListItemAndPlayerLabel(const CFileItem* item,
case LISTITEM_SEASON:
case LISTITEM_EPISODE:
case LISTITEM_EPISODENAME:
+ case LISTITEM_EPISODEPART:
case LISTITEM_DIRECTOR:
case LISTITEM_CHANNEL_NUMBER:
case LISTITEM_PREMIERED:
+ case LISTITEM_PARENTAL_RATING:
+ case LISTITEM_PARENTAL_RATING_CODE:
+ case LISTITEM_PARENTAL_RATING_ICON:
+ case LISTITEM_PARENTAL_RATING_SOURCE:
+ case LISTITEM_MEDIAPROVIDERS:
break; // obtain value from channel/epg
default:
return false;
@@ -567,6 +573,50 @@ bool CPVRGUIInfo::GetListItemAndPlayerLabel(const CFileItem* item,
strValue =
CServiceBroker::GetPVRManager().GetClient(recording->ClientID())->GetInstanceName();
return true;
+ case VIDEOPLAYER_PARENTAL_RATING:
+ case LISTITEM_PARENTAL_RATING:
+ {
+ const unsigned int rating{recording->GetParentalRating()};
+ if (rating > 0)
+ {
+ strValue = std::to_string(rating);
+ return true;
+ }
+ return false;
+ }
+ case VIDEOPLAYER_PARENTAL_RATING_CODE:
+ case LISTITEM_PARENTAL_RATING_CODE:
+ strValue = recording->GetParentalRatingCode();
+ return true;
+ case VIDEOPLAYER_PARENTAL_RATING_ICON:
+ case LISTITEM_PARENTAL_RATING_ICON:
+ strValue = recording->GetParentalRatingIcon();
+ return true;
+ case VIDEOPLAYER_PARENTAL_RATING_SOURCE:
+ case LISTITEM_PARENTAL_RATING_SOURCE:
+ strValue = recording->GetParentalRatingSource();
+ return true;
+ case VIDEOPLAYER_EPISODEPART:
+ case LISTITEM_EPISODEPART:
+ if (recording->m_iEpisode > 0 && recording->EpisodePart() > 0)
+ {
+ strValue = std::to_string(recording->EpisodePart());
+ return true;
+ }
+ return false;
+ case MUSICPLAYER_MEDIAPROVIDERS:
+ case VIDEOPLAYER_MEDIAPROVIDERS:
+ case LISTITEM_MEDIAPROVIDERS:
+ if (recording->HasClientProvider())
+ {
+ const std::shared_ptr<const CPVRProvider> provider{recording->GetProvider()};
+ if (provider)
+ {
+ strValue = provider->GetName();
+ return true;
+ }
+ }
+ return false;
}
return false;
}
@@ -759,6 +809,11 @@ bool CPVRGUIInfo::GetListItemAndPlayerLabel(const CFileItem* item,
return true;
}
return false;
+ case VIDEOPLAYER_EPISODEPART:
+ case LISTITEM_EPISODEPART:
+ if (epgTag->EpisodeNumber() >= 0 && epgTag->EpisodePart() > 0)
+ strValue = std::to_string(epgTag->EpisodePart());
+ return true;
case VIDEOPLAYER_EPISODENAME:
case LISTITEM_EPISODENAME:
if (!CServiceBroker::GetPVRManager().IsParentalLocked(epgTag))
@@ -785,12 +840,16 @@ bool CPVRGUIInfo::GetListItemAndPlayerLabel(const CFileItem* item,
return true;
case VIDEOPLAYER_PARENTAL_RATING:
case LISTITEM_PARENTAL_RATING:
- if (epgTag->ParentalRating() > 0)
+ {
+ const unsigned int rating{epgTag->ParentalRating()};
+ if (rating > 0)
{
- strValue = std::to_string(epgTag->ParentalRating());
+ strValue = std::to_string(rating);
return true;
}
return false;
+ }
+ case VIDEOPLAYER_PARENTAL_RATING_CODE:
case LISTITEM_PARENTAL_RATING_CODE:
strValue = epgTag->ParentalRatingCode();
return true;
@@ -800,6 +859,14 @@ bool CPVRGUIInfo::GetListItemAndPlayerLabel(const CFileItem* item,
case LISTITEM_PVR_INSTANCE_NAME:
strValue = CServiceBroker::GetPVRManager().GetClient(epgTag->ClientID())->GetInstanceName();
return true;
+ case VIDEOPLAYER_PARENTAL_RATING_ICON:
+ case LISTITEM_PARENTAL_RATING_ICON:
+ strValue = epgTag->ParentalRatingIcon();
+ return true;
+ case VIDEOPLAYER_PARENTAL_RATING_SOURCE:
+ case LISTITEM_PARENTAL_RATING_SOURCE:
+ strValue = epgTag->ParentalRatingSource();
+ return true;
case VIDEOPLAYER_PREMIERED:
case LISTITEM_PREMIERED:
if (epgTag->FirstAired().IsValid())
@@ -888,6 +955,19 @@ bool CPVRGUIInfo::GetListItemAndPlayerLabel(const CFileItem* item,
return true;
}
break;
+ case MUSICPLAYER_MEDIAPROVIDERS:
+ case VIDEOPLAYER_MEDIAPROVIDERS:
+ case LISTITEM_MEDIAPROVIDERS:
+ if (channel->HasClientProvider())
+ {
+ const std::shared_ptr<const CPVRProvider> provider{channel->GetProvider()};
+ if (provider)
+ {
+ strValue = provider->GetName();
+ return true;
+ }
+ }
+ break;
}
}
@@ -1370,14 +1450,17 @@ bool CPVRGUIInfo::GetPVRInt(const CFileItem* item, const CGUIInfo& info, int& iV
iValue = GetTimeShiftSeekPercent();
return true;
case PVR_ACTUAL_STREAM_SIG_PROGR:
- iValue = std::lrintf(static_cast<float>(m_qualityInfo.Signal()) / 0xFFFF * 100);
+ iValue =
+ static_cast<int>(std::lrintf(static_cast<float>(m_qualityInfo.Signal()) / 0xFFFF * 100));
return true;
case PVR_ACTUAL_STREAM_SNR_PROGR:
- iValue = std::lrintf(static_cast<float>(m_qualityInfo.SNR()) / 0xFFFF * 100);
+ iValue =
+ static_cast<int>(std::lrintf(static_cast<float>(m_qualityInfo.SNR()) / 0xFFFF * 100));
return true;
case PVR_BACKEND_DISKSPACE_PROGR:
if (m_iBackendDiskTotal > 0)
- iValue = std::lrintf(static_cast<float>(m_iBackendDiskUsed) / m_iBackendDiskTotal * 100);
+ iValue = static_cast<int>(
+ std::lrintf(static_cast<float>(m_iBackendDiskUsed) / m_iBackendDiskTotal * 100));
else
iValue = 0xFF;
return true;
@@ -2096,7 +2179,7 @@ int CPVRGUIInfo::GetTimeShiftSeekPercent() const
float percentPerSecond = 100.0f / totalTime;
float percent = progress + percentPerSecond * seekSize;
percent = std::max(0.0f, std::min(percent, 100.0f));
- return std::lrintf(percent);
+ return static_cast<int>(std::lrintf(percent));
}
return progress;
}
diff --git a/xbmc/pvr/guilib/guiinfo/PVRGUITimesInfo.cpp b/xbmc/pvr/guilib/guiinfo/PVRGUITimesInfo.cpp
index 4691fd6033..e2c320a19c 100644
--- a/xbmc/pvr/guilib/guiinfo/PVRGUITimesInfo.cpp
+++ b/xbmc/pvr/guilib/guiinfo/PVRGUITimesInfo.cpp
@@ -20,6 +20,7 @@
#include "settings/SettingsComponent.h"
#include "utils/StringUtils.h"
+#include <chrono>
#include <cmath>
#include <ctime>
#include <memory>
@@ -82,7 +83,8 @@ void CPVRGUITimesInfo::UpdatePlayingTag()
else if (m_iTimeshiftEndTime > m_iTimeshiftStartTime)
{
m_playingEpgTag.reset();
- m_iDuration = m_iTimeshiftEndTime - m_iTimeshiftStartTime;
+ std::chrono::duration<unsigned int> duration{m_iTimeshiftEndTime - m_iTimeshiftStartTime};
+ m_iDuration = duration.count();
}
else
{
@@ -230,7 +232,9 @@ void CPVRGUITimesInfo::UpdateTimeshiftProgressData()
//////////////////////////////////////////////////////////////////////////////////////
// duration
//////////////////////////////////////////////////////////////////////////////////////
- m_iTimeshiftProgressDuration = m_iTimeshiftProgressEndTime - m_iTimeshiftProgressStartTime;
+ std::chrono::duration<unsigned int> duration{m_iTimeshiftProgressEndTime -
+ m_iTimeshiftProgressStartTime};
+ m_iTimeshiftProgressDuration = duration.count();
}
void CPVRGUITimesInfo::Update()
@@ -350,25 +354,28 @@ int CPVRGUITimesInfo::GetRemainingTime(const std::shared_ptr<const CPVREpgInfoTa
if (epgTag && m_playingEpgTag && *epgTag != *m_playingEpgTag)
return epgTag->GetDuration() - epgTag->Progress();
else
- return m_iDuration - GetElapsedTime();
+ return static_cast<int>(m_iDuration - GetElapsedTime());
}
int CPVRGUITimesInfo::GetTimeshiftProgress() const
{
std::unique_lock<CCriticalSection> lock(m_critSection);
- return std::lrintf(static_cast<float>(m_iTimeshiftPlayTime - m_iTimeshiftStartTime) / (m_iTimeshiftEndTime - m_iTimeshiftStartTime) * 100);
+ const std::chrono::duration<float> current{m_iTimeshiftPlayTime - m_iTimeshiftStartTime};
+ const std::chrono::duration<float> total{m_iTimeshiftEndTime - m_iTimeshiftStartTime};
+ return static_cast<int>(std::lrintf(current.count() / total.count() * 100));
}
int CPVRGUITimesInfo::GetTimeshiftProgressDuration() const
{
std::unique_lock<CCriticalSection> lock(m_critSection);
- return m_iTimeshiftProgressDuration;
+ return static_cast<int>(m_iTimeshiftProgressDuration);
}
int CPVRGUITimesInfo::GetTimeshiftProgressPlayPosition() const
{
std::unique_lock<CCriticalSection> lock(m_critSection);
- return std::lrintf(static_cast<float>(m_iTimeshiftPlayTime - m_iTimeshiftProgressStartTime) / m_iTimeshiftProgressDuration * 100);
+ const std::chrono::duration<float> duration{m_iTimeshiftPlayTime - m_iTimeshiftProgressStartTime};
+ return static_cast<int>(std::lrintf(duration.count() / m_iTimeshiftProgressDuration * 100));
}
int CPVRGUITimesInfo::GetTimeshiftProgressEpgStart() const
@@ -378,7 +385,8 @@ int CPVRGUITimesInfo::GetTimeshiftProgressEpgStart() const
{
time_t epgStart = 0;
m_playingEpgTag->StartAsUTC().GetAsTime(epgStart);
- return std::lrintf(static_cast<float>(epgStart - m_iTimeshiftProgressStartTime) / m_iTimeshiftProgressDuration * 100);
+ const std::chrono::duration<float> duration{epgStart - m_iTimeshiftProgressStartTime};
+ return static_cast<int>(std::lrintf(duration.count() / m_iTimeshiftProgressDuration * 100));
}
return 0;
}
@@ -390,7 +398,8 @@ int CPVRGUITimesInfo::GetTimeshiftProgressEpgEnd() const
{
time_t epgEnd = 0;
m_playingEpgTag->EndAsUTC().GetAsTime(epgEnd);
- return std::lrintf(static_cast<float>(epgEnd - m_iTimeshiftProgressStartTime) / m_iTimeshiftProgressDuration * 100);
+ const std::chrono::duration<float> duration{epgEnd - m_iTimeshiftProgressStartTime};
+ return static_cast<int>(std::lrintf(duration.count() / m_iTimeshiftProgressDuration * 100));
}
return 0;
}
@@ -398,13 +407,16 @@ int CPVRGUITimesInfo::GetTimeshiftProgressEpgEnd() const
int CPVRGUITimesInfo::GetTimeshiftProgressBufferStart() const
{
std::unique_lock<CCriticalSection> lock(m_critSection);
- return std::lrintf(static_cast<float>(m_iTimeshiftStartTime - m_iTimeshiftProgressStartTime) / m_iTimeshiftProgressDuration * 100);
+ const std::chrono::duration<float> duration{m_iTimeshiftStartTime -
+ m_iTimeshiftProgressStartTime};
+ return static_cast<int>(std::lrintf(duration.count() / m_iTimeshiftProgressDuration * 100));
}
int CPVRGUITimesInfo::GetTimeshiftProgressBufferEnd() const
{
std::unique_lock<CCriticalSection> lock(m_critSection);
- return std::lrintf(static_cast<float>(m_iTimeshiftEndTime - m_iTimeshiftProgressStartTime) / m_iTimeshiftProgressDuration * 100);
+ const std::chrono::duration<float> duration{m_iTimeshiftEndTime - m_iTimeshiftProgressStartTime};
+ return static_cast<int>(std::lrintf(duration.count() / m_iTimeshiftProgressDuration * 100));
}
int CPVRGUITimesInfo::GetEpgEventDuration(const std::shared_ptr<const CPVREpgInfoTag>& epgTag) const
@@ -413,16 +425,16 @@ int CPVRGUITimesInfo::GetEpgEventDuration(const std::shared_ptr<const CPVREpgInf
if (epgTag && m_playingEpgTag && *epgTag != *m_playingEpgTag)
return epgTag->GetDuration();
else
- return m_iDuration;
+ return static_cast<int>(m_iDuration);
}
int CPVRGUITimesInfo::GetEpgEventProgress(const std::shared_ptr<const CPVREpgInfoTag>& epgTag) const
{
std::unique_lock<CCriticalSection> lock(m_critSection);
if (epgTag && m_playingEpgTag && *epgTag != *m_playingEpgTag)
- return std::lrintf(epgTag->ProgressPercentage());
+ return static_cast<int>(std::lrintf(epgTag->ProgressPercentage()));
else
- return std::lrintf(static_cast<float>(GetElapsedTime()) / m_iDuration * 100);
+ return static_cast<int>(std::lrintf(static_cast<float>(GetElapsedTime()) / m_iDuration * 100));
}
bool CPVRGUITimesInfo::IsTimeshifting() const
diff --git a/xbmc/pvr/guilib/guiinfo/PVRGUITimesInfo.h b/xbmc/pvr/guilib/guiinfo/PVRGUITimesInfo.h
index 73b194f78e..7231b5880d 100644
--- a/xbmc/pvr/guilib/guiinfo/PVRGUITimesInfo.h
+++ b/xbmc/pvr/guilib/guiinfo/PVRGUITimesInfo.h
@@ -81,7 +81,7 @@ namespace PVR
time_t m_iTimeshiftStartTime;
time_t m_iTimeshiftEndTime;
time_t m_iTimeshiftPlayTime;
- unsigned int m_iTimeshiftOffset;
+ int64_t m_iTimeshiftOffset;
time_t m_iTimeshiftProgressStartTime;
time_t m_iTimeshiftProgressEndTime;
diff --git a/xbmc/pvr/providers/CMakeLists.txt b/xbmc/pvr/providers/CMakeLists.txt
index f01a35a9e3..facab95268 100644
--- a/xbmc/pvr/providers/CMakeLists.txt
+++ b/xbmc/pvr/providers/CMakeLists.txt
@@ -1,7 +1,9 @@
set(SOURCES PVRProvider.cpp
- PVRProviders.cpp)
+ PVRProviders.cpp
+ PVRProvidersPath.cpp)
set(HEADERS PVRProvider.h
- PVRProviders.h)
+ PVRProviders.h
+ PVRProvidersPath.h)
core_add_library(pvr_providers)
diff --git a/xbmc/pvr/providers/PVRProvider.cpp b/xbmc/pvr/providers/PVRProvider.cpp
index 99671aa889..428c890f6c 100644
--- a/xbmc/pvr/providers/PVRProvider.cpp
+++ b/xbmc/pvr/providers/PVRProvider.cpp
@@ -391,3 +391,11 @@ std::string CPVRProvider::GetClientThumbPath() const
std::unique_lock<CCriticalSection> lock(m_critSection);
return m_thumbPath.GetClientImage();
}
+
+void CPVRProvider::ToSortable(SortItem& sortable, Field field) const
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ if (field == FieldProvider)
+ sortable[FieldProvider] = StringUtils::Format(
+ "{} {} {} {}", m_iClientId, m_type == PVR_PROVIDER_TYPE_ADDON ? 0 : 1, m_type, m_strName);
+}
diff --git a/xbmc/pvr/providers/PVRProvider.h b/xbmc/pvr/providers/PVRProvider.h
index 1e8d835061..22f56b0178 100644
--- a/xbmc/pvr/providers/PVRProvider.h
+++ b/xbmc/pvr/providers/PVRProvider.h
@@ -12,6 +12,7 @@
#include "pvr/PVRCachedImage.h"
#include "threads/CriticalSection.h"
#include "utils/ISerializable.h"
+#include "utils/ISortable.h"
#include <memory>
#include <string>
@@ -29,7 +30,7 @@ enum class ProviderUpdateMode
static constexpr int PVR_PROVIDER_ADDON_UID = -1;
static constexpr int PVR_PROVIDER_INVALID_DB_ID = -1;
-class CPVRProvider final : public ISerializable
+class CPVRProvider final : public ISerializable, public ISortable
{
public:
static const std::string IMAGE_OWNER_PATTERN;
@@ -46,6 +47,9 @@ public:
void Serialize(CVariant& value) const override;
+ // ISortable implementation
+ void ToSortable(SortItem& sortable, Field field) const override;
+
/*!
* @brief The database id of this provider
*
diff --git a/xbmc/pvr/providers/PVRProviders.cpp b/xbmc/pvr/providers/PVRProviders.cpp
index 547026a13d..7cc8eb576d 100644
--- a/xbmc/pvr/providers/PVRProviders.cpp
+++ b/xbmc/pvr/providers/PVRProviders.cpp
@@ -82,10 +82,32 @@ std::vector<std::shared_ptr<CPVRProvider>> CPVRProvidersContainer::GetProvidersL
return m_providers;
}
-std::size_t CPVRProvidersContainer::GetNumProviders() const
+std::vector<std::shared_ptr<CPVRProvider>> CPVRProviders::GetProviders() const
{
+ std::vector<std::shared_ptr<CPVRProvider>> providers;
std::unique_lock<CCriticalSection> lock(m_critSection);
- return m_providers.size();
+
+ //! @todo optimize; get rid of iteration.
+ providers.reserve(m_providers.size());
+ for (const auto& provider : m_providers)
+ {
+ if (provider->IsClientProvider())
+ {
+ // Ignore non installed / disabled client providers
+ const std::shared_ptr<const CPVRClient> client{
+ CServiceBroker::GetPVRManager().GetClient(provider->GetClientId())};
+ if (!client)
+ continue;
+ }
+ providers.emplace_back(provider);
+ }
+ return providers;
+}
+
+std::size_t CPVRProviders::GetNumProviders() const
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ return GetProviders().size();
}
bool CPVRProviders::Update(const std::vector<std::shared_ptr<CPVRClient>>& clients)
@@ -137,7 +159,7 @@ bool CPVRProviders::UpdateFromClients(const std::vector<std::shared_ptr<CPVRClie
for (const auto& clientInfo : clientProviderInfos)
{
auto addonProvider = std::make_shared<CPVRProvider>(
- clientInfo["clientid"].asInteger32(), clientInfo["name"].asString(),
+ clientInfo["clientid"].asInteger32(), clientInfo["fullname"].asString(),
clientInfo["icon"].asString(), clientInfo["thumb"].asString());
newAddonProviderList.CheckAndAddEntry(addonProvider, ProviderUpdateMode::BY_CLIENT);
diff --git a/xbmc/pvr/providers/PVRProviders.h b/xbmc/pvr/providers/PVRProviders.h
index 8196d3b6d2..c342e9738e 100644
--- a/xbmc/pvr/providers/PVRProviders.h
+++ b/xbmc/pvr/providers/PVRProviders.h
@@ -44,12 +44,6 @@ public:
*/
std::vector<std::shared_ptr<CPVRProvider>> GetProvidersList() const;
- /*!
- * Get the number of providers in this container
- * @return The total number of providers
- */
- std::size_t GetNumProviders() const;
-
protected:
void InsertEntry(const std::shared_ptr<CPVRProvider>& newProvider, ProviderUpdateMode updateMode);
@@ -64,6 +58,18 @@ public:
CPVRProviders() = default;
~CPVRProviders() = default;
+ /*!
+ * Get all enabled providers
+ * @return The list of all enabled providers
+ */
+ std::vector<std::shared_ptr<CPVRProvider>> GetProviders() const;
+
+ /*!
+ * Get the number of enabled providers
+ * @return The total number of enabled providers
+ */
+ std::size_t GetNumProviders() const;
+
/**
* @brief Update all providers from PVR database and from given clients.
* @param clients The PVR clients data should be loaded for. Leave empty for all clients.
diff --git a/xbmc/pvr/providers/PVRProvidersPath.cpp b/xbmc/pvr/providers/PVRProvidersPath.cpp
new file mode 100644
index 0000000000..4abe0331f0
--- /dev/null
+++ b/xbmc/pvr/providers/PVRProvidersPath.cpp
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2024 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "PVRProvidersPath.h"
+
+#include "utils/StringUtils.h"
+#include "utils/URIUtils.h"
+
+#include <string>
+#include <vector>
+
+using namespace PVR;
+
+const std::string CPVRProvidersPath::PATH_TV_PROVIDERS = "pvr://providers/tv/";
+const std::string CPVRProvidersPath::PATH_RADIO_PROVIDERS = "pvr://providers/radio/";
+const std::string CPVRProvidersPath::CHANNELS = "channels";
+const std::string CPVRProvidersPath::RECORDINGS = "recordings";
+
+//pvr://providers/(tv|radio)/<provider-uid@client-id>/(channels|recordings)/
+
+CPVRProvidersPath::CPVRProvidersPath(const std::string& path)
+{
+ Init(path);
+}
+
+CPVRProvidersPath::CPVRProvidersPath(CPVRProvidersPath::Kind kind,
+ int clientId,
+ int providerUid,
+ const std::string& lastSegment /* = "" */)
+{
+ m_kind = kind;
+ m_isValid = ((m_kind == Kind::RADIO) || (m_kind == Kind::TV));
+ if (m_isValid)
+ {
+ if (lastSegment.empty())
+ {
+ m_path = StringUtils::Format("pvr://providers/{}/{}@{}/",
+ (m_kind == Kind::RADIO) ? "radio" : "tv", providerUid, clientId);
+ m_isProvider = true;
+ m_isChannels = false;
+ m_isRecordings = false;
+ }
+ else
+ {
+ m_path = StringUtils::Format("pvr://providers/{}/{}@{}/{}/",
+ (m_kind == Kind::RADIO) ? "radio" : "tv", providerUid, clientId,
+ lastSegment);
+ m_isProvider = false;
+ m_isChannels = (lastSegment == CHANNELS);
+ m_isRecordings = (lastSegment == RECORDINGS);
+ }
+
+ m_clientId = clientId;
+ m_providerUid = providerUid;
+ m_isRoot = false;
+ }
+}
+
+bool CPVRProvidersPath::Init(const std::string& path)
+{
+ std::string varPath(path);
+ URIUtils::RemoveSlashAtEnd(varPath);
+
+ m_path = varPath;
+ const std::vector<std::string> segments{URIUtils::SplitPath(m_path)};
+
+ m_isValid =
+ ((segments.size() >= 3) && (segments.size() <= 5) && (segments.at(1) == "providers") &&
+ ((segments.at(2) == "radio") || (segments.at(2) == "tv")));
+ m_isRoot = (m_isValid && (segments.size() == 3));
+ if (m_isValid)
+ m_kind = (segments.at(2) == "radio") ? Kind::RADIO : Kind::TV;
+
+ if (!m_isValid || m_isRoot)
+ {
+ m_providerUid = PVR_PROVIDER_INVALID_UID;
+ m_clientId = PVR_CLIENT_INVALID_UID;
+ m_isProvider = false;
+ m_isChannels = false;
+ m_isRecordings = false;
+ }
+ else
+ {
+ std::vector<std::string> tokens{StringUtils::Split(segments.at(3), "@")};
+ if (tokens.size() == 2 && !tokens[0].empty() && !tokens[1].empty())
+ {
+ m_providerUid = std::stoi(tokens[0]);
+ m_clientId = std::stoi(tokens[1]);
+ m_isProvider = (segments.size() == 4);
+
+ if (segments.size() == 5)
+ {
+ m_isChannels = (segments.at(4) == CHANNELS);
+ m_isRecordings = !m_isChannels && (segments.at(4) == RECORDINGS);
+ m_isValid = (m_isChannels || m_isRecordings);
+ }
+ }
+ else
+ {
+ m_isValid = false;
+ }
+ }
+
+ return m_isValid;
+}
diff --git a/xbmc/pvr/providers/PVRProvidersPath.h b/xbmc/pvr/providers/PVRProvidersPath.h
new file mode 100644
index 0000000000..8ff7de067d
--- /dev/null
+++ b/xbmc/pvr/providers/PVRProvidersPath.h
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2024 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_providers.h" // PVR_PROVIDER_INVALID_UID
+#include "pvr/PVRConstants.h" // PVR_CLIENT_INVALID_UID
+
+#include <string>
+
+namespace PVR
+{
+class CPVRProvidersPath
+{
+public:
+ static const std::string PATH_TV_PROVIDERS;
+ static const std::string PATH_RADIO_PROVIDERS;
+ static const std::string CHANNELS;
+ static const std::string RECORDINGS;
+
+ enum class Kind
+ {
+ UNKNOWN,
+ RADIO,
+ TV
+ };
+
+ explicit CPVRProvidersPath(const std::string& path);
+ CPVRProvidersPath(Kind kind, int clientId, int providerUid, const std::string& lastSegment = "");
+
+ bool IsValid() const { return m_isValid; }
+
+ operator std::string() const { return m_path; }
+
+ const std::string& GetPath() const { return m_path; }
+ bool IsProvidersRoot() const { return m_isRoot; }
+ bool IsProvider() const { return m_isProvider; }
+ bool IsChannels() const { return m_isChannels; }
+ bool IsRecordings() const { return m_isRecordings; }
+ bool IsRadio() const { return m_kind == Kind::RADIO; }
+ Kind GetKind() const { return m_kind; }
+ int GetProviderUid() const { return m_providerUid; }
+ int GetClientId() const { return m_clientId; }
+
+private:
+ bool Init(const std::string& path);
+
+ std::string m_path;
+ bool m_isValid{false};
+ bool m_isRoot{false};
+ bool m_isProvider{false};
+ bool m_isChannels{false};
+ bool m_isRecordings{false};
+ Kind m_kind{Kind::UNKNOWN};
+ int m_providerUid{PVR_PROVIDER_INVALID_UID};
+ int m_clientId{PVR_CLIENT_INVALID_UID};
+};
+} // namespace PVR
diff --git a/xbmc/pvr/recordings/PVRRecording.cpp b/xbmc/pvr/recordings/PVRRecording.cpp
index 2f1b09ddb7..9c5f9d4751 100644
--- a/xbmc/pvr/recordings/PVRRecording.cpp
+++ b/xbmc/pvr/recordings/PVRRecording.cpp
@@ -9,7 +9,6 @@
#include "PVRRecording.h"
#include "ServiceBroker.h"
-#include "addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_recordings.h"
#include "cores/EdlEdit.h"
#include "guilib/LocalizeStrings.h"
#include "pvr/PVRManager.h"
@@ -91,6 +90,7 @@ CPVRRecording::CPVRRecording(const PVR_RECORDING& recording, unsigned int iClien
m_strShowTitle = recording.strEpisodeName;
m_iSeason = recording.iSeriesNumber;
m_iEpisode = recording.iEpisodeNumber;
+ m_episodePartNumber = recording.iEpisodePartNumber;
if (recording.iYear > 0)
SetYear(recording.iYear);
m_iClientId = iClientId;
@@ -118,7 +118,12 @@ CPVRRecording::CPVRRecording(const PVR_RECORDING& recording, unsigned int iClien
m_sizeInBytes = recording.sizeInBytes;
if (recording.strProviderName)
m_strProviderName = recording.strProviderName;
- m_iClientProviderUniqueId = recording.iClientProviderUid;
+ m_iClientProviderUid = recording.iClientProviderUid;
+
+ // Workaround for C++ PVR Add-on API wrapper not initializing this value correctly until API 9.0.1
+ //! @todo Remove with next incompatible API bump.
+ if (m_iClientProviderUid == 0)
+ m_iClientProviderUid = PVR_PROVIDER_INVALID_UID;
SetGenre(recording.iGenreType, recording.iGenreSubType,
recording.strGenreDescription ? recording.strGenreDescription : "");
@@ -127,6 +132,14 @@ CPVRRecording::CPVRRecording(const PVR_RECORDING& recording, unsigned int iClien
CVideoInfoTag::SetResumePoint(recording.iLastPlayedPosition, recording.iDuration, "");
SetDuration(recording.iDuration);
+ m_parentalRating = recording.iParentalRating;
+ if (recording.strParentalRatingCode)
+ m_parentalRatingCode = recording.strParentalRatingCode;
+ if (recording.strParentalRatingIcon)
+ m_parentalRatingIcon = recording.strParentalRatingIcon;
+ if (recording.strParentalRatingSource)
+ m_parentalRatingSource = recording.strParentalRatingSource;
+
// As the channel a recording was done on (probably long time ago) might no longer be
// available today prefer addon-supplied channel type (tv/radio) over channel attribute.
if (recording.channelType != PVR_RECORDING_CHANNEL_TYPE_UNKNOWN)
@@ -180,7 +193,12 @@ bool CPVRRecording::operator==(const CPVRRecording& right) const
m_iGenreSubType == right.m_iGenreSubType && m_firstAired == right.m_firstAired &&
m_iFlags == right.m_iFlags && m_sizeInBytes == right.m_sizeInBytes &&
m_strProviderName == right.m_strProviderName &&
- m_iClientProviderUniqueId == right.m_iClientProviderUniqueId);
+ m_iClientProviderUid == right.m_iClientProviderUid &&
+ m_parentalRating == right.m_parentalRating &&
+ m_parentalRatingCode == right.m_parentalRatingCode &&
+ m_parentalRatingIcon == right.m_parentalRatingIcon &&
+ m_parentalRatingSource == right.m_parentalRatingSource &&
+ m_episodePartNumber == right.m_episodePartNumber);
}
bool CPVRRecording::operator!=(const CPVRRecording& right) const
@@ -204,6 +222,11 @@ void CPVRRecording::Serialize(CVariant& value) const
value["channeluid"] = m_iChannelUid;
value["radio"] = m_bRadio;
value["genre"] = m_genre;
+ value["parentalrating"] = m_parentalRating;
+ value["parentalratingcode"] = m_parentalRatingCode;
+ value["parentalratingicon"] = m_parentalRatingIcon;
+ value["parentalratingsource"] = m_parentalRatingSource;
+ value["episodepart"] = m_episodePartNumber;
if (!value.isMember("art"))
value["art"] = CVariant(CVariant::VariantTypeObject);
@@ -221,7 +244,7 @@ void CPVRRecording::ToSortable(SortItem& sortable, Field field) const
if (field == FieldSize)
sortable[FieldSize] = m_sizeInBytes;
else if (field == FieldProvider)
- sortable[FieldProvider] = StringUtils::Format("{} {}", m_iClientId, m_iClientProviderUniqueId);
+ sortable[FieldProvider] = StringUtils::Format("{} {}", m_iClientId, m_iClientProviderUid);
else
CVideoInfoTag::ToSortable(sortable, field);
}
@@ -229,7 +252,7 @@ void CPVRRecording::ToSortable(SortItem& sortable, Field field) const
void CPVRRecording::Reset()
{
m_strRecordingId.clear();
- m_iClientId = -1;
+ m_iClientId = PVR_CLIENT_INVALID_UID;
m_strChannelName.clear();
m_strDirectory.clear();
m_iPriority = -1;
@@ -240,8 +263,9 @@ void CPVRRecording::Reset()
m_bIsDeleted = false;
m_bInProgress = true;
m_iEpgEventId = EPG_TAG_INVALID_UID;
- m_iSeason = -1;
- m_iEpisode = -1;
+ m_iSeason = PVR_RECORDING_INVALID_SERIES_EPISODE;
+ m_iEpisode = PVR_RECORDING_INVALID_SERIES_EPISODE;
+ m_episodePartNumber = PVR_RECORDING_INVALID_SERIES_EPISODE;
m_iChannelUid = PVR_CHANNEL_INVALID_UID;
m_bRadio = false;
m_iFlags = PVR_RECORDING_FLAG_UNDEFINED;
@@ -250,9 +274,15 @@ void CPVRRecording::Reset()
m_sizeInBytes = 0;
}
m_strProviderName.clear();
- m_iClientProviderUniqueId = PVR_PROVIDER_INVALID_UID;
+ m_iClientProviderUid = PVR_PROVIDER_INVALID_UID;
m_recordingTime.Reset();
+
+ m_parentalRating = 0;
+ m_parentalRatingCode.clear();
+ m_parentalRatingIcon.clear();
+ m_parentalRatingSource.clear();
+
CVideoInfoTag::Reset();
}
@@ -305,8 +335,8 @@ bool CPVRRecording::SetResumePoint(const CBookmark& resumePoint)
const std::shared_ptr<CPVRClient> client = CServiceBroker::GetPVRManager().GetClient(m_iClientId);
if (client && client->GetClientCapabilities().SupportsRecordingsLastPlayedPosition())
{
- if (client->SetRecordingLastPlayedPosition(*this, lrint(resumePoint.timeInSeconds)) !=
- PVR_ERROR_NO_ERROR)
+ if (client->SetRecordingLastPlayedPosition(
+ *this, static_cast<int>(std::lrint(resumePoint.timeInSeconds))) != PVR_ERROR_NO_ERROR)
return false;
}
@@ -320,7 +350,8 @@ bool CPVRRecording::SetResumePoint(double timeInSeconds,
const std::shared_ptr<CPVRClient> client = CServiceBroker::GetPVRManager().GetClient(m_iClientId);
if (client && client->GetClientCapabilities().SupportsRecordingsLastPlayedPosition())
{
- if (client->SetRecordingLastPlayedPosition(*this, lrint(timeInSeconds)) != PVR_ERROR_NO_ERROR)
+ if (client->SetRecordingLastPlayedPosition(
+ *this, static_cast<int>(std::lrint(timeInSeconds))) != PVR_ERROR_NO_ERROR)
return false;
}
@@ -416,6 +447,7 @@ void CPVRRecording::Update(const CPVRRecording& tag, const CPVRClient& client)
m_strShowTitle = tag.m_strShowTitle;
m_iSeason = tag.m_iSeason;
m_iEpisode = tag.m_iEpisode;
+ m_episodePartNumber = tag.m_episodePartNumber;
SetPremiered(tag.GetPremiered());
m_recordingTime = tag.m_recordingTime;
m_iPriority = tag.m_iPriority;
@@ -425,6 +457,10 @@ void CPVRRecording::Update(const CPVRRecording& tag, const CPVRClient& client)
m_strPlotOutline = tag.m_strPlotOutline;
m_strChannelName = tag.m_strChannelName;
m_genre = tag.m_genre;
+ m_parentalRating = tag.m_parentalRating;
+ m_parentalRatingCode = tag.m_parentalRatingCode;
+ m_parentalRatingIcon = tag.m_parentalRatingIcon;
+ m_parentalRatingSource = tag.m_parentalRatingSource;
m_iconPath = tag.m_iconPath;
m_thumbnailPath = tag.m_thumbnailPath;
m_fanartPath = tag.m_fanartPath;
@@ -438,7 +474,7 @@ void CPVRRecording::Update(const CPVRRecording& tag, const CPVRClient& client)
std::unique_lock<CCriticalSection> lock(m_critSection);
m_sizeInBytes = tag.m_sizeInBytes;
m_strProviderName = tag.m_strProviderName;
- m_iClientProviderUniqueId = tag.m_iClientProviderUniqueId;
+ m_iClientProviderUid = tag.m_iClientProviderUid;
}
if (client.GetClientCapabilities().SupportsRecordingsPlayCount())
@@ -662,10 +698,10 @@ int64_t CPVRRecording::GetSizeInBytes() const
return m_sizeInBytes;
}
-int CPVRRecording::ClientProviderUniqueId() const
+int CPVRRecording::ClientProviderUid() const
{
std::unique_lock<CCriticalSection> lock(m_critSection);
- return m_iClientProviderUniqueId;
+ return m_iClientProviderUid;
}
std::string CPVRRecording::ProviderName() const
@@ -683,16 +719,46 @@ std::shared_ptr<CPVRProvider> CPVRRecording::GetDefaultProvider() const
bool CPVRRecording::HasClientProvider() const
{
std::unique_lock<CCriticalSection> lock(m_critSection);
- return m_iClientProviderUniqueId != PVR_PROVIDER_INVALID_UID;
+ return m_iClientProviderUid != PVR_PROVIDER_INVALID_UID;
}
std::shared_ptr<CPVRProvider> CPVRRecording::GetProvider() const
{
- auto provider = CServiceBroker::GetPVRManager().Providers()->GetByClient(
- m_iClientId, m_iClientProviderUniqueId);
+ auto provider =
+ CServiceBroker::GetPVRManager().Providers()->GetByClient(m_iClientId, m_iClientProviderUid);
if (!provider)
provider = GetDefaultProvider();
return provider;
}
+
+unsigned int CPVRRecording::GetParentalRating() const
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ return m_parentalRating;
+}
+
+const std::string& CPVRRecording::GetParentalRatingCode() const
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ return m_parentalRatingCode;
+}
+
+const std::string& CPVRRecording::GetParentalRatingIcon() const
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ return m_parentalRatingIcon;
+}
+
+const std::string& CPVRRecording::GetParentalRatingSource() const
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ return m_parentalRatingSource;
+}
+
+int CPVRRecording::EpisodePart() const
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ return m_episodePartNumber;
+}
diff --git a/xbmc/pvr/recordings/PVRRecording.h b/xbmc/pvr/recordings/PVRRecording.h
index a9d43374ae..4e70fc6ffe 100644
--- a/xbmc/pvr/recordings/PVRRecording.h
+++ b/xbmc/pvr/recordings/PVRRecording.h
@@ -10,6 +10,7 @@
#include "XBDateTime.h"
#include "addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_providers.h"
+#include "addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_recordings.h"
#include "pvr/PVRCachedImage.h"
#include "threads/CriticalSection.h"
#include "threads/SystemClock.h"
@@ -470,7 +471,7 @@ public:
* @brief Get the uid of the provider on the client which this recording is from
* @return the client uid of the provider or PVR_PROVIDER_INVALID_UID
*/
- int ClientProviderUniqueId() const;
+ int ClientProviderUid() const;
/*!
* @brief Get the client provider name for this recording
@@ -499,6 +500,36 @@ public:
*/
std::shared_ptr<CPVRProvider> GetProvider() const;
+ /*!
+ * @brief Get the parental rating of this recording.
+ * @return The parental rating.
+ */
+ unsigned int GetParentalRating() const;
+
+ /*!
+ * @brief Get the parental rating code of this recording.
+ * @return The parental rating code.
+ */
+ const std::string& GetParentalRatingCode() const;
+
+ /*!
+ * @brief Get the parental rating icon path of this recording.
+ * @return The parental rating icon path.
+ */
+ const std::string& GetParentalRatingIcon() const;
+
+ /*!
+ * @brief Get the parental rating source of this recording.
+ * @return The parental rating source.
+ */
+ const std::string& GetParentalRatingSource() const;
+
+ /*!
+ * @brief Get the episode part number of this recording.
+ * @return The episode part number.
+ */
+ int EpisodePart() const;
+
private:
CPVRRecording(const CPVRRecording& tag) = delete;
CPVRRecording& operator=(const CPVRRecording& other) = delete;
@@ -531,8 +562,13 @@ private:
int64_t m_sizeInBytes = 0; /*!< the size of the recording in bytes */
bool m_bDirty = false;
std::string m_strProviderName; /*!< name of the provider this recording is from */
- int m_iClientProviderUniqueId =
+ int m_iClientProviderUid =
PVR_PROVIDER_INVALID_UID; /*!< provider uid associated with this recording on the client */
+ unsigned int m_parentalRating{0}; /*!< parental rating */
+ std::string m_parentalRatingCode; /*!< Parental rating code */
+ std::string m_parentalRatingIcon; /*!< parental rating icon path */
+ std::string m_parentalRatingSource; /*!< parental rating source */
+ int m_episodePartNumber{PVR_RECORDING_INVALID_SERIES_EPISODE}; /*!< episode part number */
mutable CCriticalSection m_critSection;
};
diff --git a/xbmc/pvr/recordings/PVRRecordings.cpp b/xbmc/pvr/recordings/PVRRecordings.cpp
index b6049ee6fb..363196b1d0 100644
--- a/xbmc/pvr/recordings/PVRRecordings.cpp
+++ b/xbmc/pvr/recordings/PVRRecordings.cpp
@@ -187,6 +187,37 @@ std::shared_ptr<CPVRRecording> CPVRRecordings::GetById(int iClientId,
return retVal;
}
+namespace
+{
+bool MatchProvider(const std::shared_ptr<CPVRRecording>& recording,
+ bool isRadio,
+ int clientId,
+ int providerId)
+{
+ return recording->IsRadio() == isRadio && recording->ClientID() == clientId &&
+ (providerId == PVR_PROVIDER_INVALID_UID || recording->ClientProviderUid() == providerId);
+}
+} // unnamed namespace
+
+bool CPVRRecordings::HasRecordingForProvider(bool isRadio, int clientId, int providerId) const
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ return std::any_of(m_recordings.cbegin(), m_recordings.cend(),
+ [isRadio, clientId, providerId](const auto& entry)
+ { return MatchProvider(entry.second, isRadio, clientId, providerId); });
+}
+
+unsigned int CPVRRecordings::GetRecordingCountByProvider(bool isRadio,
+ int clientId,
+ int providerId) const
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ auto recs = std::count_if(m_recordings.cbegin(), m_recordings.cend(),
+ [isRadio, clientId, providerId](const auto& entry)
+ { return MatchProvider(entry.second, isRadio, clientId, providerId); });
+ return static_cast<unsigned int>(recs);
+}
+
void CPVRRecordings::UpdateFromClient(const std::shared_ptr<CPVRRecording>& tag,
const CPVRClient& client)
{
diff --git a/xbmc/pvr/recordings/PVRRecordings.h b/xbmc/pvr/recordings/PVRRecordings.h
index 7184b6c3e8..4857da3618 100644
--- a/xbmc/pvr/recordings/PVRRecordings.h
+++ b/xbmc/pvr/recordings/PVRRecordings.h
@@ -93,6 +93,24 @@ public:
std::shared_ptr<CPVRRecording> GetById(unsigned int iId) const;
/*!
+ * @brief Check whether at least one recording is offered by the given provider.
+ * @param isRadio Check radio or TV recordings.
+ * @param clientId The clientId to check.
+ * @param providerId The providerId to check.
+ * @return True, if at least one matching recording is offered by the provider, false otherwise.
+ */
+ bool HasRecordingForProvider(bool isRadio, int clientId, int providerId) const;
+
+ /*!
+ * @brief Get the total count of recordings offered by the given provider.
+ * @param isRadio Check radio or TV recordings.
+ * @param clientId The clientId of the provider.
+ * @param providerId The providerId.
+ * @return The total count of matching recordings.
+ */
+ unsigned int GetRecordingCountByProvider(bool isRadio, int clientId, int providerId) const;
+
+ /*!
* @brief Get the recording for the given epg tag, if any.
* @param epgTag The epg tag.
* @return The requested recording, or an empty recordingptr if none was found.
diff --git a/xbmc/pvr/settings/CMakeLists.txt b/xbmc/pvr/settings/CMakeLists.txt
index 6b32503a69..d171dc8b61 100644
--- a/xbmc/pvr/settings/CMakeLists.txt
+++ b/xbmc/pvr/settings/CMakeLists.txt
@@ -1,5 +1,18 @@
-set(SOURCES PVRSettings.cpp)
+set(SOURCES PVRCustomTimerSettings.cpp
+ PVRIntSettingDefinition.cpp
+ PVRIntSettingValues.cpp
+ PVRSettings.cpp
+ PVRStringSettingDefinition.cpp
+ PVRStringSettingValues.cpp
+ PVRTimerSettingDefinition.cpp)
-set(HEADERS PVRSettings.h)
+set(HEADERS IPVRSettingsContainer.h
+ PVRCustomTimerSettings.h
+ PVRIntSettingDefinition.h
+ PVRIntSettingValues.h
+ PVRSettings.h
+ PVRStringSettingDefinition.h
+ PVRStringSettingValues.h
+ PVRTimerSettingDefinition.h)
core_add_library(pvr_settings)
diff --git a/xbmc/pvr/settings/IPVRSettingsContainer.h b/xbmc/pvr/settings/IPVRSettingsContainer.h
new file mode 100644
index 0000000000..d5f76ce8f0
--- /dev/null
+++ b/xbmc/pvr/settings/IPVRSettingsContainer.h
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2024 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include <memory>
+
+class CSettingGroup;
+
+namespace PVR
+{
+class IPVRSettingsContainer
+{
+public:
+ virtual ~IPVRSettingsContainer() = default;
+
+ virtual void AddMultiIntSetting(const std::shared_ptr<CSettingGroup>& group,
+ const std::string& settingName,
+ int settingValue)
+ {
+ }
+ virtual void AddSingleIntSetting(const std::shared_ptr<CSettingGroup>& group,
+ const std::string& settingName,
+ int settingValue,
+ int minValue,
+ int step,
+ int maxValue)
+ {
+ }
+ virtual void AddMultiStringSetting(const std::shared_ptr<CSettingGroup>& group,
+ const std::string& settingName,
+ const std::string& settingValue)
+ {
+ }
+ virtual void AddSingleStringSetting(const std::shared_ptr<CSettingGroup>& group,
+ const std::string& settingName,
+ const std::string& settingValue,
+ bool allowEmptyValue)
+ {
+ }
+};
+} // namespace PVR
diff --git a/xbmc/pvr/settings/PVRCustomTimerSettings.cpp b/xbmc/pvr/settings/PVRCustomTimerSettings.cpp
new file mode 100644
index 0000000000..c62266896b
--- /dev/null
+++ b/xbmc/pvr/settings/PVRCustomTimerSettings.cpp
@@ -0,0 +1,256 @@
+/*
+ * Copyright (C) 2024 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "PVRCustomTimerSettings.h"
+
+#include "pvr/settings/IPVRSettingsContainer.h"
+#include "pvr/settings/PVRTimerSettingDefinition.h"
+#include "pvr/timers/PVRTimerType.h"
+#include "settings/lib/Setting.h"
+#include "utils/StringUtils.h"
+#include "utils/log.h"
+
+#include <algorithm>
+#include <map>
+#include <memory>
+#include <string>
+#include <vector>
+
+namespace PVR
+{
+static constexpr const char* SETTING_TMR_CUSTOM_INT{"customsetting.int"};
+static constexpr const char* SETTING_TMR_CUSTOM_STRING{"customsetting.string"};
+
+CPVRCustomTimerSettings::CPVRCustomTimerSettings(
+ const CPVRTimerType& timerType,
+ const CPVRTimerInfoTag::CustomPropsMap& customProps,
+ const std::map<int, std::shared_ptr<CPVRTimerType>>& typeEntries)
+ : m_customProps(customProps)
+{
+ unsigned int idx{0};
+ for (const auto& type : typeEntries)
+ {
+ const std::vector<std::shared_ptr<const CPVRTimerSettingDefinition>>& settingDefs{
+ type.second->GetCustomSettingDefinitions()};
+ for (const auto& settingDef : settingDefs)
+ {
+ std::string settingIdPrefix;
+ if (settingDef->IsIntDefinition())
+ {
+ settingIdPrefix = SETTING_TMR_CUSTOM_INT;
+ }
+ else if (settingDef->IsStringDefinition())
+ {
+ settingIdPrefix = SETTING_TMR_CUSTOM_STRING;
+ }
+ else
+ {
+ CLog::LogF(LOGERROR, "Unknown custom setting definition ignored!");
+ continue;
+ }
+
+ const std::string settingId{StringUtils::Format("{}-{}", settingIdPrefix, idx)};
+ m_customSettingDefs.insert({settingId, settingDef});
+ ++idx;
+ }
+ }
+
+ SetTimerType(timerType);
+}
+
+void CPVRCustomTimerSettings::SetTimerType(const CPVRTimerType& timerType)
+{
+ CPVRTimerInfoTag::CustomPropsMap newCustomProps;
+ for (const auto& entry : m_customSettingDefs)
+ {
+ // Complete custom props for given type.
+ const CPVRTimerSettingDefinition& def{*entry.second};
+ if (def.GetTimerTypeId() == timerType.GetTypeId() &&
+ def.GetClientId() == timerType.GetClientId())
+ {
+ const auto it{m_customProps.find(def.GetId())};
+ if (it == m_customProps.cend())
+ newCustomProps.insert({def.GetId(), {def.GetType(), def.GetDefaultValue()}});
+ else
+ newCustomProps.insert({def.GetId(), {(*it).second.type, (*it).second.value}});
+ }
+ }
+ m_customProps = newCustomProps;
+}
+
+void CPVRCustomTimerSettings::AddSettings(IPVRSettingsContainer& settingsContainer,
+ const std::shared_ptr<CSettingGroup>& group)
+{
+ for (const auto& settingDef : m_customSettingDefs)
+ {
+ const CPVRTimerSettingDefinition& def{*settingDef.second};
+ const auto it{m_customProps.find(def.GetId())};
+ if (it == m_customProps.cend())
+ continue;
+
+ const std::string settingName{settingDef.first};
+ if (IsCustomIntSetting(settingName))
+ {
+ const int intValue{(*it).second.value.asInteger32()};
+ const CPVRIntSettingDefinition& intDef{def.GetIntDefinition()};
+ if (intDef.GetValues().empty())
+ settingsContainer.AddSingleIntSetting(group, settingName, intValue, intDef.GetMinValue(),
+ intDef.GetStepValue(), intDef.GetMaxValue());
+ else
+ settingsContainer.AddMultiIntSetting(group, settingName, intValue);
+ }
+ else if (IsCustomStringSetting(settingName))
+ {
+ const std::string stringValue{(*it).second.value.asString()};
+ const CPVRStringSettingDefinition& stringDef{def.GetStringDefinition()};
+ if (stringDef.GetValues().empty())
+ settingsContainer.AddSingleStringSetting(group, settingName, stringValue,
+ stringDef.IsAllowEmptyValue());
+ else
+ settingsContainer.AddMultiStringSetting(group, settingName, stringValue);
+ }
+ }
+}
+
+bool CPVRCustomTimerSettings::IsCustomSetting(const std::string& settingId) const
+{
+ return IsCustomIntSetting(settingId) || IsCustomStringSetting(settingId);
+}
+
+bool CPVRCustomTimerSettings::IsCustomIntSetting(const std::string& settingId) const
+{
+ return settingId.starts_with(SETTING_TMR_CUSTOM_INT);
+}
+
+bool CPVRCustomTimerSettings::IsCustomStringSetting(const std::string& settingId) const
+{
+ return settingId.starts_with(SETTING_TMR_CUSTOM_STRING);
+}
+
+bool CPVRCustomTimerSettings::UpdateIntProperty(const std::shared_ptr<const CSetting>& setting)
+{
+ const auto def{GetSettingDefintion(setting->GetId())};
+ if (!def)
+ {
+ CLog::LogF(LOGERROR, "Custom int setting definition not found");
+ return false;
+ }
+
+ CVariant& prop{m_customProps[def->GetId()].value};
+ prop = std::static_pointer_cast<const CSettingInt>(setting)->GetValue();
+ return true;
+}
+
+bool CPVRCustomTimerSettings::UpdateStringProperty(const std::shared_ptr<const CSetting>& setting)
+{
+ const auto def{GetSettingDefintion(setting->GetId())};
+ if (!def)
+ {
+ CLog::LogF(LOGERROR, "Custom string setting definition not found");
+ return false;
+ }
+
+ CVariant& prop{m_customProps[def->GetId()].value};
+ prop = std::static_pointer_cast<const CSettingString>(setting)->GetValue();
+ return true;
+}
+
+std::string CPVRCustomTimerSettings::GetSettingsLabel(const std::string& settingId) const
+{
+ const auto def{GetSettingDefintion(settingId)};
+ if (!def)
+ return {};
+
+ return def->GetName();
+}
+
+bool CPVRCustomTimerSettings::IntSettingDefinitionsFiller(const std::string& settingId,
+ std::vector<IntegerSettingOption>& list,
+ int& current)
+{
+ const auto def{GetSettingDefintion(settingId)};
+ if (!def)
+ {
+ CLog::LogF(LOGERROR, "Custom setting definition not found");
+ return false;
+ }
+
+ const std::vector<SettingIntValue>& values{def->GetIntDefinition().GetValues()};
+ std::transform(values.cbegin(), values.cend(), std::back_inserter(list),
+ [](const auto& value) { return IntegerSettingOption(value.first, value.second); });
+
+ const auto it2{m_customProps.find(def->GetId())};
+ if (it2 != m_customProps.cend())
+ current = (*it2).second.value.asInteger32();
+ else
+ current = def->GetIntDefinition().GetDefaultValue();
+
+ return true;
+}
+
+bool CPVRCustomTimerSettings::StringSettingDefinitionsFiller(const std::string& settingId,
+ std::vector<StringSettingOption>& list,
+ std::string& current)
+{
+ const auto def{GetSettingDefintion(settingId)};
+ if (!def)
+ {
+ CLog::LogF(LOGERROR, "Custom setting definition not found");
+ return false;
+ }
+
+ const std::vector<SettingStringValue>& values{def->GetStringDefinition().GetValues()};
+ std::transform(values.cbegin(), values.cend(), std::back_inserter(list),
+ [](const auto& value) { return StringSettingOption(value.first, value.second); });
+
+ const auto it2{m_customProps.find(def->GetId())};
+ if (it2 != m_customProps.cend())
+ current = (*it2).second.value.asString();
+ else
+ current = def->GetStringDefinition().GetDefaultValue();
+
+ return true;
+}
+
+bool CPVRCustomTimerSettings::IsSettingReadonlyForTimerState(const std::string& settingId,
+ PVR_TIMER_STATE timerState) const
+{
+ const auto def{GetSettingDefintion(settingId)};
+ if (!def)
+ {
+ CLog::LogF(LOGERROR, "Custom setting definition not found");
+ return false;
+ }
+
+ return def->IsReadonlyForTimerState(timerState);
+}
+
+bool CPVRCustomTimerSettings::IsSettingSupportedForTimerType(const std::string& settingId,
+ const CPVRTimerType& timerType) const
+{
+ const auto def{GetSettingDefintion(settingId)};
+ if (!def)
+ {
+ CLog::LogF(LOGERROR, "Custom setting definition not found");
+ return false;
+ }
+
+ return timerType.GetClientId() == def->GetClientId() &&
+ timerType.GetTypeId() == def->GetTimerTypeId();
+}
+
+std::shared_ptr<const CPVRTimerSettingDefinition> CPVRCustomTimerSettings::GetSettingDefintion(
+ const std::string& settingId) const
+{
+ const auto it{m_customSettingDefs.find(settingId)};
+ if (it == m_customSettingDefs.cend())
+ return {};
+
+ return (*it).second;
+}
+} // namespace PVR
diff --git a/xbmc/pvr/settings/PVRCustomTimerSettings.h b/xbmc/pvr/settings/PVRCustomTimerSettings.h
new file mode 100644
index 0000000000..864269c212
--- /dev/null
+++ b/xbmc/pvr/settings/PVRCustomTimerSettings.h
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2024 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_timers.h" // PVR_TIMER_STATE
+#include "pvr/timers/PVRTimerInfoTag.h"
+
+#include <map>
+#include <memory>
+#include <string>
+#include <vector>
+
+class CSetting;
+class CSettingGroup;
+struct IntegerSettingOption;
+struct StringSettingOption;
+
+namespace PVR
+{
+class CPVRTimerSettingDefinition;
+class CPVRTimerType;
+class IPVRSettingsContainer;
+
+class CPVRCustomTimerSettings
+{
+public:
+ CPVRCustomTimerSettings(const CPVRTimerType& timerType,
+ const CPVRTimerInfoTag::CustomPropsMap& customProps,
+ const std::map<int, std::shared_ptr<CPVRTimerType>>& typeEntries);
+ virtual ~CPVRCustomTimerSettings() = default;
+
+ void SetTimerType(const CPVRTimerType& timerType);
+
+ void AddSettings(IPVRSettingsContainer& settingsContainer,
+ const std::shared_ptr<CSettingGroup>& group);
+
+ bool IsCustomSetting(const std::string& settingId) const;
+ bool IsCustomIntSetting(const std::string& settingId) const;
+ bool IsCustomStringSetting(const std::string& settingId) const;
+
+ const CPVRTimerInfoTag::CustomPropsMap& GetProperties() const { return m_customProps; }
+
+ bool UpdateIntProperty(const std::shared_ptr<const CSetting>& setting);
+ bool UpdateStringProperty(const std::shared_ptr<const CSetting>& setting);
+
+ std::string GetSettingsLabel(const std::string& settingId) const;
+
+ bool IntSettingDefinitionsFiller(const std::string& settingId,
+ std::vector<IntegerSettingOption>& list,
+ int& current);
+ bool StringSettingDefinitionsFiller(const std::string& settingId,
+ std::vector<StringSettingOption>& list,
+ std::string& current);
+
+ bool IsSettingReadonlyForTimerState(const std::string& settingId,
+ PVR_TIMER_STATE timerState) const;
+ bool IsSettingSupportedForTimerType(const std::string& setting,
+ const CPVRTimerType& timerType) const;
+
+private:
+ std::shared_ptr<const CPVRTimerSettingDefinition> GetSettingDefintion(
+ const std::string& settingId) const;
+
+ using CustomSettingDefinitionsMap =
+ std::map<std::string,
+ std::shared_ptr<const CPVRTimerSettingDefinition>>; // setting id, setting def
+
+ CustomSettingDefinitionsMap m_customSettingDefs;
+ CPVRTimerInfoTag::CustomPropsMap m_customProps;
+};
+} // namespace PVR
diff --git a/xbmc/pvr/settings/PVRIntSettingDefinition.cpp b/xbmc/pvr/settings/PVRIntSettingDefinition.cpp
new file mode 100644
index 0000000000..d36f8321fa
--- /dev/null
+++ b/xbmc/pvr/settings/PVRIntSettingDefinition.cpp
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2024 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "PVRIntSettingDefinition.h"
+
+#include "addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_general.h"
+
+namespace PVR
+{
+CPVRIntSettingDefinition::CPVRIntSettingDefinition(const PVR_INT_SETTING_DEFINITION& def)
+ : m_values{def.values, def.iValuesSize, def.iDefaultValue},
+ m_minValue{def.iMinValue},
+ m_step{def.iStep},
+ m_maxValue{def.iMaxValue}
+{
+}
+
+bool CPVRIntSettingDefinition::operator==(const CPVRIntSettingDefinition& right) const
+{
+ return (m_values == right.m_values && m_minValue == right.m_minValue && m_step == right.m_step &&
+ m_maxValue == right.m_maxValue);
+}
+
+bool CPVRIntSettingDefinition::operator!=(const CPVRIntSettingDefinition& right) const
+{
+ return !(*this == right);
+}
+} // namespace PVR
diff --git a/xbmc/pvr/settings/PVRIntSettingDefinition.h b/xbmc/pvr/settings/PVRIntSettingDefinition.h
new file mode 100644
index 0000000000..5f37bb4525
--- /dev/null
+++ b/xbmc/pvr/settings/PVRIntSettingDefinition.h
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2024 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "pvr/settings/PVRIntSettingValues.h"
+
+#include <vector>
+
+struct PVR_INT_SETTING_DEFINITION;
+
+namespace PVR
+{
+class CPVRIntSettingDefinition
+{
+public:
+ CPVRIntSettingDefinition() = default;
+ explicit CPVRIntSettingDefinition(const PVR_INT_SETTING_DEFINITION& def);
+
+ virtual ~CPVRIntSettingDefinition() = default;
+
+ bool operator==(const CPVRIntSettingDefinition& right) const;
+ bool operator!=(const CPVRIntSettingDefinition& right) const;
+
+ const std::vector<SettingIntValue>& GetValues() const { return m_values.GetValues(); }
+ int GetDefaultValue() const { return m_values.GetDefaultValue(); }
+ int GetMinValue() const { return m_minValue; }
+ int GetStepValue() const { return m_step; }
+ int GetMaxValue() const { return m_maxValue; }
+
+private:
+ CPVRIntSettingValues m_values;
+ int m_minValue{0};
+ int m_step{0};
+ int m_maxValue{0};
+};
+} // namespace PVR
diff --git a/xbmc/pvr/settings/PVRIntSettingValues.cpp b/xbmc/pvr/settings/PVRIntSettingValues.cpp
new file mode 100644
index 0000000000..62a69fcae1
--- /dev/null
+++ b/xbmc/pvr/settings/PVRIntSettingValues.cpp
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2024 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "PVRIntSettingValues.h"
+
+#include "addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_defines.h"
+#include "guilib/LocalizeStrings.h"
+#include "utils/StringUtils.h"
+
+#include <string>
+
+namespace PVR
+{
+CPVRIntSettingValues::CPVRIntSettingValues(struct PVR_ATTRIBUTE_INT_VALUE* values,
+ unsigned int valuesSize,
+ int defaultValue,
+ int defaultDescriptionResourceId /* = 0 */)
+ : m_defaultValue(defaultValue)
+{
+ if (values && valuesSize > 0)
+ {
+ m_values.reserve(valuesSize);
+ for (unsigned int i = 0; i < valuesSize; ++i)
+ {
+ const int value{values[i].iValue};
+ const char* desc{values[i].strDescription};
+ std::string description{desc ? desc : ""};
+ if (description.empty())
+ {
+ // No description given by addon. Create one from value.
+ if (defaultDescriptionResourceId > 0)
+ description = StringUtils::Format(
+ "{} {}", g_localizeStrings.Get(defaultDescriptionResourceId), value);
+ else
+ description = std::to_string(value);
+ }
+ m_values.emplace_back(description, value);
+ }
+ }
+}
+
+CPVRIntSettingValues::CPVRIntSettingValues(int defaultValue) : m_defaultValue(defaultValue)
+{
+}
+
+CPVRIntSettingValues::CPVRIntSettingValues(struct PVR_ATTRIBUTE_INT_VALUE* values,
+ unsigned int valuesSize,
+ unsigned int defaultValue,
+ int defaultDescriptionResourceId /* = 0 */)
+ : CPVRIntSettingValues(
+ values, valuesSize, static_cast<int>(defaultValue), defaultDescriptionResourceId)
+{
+}
+
+CPVRIntSettingValues::CPVRIntSettingValues(const std::vector<SettingIntValue>& values,
+ int defaultValue)
+ : m_values(values), m_defaultValue(defaultValue)
+{
+}
+
+bool CPVRIntSettingValues::operator==(const CPVRIntSettingValues& right) const
+{
+ return (m_defaultValue == right.m_defaultValue && m_values == right.m_values);
+}
+} // namespace PVR
diff --git a/xbmc/pvr/settings/PVRIntSettingValues.h b/xbmc/pvr/settings/PVRIntSettingValues.h
new file mode 100644
index 0000000000..209104a554
--- /dev/null
+++ b/xbmc/pvr/settings/PVRIntSettingValues.h
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2024 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include <string>
+#include <utility>
+#include <vector>
+
+struct PVR_ATTRIBUTE_INT_VALUE;
+
+namespace PVR
+{
+using SettingIntValue = std::pair<std::string, int>;
+
+class CPVRIntSettingValues
+{
+public:
+ CPVRIntSettingValues() = default;
+ explicit CPVRIntSettingValues(int defaultValue);
+ CPVRIntSettingValues(struct PVR_ATTRIBUTE_INT_VALUE* values,
+ unsigned int valuesSize,
+ int defaultValue,
+ int defaultDescriptionResourceId = 0);
+ CPVRIntSettingValues(struct PVR_ATTRIBUTE_INT_VALUE* values,
+ unsigned int valuesSize,
+ unsigned int defaultValue,
+ int defaultDescriptionResourceId = 0);
+ CPVRIntSettingValues(const std::vector<SettingIntValue>& values, int defaultValue);
+
+ virtual ~CPVRIntSettingValues() = default;
+
+ bool operator==(const CPVRIntSettingValues& right) const;
+
+ const std::vector<SettingIntValue>& GetValues() const { return m_values; }
+ int GetDefaultValue() const { return m_defaultValue; }
+
+private:
+ std::vector<SettingIntValue> m_values;
+ int m_defaultValue{0};
+};
+} // namespace PVR
diff --git a/xbmc/pvr/settings/PVRStringSettingDefinition.cpp b/xbmc/pvr/settings/PVRStringSettingDefinition.cpp
new file mode 100644
index 0000000000..e7f897d175
--- /dev/null
+++ b/xbmc/pvr/settings/PVRStringSettingDefinition.cpp
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2024 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "PVRStringSettingDefinition.h"
+
+#include "addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_general.h"
+
+namespace PVR
+{
+CPVRStringSettingDefinition::CPVRStringSettingDefinition(const PVR_STRING_SETTING_DEFINITION& def)
+ : m_values{def.values, def.iValuesSize, def.strDefaultValue},
+ m_allowEmptyValue{def.bAllowEmptyValue}
+{
+}
+
+bool CPVRStringSettingDefinition::operator==(const CPVRStringSettingDefinition& right) const
+{
+ return (m_values == right.m_values && m_allowEmptyValue == right.m_allowEmptyValue);
+}
+
+bool CPVRStringSettingDefinition::operator!=(const CPVRStringSettingDefinition& right) const
+{
+ return !(*this == right);
+}
+} // namespace PVR
diff --git a/xbmc/pvr/settings/PVRStringSettingDefinition.h b/xbmc/pvr/settings/PVRStringSettingDefinition.h
new file mode 100644
index 0000000000..da42f46149
--- /dev/null
+++ b/xbmc/pvr/settings/PVRStringSettingDefinition.h
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2024 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "pvr/settings/PVRStringSettingValues.h"
+
+#include <vector>
+
+struct PVR_STRING_SETTING_DEFINITION;
+
+namespace PVR
+{
+class CPVRStringSettingDefinition
+{
+public:
+ CPVRStringSettingDefinition() = default;
+ explicit CPVRStringSettingDefinition(const PVR_STRING_SETTING_DEFINITION& def);
+
+ virtual ~CPVRStringSettingDefinition() = default;
+
+ bool operator==(const CPVRStringSettingDefinition& right) const;
+ bool operator!=(const CPVRStringSettingDefinition& right) const;
+
+ const std::vector<SettingStringValue>& GetValues() const { return m_values.GetValues(); }
+ const std::string& GetDefaultValue() const { return m_values.GetDefaultValue(); }
+
+ bool IsAllowEmptyValue() const { return m_allowEmptyValue; }
+
+private:
+ CPVRStringSettingValues m_values;
+ bool m_allowEmptyValue{true};
+};
+} // namespace PVR
diff --git a/xbmc/pvr/settings/PVRStringSettingValues.cpp b/xbmc/pvr/settings/PVRStringSettingValues.cpp
new file mode 100644
index 0000000000..e3227aea95
--- /dev/null
+++ b/xbmc/pvr/settings/PVRStringSettingValues.cpp
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2024 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "PVRStringSettingValues.h"
+
+#include "addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_defines.h"
+#include "guilib/LocalizeStrings.h"
+#include "utils/StringUtils.h"
+
+#include <string>
+
+namespace PVR
+{
+CPVRStringSettingValues::CPVRStringSettingValues(struct PVR_ATTRIBUTE_STRING_VALUE* values,
+ unsigned int valuesSize,
+ const std::string& defaultValue,
+ int defaultDescriptionResourceId /* = 0 */)
+ : m_defaultValue(defaultValue)
+{
+ if (values && valuesSize > 0)
+ {
+ m_values.reserve(valuesSize);
+ for (unsigned int i = 0; i < valuesSize; ++i)
+ {
+ const std::string value{values[i].strValue ? values[i].strValue : ""};
+ const char* desc{values[i].strDescription};
+ std::string description{desc ? desc : ""};
+ if (description.empty())
+ {
+ // No description given by addon. Create one from value.
+ if (defaultDescriptionResourceId > 0)
+ description = StringUtils::Format(
+ "{} {}", g_localizeStrings.Get(defaultDescriptionResourceId), value);
+ else
+ description = value;
+ }
+ m_values.emplace_back(description, value);
+ }
+ }
+}
+
+bool CPVRStringSettingValues::operator==(const CPVRStringSettingValues& right) const
+{
+ return (m_defaultValue == right.m_defaultValue && m_values == right.m_values);
+}
+} // namespace PVR
diff --git a/xbmc/pvr/settings/PVRStringSettingValues.h b/xbmc/pvr/settings/PVRStringSettingValues.h
new file mode 100644
index 0000000000..591b5018db
--- /dev/null
+++ b/xbmc/pvr/settings/PVRStringSettingValues.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2024 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include <string>
+#include <utility>
+#include <vector>
+
+struct PVR_ATTRIBUTE_STRING_VALUE;
+
+namespace PVR
+{
+using SettingStringValue = std::pair<std::string, std::string>;
+
+class CPVRStringSettingValues
+{
+public:
+ CPVRStringSettingValues() = default;
+ CPVRStringSettingValues(struct PVR_ATTRIBUTE_STRING_VALUE* values,
+ unsigned int valuesSize,
+ const std::string& defaultValue,
+ int defaultDescriptionResourceId = 0);
+
+ virtual ~CPVRStringSettingValues() = default;
+
+ bool operator==(const CPVRStringSettingValues& right) const;
+
+ const std::vector<SettingStringValue>& GetValues() const { return m_values; }
+ const std::string& GetDefaultValue() const { return m_defaultValue; }
+
+private:
+ std::vector<SettingStringValue> m_values;
+ std::string m_defaultValue;
+};
+} // namespace PVR
diff --git a/xbmc/pvr/settings/PVRTimerSettingDefinition.cpp b/xbmc/pvr/settings/PVRTimerSettingDefinition.cpp
new file mode 100644
index 0000000000..cfabb48754
--- /dev/null
+++ b/xbmc/pvr/settings/PVRTimerSettingDefinition.cpp
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2024 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "PVRTimerSettingDefinition.h"
+
+#include "addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_general.h"
+#include "utils/Variant.h"
+#include "utils/log.h"
+
+namespace PVR
+{
+std::vector<std::shared_ptr<const CPVRTimerSettingDefinition>> CPVRTimerSettingDefinition::
+ CreateSettingDefinitionsList(int clientId,
+ unsigned int timerTypeId,
+ struct PVR_SETTING_DEFINITION** defs,
+ unsigned int settingDefsSize)
+{
+ std::vector<std::shared_ptr<const CPVRTimerSettingDefinition>> defsList;
+ if (defs && settingDefsSize > 0)
+ {
+ defsList.reserve(settingDefsSize);
+ for (unsigned int i = 0; i < settingDefsSize; ++i)
+ {
+ const PVR_SETTING_DEFINITION* def{defs[i]};
+ if (def)
+ {
+ defsList.emplace_back(
+ std::make_shared<const CPVRTimerSettingDefinition>(clientId, timerTypeId, *def));
+ }
+ }
+ }
+ return defsList;
+}
+
+CPVRTimerSettingDefinition::CPVRTimerSettingDefinition(int clientId,
+ unsigned int timerTypeId,
+ const PVR_SETTING_DEFINITION& def)
+ : m_clientId(clientId),
+ m_timerTypeId(timerTypeId),
+ m_id(def.iId),
+ m_name(def.strName ? def.strName : ""),
+ m_type(def.eType),
+ m_readonlyConditions(def.iReadonlyConditions)
+{
+ if (def.intSettingDefinition)
+ m_intDefinition = CPVRIntSettingDefinition{*def.intSettingDefinition};
+ if (def.stringSettingDefinition)
+ m_stringDefinition = CPVRStringSettingDefinition{*def.stringSettingDefinition};
+}
+
+bool CPVRTimerSettingDefinition::operator==(const CPVRTimerSettingDefinition& right) const
+{
+ return (m_id == right.m_id && m_name == right.m_name && m_type == right.m_type &&
+ m_readonlyConditions == right.m_readonlyConditions &&
+ m_intDefinition == right.m_intDefinition &&
+ m_stringDefinition == right.m_stringDefinition);
+}
+
+bool CPVRTimerSettingDefinition::operator!=(const CPVRTimerSettingDefinition& right) const
+{
+ return !(*this == right);
+}
+
+CVariant CPVRTimerSettingDefinition::GetDefaultValue() const
+{
+ if (GetType() == PVR_SETTING_TYPE::INTEGER)
+ return CVariant{GetIntDefinition().GetDefaultValue()};
+ else if (GetType() == PVR_SETTING_TYPE::STRING)
+ return CVariant{GetStringDefinition().GetDefaultValue()};
+
+ CLog::LogF(LOGERROR, "Unknown setting type for custom property");
+ return {};
+}
+
+bool CPVRTimerSettingDefinition::IsReadonlyForTimerState(PVR_TIMER_STATE timerState) const
+{
+ switch (timerState)
+ {
+ case PVR_TIMER_STATE_DISABLED:
+ return m_readonlyConditions & PVR_SETTING_READONLY_CONDITION_TIMER_DISABLED;
+ case PVR_TIMER_STATE_SCHEDULED:
+ case PVR_TIMER_STATE_CONFLICT_OK:
+ case PVR_TIMER_STATE_CONFLICT_NOK:
+ return m_readonlyConditions & PVR_SETTING_READONLY_CONDITION_TIMER_SCHEDULED;
+ case PVR_TIMER_STATE_RECORDING:
+ return m_readonlyConditions & PVR_SETTING_READONLY_CONDITION_TIMER_RECORDING;
+ case PVR_TIMER_STATE_COMPLETED:
+ case PVR_TIMER_STATE_ABORTED:
+ case PVR_TIMER_STATE_CANCELLED:
+ case PVR_TIMER_STATE_ERROR:
+ return m_readonlyConditions & PVR_SETTING_READONLY_CONDITION_TIMER_COMPLETED;
+ default:
+ CLog::LogF(LOGWARNING, "Unhandled timer state {}", timerState);
+ break;
+ }
+ return false;
+}
+} // namespace PVR
diff --git a/xbmc/pvr/settings/PVRTimerSettingDefinition.h b/xbmc/pvr/settings/PVRTimerSettingDefinition.h
new file mode 100644
index 0000000000..beec742a48
--- /dev/null
+++ b/xbmc/pvr/settings/PVRTimerSettingDefinition.h
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2024 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_general.h"
+#include "addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_timers.h"
+#include "pvr/PVRConstants.h" // PVR_CLIENT_INVALID_UID
+#include "pvr/settings/PVRIntSettingDefinition.h"
+#include "pvr/settings/PVRStringSettingDefinition.h"
+
+#include <cstdint>
+#include <memory>
+#include <string>
+#include <vector>
+
+class CVariant;
+
+namespace PVR
+{
+class CPVRTimerSettingDefinition
+{
+public:
+ static std::vector<std::shared_ptr<const CPVRTimerSettingDefinition>>
+ CreateSettingDefinitionsList(int clientId,
+ unsigned int timerTypeId,
+ struct PVR_SETTING_DEFINITION** defs,
+ unsigned int settingDefsSize);
+
+ CPVRTimerSettingDefinition(int clientId,
+ unsigned int timerTypeId,
+ const PVR_SETTING_DEFINITION& def);
+
+ virtual ~CPVRTimerSettingDefinition() = default;
+
+ bool operator==(const CPVRTimerSettingDefinition& right) const;
+ bool operator!=(const CPVRTimerSettingDefinition& right) const;
+
+ int GetClientId() const { return m_clientId; }
+ unsigned int GetTimerTypeId() const { return m_timerTypeId; }
+ unsigned int GetId() const { return m_id; }
+ const std::string& GetName() const { return m_name; }
+ PVR_SETTING_TYPE GetType() const { return m_type; }
+ CVariant GetDefaultValue() const;
+
+ bool IsIntDefinition() const { return m_type == PVR_SETTING_TYPE::INTEGER; }
+ bool IsStringDefinition() const { return m_type == PVR_SETTING_TYPE::STRING; }
+ const CPVRIntSettingDefinition& GetIntDefinition() const { return m_intDefinition; }
+ const CPVRStringSettingDefinition& GetStringDefinition() const { return m_stringDefinition; }
+
+ bool IsReadonlyForTimerState(PVR_TIMER_STATE timerState) const;
+
+private:
+ int m_clientId{PVR_CLIENT_INVALID_UID};
+ unsigned int m_timerTypeId{PVR_TIMER_TYPE_NONE};
+ unsigned int m_id{0};
+ std::string m_name;
+ PVR_SETTING_TYPE m_type{PVR_SETTING_TYPE::INTEGER};
+ uint64_t m_readonlyConditions{PVR_SETTING_READONLY_CONDITION_NONE};
+ CPVRIntSettingDefinition m_intDefinition;
+ CPVRStringSettingDefinition m_stringDefinition;
+};
+} // namespace PVR
diff --git a/xbmc/pvr/timers/PVRTimerInfoTag.cpp b/xbmc/pvr/timers/PVRTimerInfoTag.cpp
index 100cfa6674..6f5ea746ea 100644
--- a/xbmc/pvr/timers/PVRTimerInfoTag.cpp
+++ b/xbmc/pvr/timers/PVRTimerInfoTag.cpp
@@ -9,6 +9,7 @@
#include "PVRTimerInfoTag.h"
#include "ServiceBroker.h"
+#include "addons/AddonVersion.h"
#include "guilib/LocalizeStrings.h"
#include "pvr/PVRDatabase.h"
#include "pvr/PVRManager.h"
@@ -89,7 +90,8 @@ CPVRTimerInfoTag::CPVRTimerInfoTag(bool bRadio /* = false */)
CPVRTimerInfoTag::CPVRTimerInfoTag(const PVR_TIMER& timer,
const std::shared_ptr<CPVRChannel>& channel,
- unsigned int iClientId)
+ unsigned int iClientId,
+ const ADDON::CAddonVersion& addonApiVersion)
: m_strTitle(timer.strTitle ? timer.strTitle : ""),
m_strEpgSearchString(timer.strEpgSearchString ? timer.strEpgSearchString : ""),
m_bFullTextEpgSearch(timer.bFullTextEpgSearch),
@@ -133,6 +135,22 @@ CPVRTimerInfoTag::CPVRTimerInfoTag(const PVR_TIMER& timer,
if (m_iClientIndex == PVR_TIMER_NO_CLIENT_INDEX)
CLog::LogF(LOGERROR, "Invalid client index supplied by client {} (must be > 0)!", m_iClientId);
+ //! @todo version check can be removed with next incompatible API bump
+ static const ADDON::CAddonVersion customSettingsMinApiVersion{"9.1.0"};
+ if (addonApiVersion >= customSettingsMinApiVersion)
+ {
+ for (unsigned int i = 0; i < timer.iCustomPropsSize; ++i)
+ {
+ const PVR_SETTING_KEY_VALUE_PAIR& prop{timer.customProps[i]};
+ if (prop.eType == PVR_SETTING_TYPE::INTEGER)
+ m_customProps.insert({prop.iKey, {prop.eType, CVariant{prop.iValue}}});
+ else if (prop.eType == PVR_SETTING_TYPE::STRING)
+ m_customProps.insert({prop.iKey, {prop.eType, CVariant{prop.strValue}}});
+ else
+ CLog::LogF(LOGERROR, "Unknown setting type for custom property");
+ }
+ }
+
const std::shared_ptr<const CPVRClient> client =
CServiceBroker::GetPVRManager().GetClient(m_iClientId);
if (client && client->GetClientCapabilities().SupportsTimers())
@@ -221,7 +239,8 @@ bool CPVRTimerInfoTag::operator==(const CPVRTimerInfoTag& right) const
m_iRadioChildTimersActive == right.m_iRadioChildTimersActive &&
m_iRadioChildTimersConflictNOK == right.m_iRadioChildTimersConflictNOK &&
m_iRadioChildTimersRecording == right.m_iRadioChildTimersRecording &&
- m_iRadioChildTimersErrors == right.m_iRadioChildTimersErrors);
+ m_iRadioChildTimersErrors == right.m_iRadioChildTimersErrors &&
+ m_customProps == right.m_customProps);
}
bool CPVRTimerInfoTag::operator!=(const CPVRTimerInfoTag& right) const
@@ -529,7 +548,7 @@ std::string CPVRTimerInfoTag::GetWeekdaysString() const
bool CPVRTimerInfoTag::IsOwnedByClient() const
{
- return m_timerType->GetClientId() > -1;
+ return m_timerType->GetClientId() > PVR_CLIENT_INVALID_UID;
}
bool CPVRTimerInfoTag::AddToClient() const
@@ -605,6 +624,7 @@ bool CPVRTimerInfoTag::UpdateEntry(const std::shared_ptr<const CPVRTimerInfoTag>
m_strSummary = tag->m_strSummary;
m_channel = tag->m_channel;
m_bProbedEpgTag = tag->m_bProbedEpgTag;
+ m_customProps = tag->m_customProps;
m_iTVChildTimersActive = tag->m_iTVChildTimersActive;
m_iTVChildTimersConflictNOK = tag->m_iTVChildTimersConflictNOK;
@@ -1150,7 +1170,7 @@ int CPVRTimerInfoTag::GetDuration() const
time_t start, end;
m_StartTime.GetAsTime(start);
m_StopTime.GetAsTime(end);
- return end - start > 0 ? end - start : 3600;
+ return end - start > 0 ? static_cast<int>(end - start) : 3600;
}
CDateTime CPVRTimerInfoTag::FirstDayAsUTC() const
diff --git a/xbmc/pvr/timers/PVRTimerInfoTag.h b/xbmc/pvr/timers/PVRTimerInfoTag.h
index 4098c067c7..4aa17696eb 100644
--- a/xbmc/pvr/timers/PVRTimerInfoTag.h
+++ b/xbmc/pvr/timers/PVRTimerInfoTag.h
@@ -9,15 +9,23 @@
#pragma once
#include "XBDateTime.h"
+#include "addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_general.h" // PVR_SETTING_TYPE
#include "pvr/timers/PVRTimerType.h"
#include "threads/CriticalSection.h"
#include "utils/ISerializable.h"
+#include "utils/Variant.h"
+#include <map>
#include <memory>
#include <string>
struct PVR_TIMER;
+namespace ADDON
+{
+class CAddonVersion;
+}
+
namespace PVR
{
class CPVRChannel;
@@ -40,7 +48,8 @@ public:
explicit CPVRTimerInfoTag(bool bRadio = false);
CPVRTimerInfoTag(const PVR_TIMER& timer,
const std::shared_ptr<CPVRChannel>& channel,
- unsigned int iClientId);
+ unsigned int iClientId,
+ const ADDON::CAddonVersion& addonApiVersion);
bool operator==(const CPVRTimerInfoTag& right) const;
bool operator!=(const CPVRTimerInfoTag& right) const;
@@ -227,7 +236,7 @@ public:
/*!
* @brief The ID of the client for this timer.
- * @return The client ID or -1 if this is a local timer.
+ * @return The client ID or PVR_CLIENT_INVALID_UID if this is a local timer.
*/
int ClientID() const { return m_iClientId; }
@@ -515,6 +524,31 @@ public:
int RecordingGroup() const { return m_iRecordingGroup; }
/*!
+ * @brief custom property detail: type, value
+ */
+ struct CustomPropDetails
+ {
+ PVR_SETTING_TYPE type{PVR_SETTING_TYPE::INTEGER};
+ CVariant value;
+
+ bool operator==(const CustomPropDetails& right) const
+ {
+ return type == right.type && value == right.value;
+ }
+ };
+
+ /*!
+ * @brief custom properties map: <prop id, details>
+ */
+ using CustomPropsMap = std::map<unsigned int, CustomPropDetails>;
+
+ /*!
+ * @brief Get custom properties for this tag.
+ * @return The list of properties or an empty list if none present.
+ */
+ const CustomPropsMap& GetCustomProperties() const { return m_customProps; }
+
+ /*!
* @brief Get the UID of the epg event associated with this timer tag, if any.
* @return The UID or EPG_TAG_INVALID_UID.
*/
@@ -651,6 +685,7 @@ private:
mutable unsigned int
m_iEpgUid; /*!< id of epg event associated with this timer, EPG_TAG_INVALID_UID if none. */
std::string m_strSeriesLink; /*!< series link */
+ CustomPropsMap m_customProps; /*!< the map with custom properties supplied by the client. */
CDateTime m_StartTime; /*!< start time */
CDateTime m_StopTime; /*!< stop time */
diff --git a/xbmc/pvr/timers/PVRTimerRuleMatcher.cpp b/xbmc/pvr/timers/PVRTimerRuleMatcher.cpp
index 42cc4e7e0b..fdaf93420a 100644
--- a/xbmc/pvr/timers/PVRTimerRuleMatcher.cpp
+++ b/xbmc/pvr/timers/PVRTimerRuleMatcher.cpp
@@ -10,7 +10,7 @@
#include "XBDateTime.h"
#include "addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_channels.h" // PVR_CHANNEL_INVALID_UID
-#include "pvr/addons/PVRClient.h" // PVR_ANY_CLIENT_ID
+#include "pvr/PVRConstants.h" // PVR_CLIENT_INVALID_UID
#include "pvr/epg/EpgInfoTag.h"
#include "pvr/timers/PVRTimerInfoTag.h"
#include "utils/RegExp.h"
@@ -92,7 +92,7 @@ bool CPVRTimerRuleMatcher::MatchChannel(const std::shared_ptr<const CPVREpgInfoT
{
if (m_timerRule->GetTimerType()->SupportsAnyChannel() &&
m_timerRule->ClientChannelUID() == PVR_CHANNEL_INVALID_UID &&
- (m_timerRule->ClientID() == PVR_ANY_CLIENT_ID ||
+ (m_timerRule->ClientID() == PVR_CLIENT_INVALID_UID ||
m_timerRule->ClientID() == epgTag->ClientID()))
return true; // matches any channel from any client / any channel from a certain client
diff --git a/xbmc/pvr/timers/PVRTimerType.cpp b/xbmc/pvr/timers/PVRTimerType.cpp
index 9a68e49305..3df0f81808 100644
--- a/xbmc/pvr/timers/PVRTimerType.cpp
+++ b/xbmc/pvr/timers/PVRTimerType.cpp
@@ -9,10 +9,12 @@
#include "PVRTimerType.h"
#include "ServiceBroker.h"
+#include "addons/AddonVersion.h"
#include "guilib/LocalizeStrings.h"
#include "pvr/PVRManager.h"
#include "pvr/addons/PVRClient.h"
#include "pvr/addons/PVRClients.h"
+#include "pvr/settings/PVRTimerSettingDefinition.h"
#include "utils/StringUtils.h"
#include "utils/log.h"
@@ -34,71 +36,52 @@ const std::vector<std::shared_ptr<CPVRTimerType>> CPVRTimerType::GetAllTypes()
int iTypeId = PVR_TIMER_TYPE_NONE;
// one time time-based reminder
- allTypes.emplace_back(std::make_shared<CPVRTimerType>(++iTypeId,
- PVR_TIMER_TYPE_IS_MANUAL |
- PVR_TIMER_TYPE_IS_REMINDER |
- PVR_TIMER_TYPE_SUPPORTS_CHANNELS |
- PVR_TIMER_TYPE_SUPPORTS_START_TIME |
- PVR_TIMER_TYPE_SUPPORTS_END_TIME));
+ allTypes.emplace_back(std::make_shared<CPVRTimerType>(
+ ++iTypeId, PVR_TIMER_TYPE_IS_MANUAL | PVR_TIMER_TYPE_IS_REMINDER |
+ PVR_TIMER_TYPE_SUPPORTS_CHANNELS | PVR_TIMER_TYPE_SUPPORTS_START_TIME |
+ PVR_TIMER_TYPE_SUPPORTS_END_TIME));
// one time epg-based reminder
- allTypes.emplace_back(std::make_shared<CPVRTimerType>(++iTypeId,
- PVR_TIMER_TYPE_IS_REMINDER |
- PVR_TIMER_TYPE_REQUIRES_EPG_TAG_ON_CREATE |
- PVR_TIMER_TYPE_SUPPORTS_CHANNELS |
- PVR_TIMER_TYPE_SUPPORTS_START_TIME |
- PVR_TIMER_TYPE_SUPPORTS_START_MARGIN));
+ allTypes.emplace_back(std::make_shared<CPVRTimerType>(
+ ++iTypeId, PVR_TIMER_TYPE_IS_REMINDER | PVR_TIMER_TYPE_REQUIRES_EPG_TAG_ON_CREATE |
+ PVR_TIMER_TYPE_SUPPORTS_CHANNELS | PVR_TIMER_TYPE_SUPPORTS_START_TIME |
+ PVR_TIMER_TYPE_SUPPORTS_START_MARGIN));
// time-based reminder rule
- allTypes.emplace_back(std::make_shared<CPVRTimerType>(++iTypeId,
- PVR_TIMER_TYPE_IS_REPEATING |
- PVR_TIMER_TYPE_IS_MANUAL |
- PVR_TIMER_TYPE_IS_REMINDER |
- PVR_TIMER_TYPE_SUPPORTS_ENABLE_DISABLE |
- PVR_TIMER_TYPE_SUPPORTS_CHANNELS |
- PVR_TIMER_TYPE_SUPPORTS_START_TIME |
- PVR_TIMER_TYPE_SUPPORTS_END_TIME |
- PVR_TIMER_TYPE_SUPPORTS_FIRST_DAY |
- PVR_TIMER_TYPE_SUPPORTS_WEEKDAYS));
+ allTypes.emplace_back(std::make_shared<CPVRTimerType>(
+ ++iTypeId, PVR_TIMER_TYPE_IS_REPEATING | PVR_TIMER_TYPE_IS_MANUAL |
+ PVR_TIMER_TYPE_IS_REMINDER | PVR_TIMER_TYPE_SUPPORTS_ENABLE_DISABLE |
+ PVR_TIMER_TYPE_SUPPORTS_CHANNELS | PVR_TIMER_TYPE_SUPPORTS_START_TIME |
+ PVR_TIMER_TYPE_SUPPORTS_END_TIME | PVR_TIMER_TYPE_SUPPORTS_FIRST_DAY |
+ PVR_TIMER_TYPE_SUPPORTS_WEEKDAYS));
// one time read-only time-based reminder (created by timer rule)
- allTypes.emplace_back(std::make_shared<CPVRTimerType>(++iTypeId,
- PVR_TIMER_TYPE_IS_MANUAL |
- PVR_TIMER_TYPE_IS_REMINDER |
- PVR_TIMER_TYPE_IS_READONLY |
- PVR_TIMER_TYPE_SUPPORTS_ENABLE_DISABLE |
- PVR_TIMER_TYPE_SUPPORTS_CHANNELS |
- PVR_TIMER_TYPE_SUPPORTS_START_TIME |
- PVR_TIMER_TYPE_SUPPORTS_END_TIME,
- g_localizeStrings.Get(819))); // One time (Scheduled by timer rule)
+ allTypes.emplace_back(std::make_shared<CPVRTimerType>(
+ ++iTypeId,
+ PVR_TIMER_TYPE_IS_MANUAL | PVR_TIMER_TYPE_IS_REMINDER | PVR_TIMER_TYPE_IS_READONLY |
+ PVR_TIMER_TYPE_SUPPORTS_ENABLE_DISABLE | PVR_TIMER_TYPE_SUPPORTS_CHANNELS |
+ PVR_TIMER_TYPE_SUPPORTS_START_TIME | PVR_TIMER_TYPE_SUPPORTS_END_TIME,
+ g_localizeStrings.Get(819))); // One time (Scheduled by timer rule)
// epg-based reminder rule
- allTypes.emplace_back(std::make_shared<CPVRTimerType>(++iTypeId,
- PVR_TIMER_TYPE_IS_REPEATING |
- PVR_TIMER_TYPE_IS_REMINDER |
- PVR_TIMER_TYPE_SUPPORTS_ENABLE_DISABLE |
- PVR_TIMER_TYPE_SUPPORTS_TITLE_EPG_MATCH |
- PVR_TIMER_TYPE_SUPPORTS_FULLTEXT_EPG_MATCH |
- PVR_TIMER_TYPE_SUPPORTS_CHANNELS |
- PVR_TIMER_TYPE_SUPPORTS_ANY_CHANNEL |
- PVR_TIMER_TYPE_SUPPORTS_START_TIME |
- PVR_TIMER_TYPE_SUPPORTS_START_ANYTIME |
- PVR_TIMER_TYPE_SUPPORTS_END_TIME |
- PVR_TIMER_TYPE_SUPPORTS_END_ANYTIME |
- PVR_TIMER_TYPE_SUPPORTS_FIRST_DAY |
- PVR_TIMER_TYPE_SUPPORTS_WEEKDAYS |
- PVR_TIMER_TYPE_SUPPORTS_START_END_MARGIN));
+ allTypes.emplace_back(std::make_shared<CPVRTimerType>(
+ ++iTypeId, PVR_TIMER_TYPE_IS_REPEATING | PVR_TIMER_TYPE_IS_REMINDER |
+ PVR_TIMER_TYPE_SUPPORTS_ENABLE_DISABLE |
+ PVR_TIMER_TYPE_SUPPORTS_TITLE_EPG_MATCH |
+ PVR_TIMER_TYPE_SUPPORTS_FULLTEXT_EPG_MATCH | PVR_TIMER_TYPE_SUPPORTS_CHANNELS |
+ PVR_TIMER_TYPE_SUPPORTS_ANY_CHANNEL | PVR_TIMER_TYPE_SUPPORTS_START_TIME |
+ PVR_TIMER_TYPE_SUPPORTS_START_ANYTIME | PVR_TIMER_TYPE_SUPPORTS_END_TIME |
+ PVR_TIMER_TYPE_SUPPORTS_END_ANYTIME | PVR_TIMER_TYPE_SUPPORTS_FIRST_DAY |
+ PVR_TIMER_TYPE_SUPPORTS_WEEKDAYS | PVR_TIMER_TYPE_SUPPORTS_START_END_MARGIN));
// one time read-only epg-based reminder (created by timer rule)
- allTypes.emplace_back(std::make_shared<CPVRTimerType>(++iTypeId,
- PVR_TIMER_TYPE_IS_REMINDER |
- PVR_TIMER_TYPE_IS_READONLY |
- PVR_TIMER_TYPE_REQUIRES_EPG_TAG_ON_CREATE |
- PVR_TIMER_TYPE_SUPPORTS_ENABLE_DISABLE |
- PVR_TIMER_TYPE_SUPPORTS_CHANNELS |
- PVR_TIMER_TYPE_SUPPORTS_START_TIME |
- PVR_TIMER_TYPE_SUPPORTS_START_MARGIN,
- g_localizeStrings.Get(819))); // One time (Scheduled by timer rule)
+ allTypes.emplace_back(std::make_shared<CPVRTimerType>(
+ ++iTypeId,
+ PVR_TIMER_TYPE_IS_REMINDER | PVR_TIMER_TYPE_IS_READONLY |
+ PVR_TIMER_TYPE_REQUIRES_EPG_TAG_ON_CREATE | PVR_TIMER_TYPE_SUPPORTS_ENABLE_DISABLE |
+ PVR_TIMER_TYPE_SUPPORTS_CHANNELS | PVR_TIMER_TYPE_SUPPORTS_START_TIME |
+ PVR_TIMER_TYPE_SUPPORTS_START_MARGIN,
+ g_localizeStrings.Get(819))); // One time (Scheduled by timer rule)
return allTypes;
}
@@ -121,16 +104,16 @@ std::shared_ptr<CPVRTimerType> CPVRTimerType::CreateFromIds(unsigned int iTypeId
{
const std::vector<std::shared_ptr<CPVRTimerType>> types = GetAllTypes();
const auto it =
- std::find_if(types.cbegin(), types.cend(), [iClientId, iTypeId](const auto& type) {
- return type->GetClientId() == iClientId && type->GetTypeId() == iTypeId;
- });
+ std::find_if(types.cbegin(), types.cend(),
+ [iClientId, iTypeId](const auto& type)
+ { return type->GetClientId() == iClientId && type->GetTypeId() == iTypeId; });
if (it != types.cend())
return (*it);
- if (iClientId != -1)
+ if (iClientId != PVR_CLIENT_INVALID_UID)
{
// fallback. try to obtain local timer type.
- std::shared_ptr<CPVRTimerType> type = CreateFromIds(iTypeId, -1);
+ std::shared_ptr<CPVRTimerType> type = CreateFromIds(iTypeId, PVR_CLIENT_INVALID_UID);
if (type)
return type;
}
@@ -145,7 +128,8 @@ std::shared_ptr<CPVRTimerType> CPVRTimerType::CreateFromAttributes(uint64_t iMus
{
const std::vector<std::shared_ptr<CPVRTimerType>> types = GetAllTypes();
const auto it = std::find_if(types.cbegin(), types.cend(),
- [iClientId, iMustHaveAttr, iMustNotHaveAttr](const auto& type) {
+ [iClientId, iMustHaveAttr, iMustNotHaveAttr](const auto& type)
+ {
return type->GetClientId() == iClientId &&
(type->GetAttributes() & iMustHaveAttr) == iMustHaveAttr &&
(type->GetAttributes() & iMustNotHaveAttr) == 0;
@@ -153,10 +137,11 @@ std::shared_ptr<CPVRTimerType> CPVRTimerType::CreateFromAttributes(uint64_t iMus
if (it != types.cend())
return (*it);
- if (iClientId != -1)
+ if (iClientId != PVR_CLIENT_INVALID_UID)
{
// fallback. try to obtain local timer type.
- std::shared_ptr<CPVRTimerType> type = CreateFromAttributes(iMustHaveAttr, iMustNotHaveAttr, -1);
+ std::shared_ptr<CPVRTimerType> type =
+ CreateFromAttributes(iMustHaveAttr, iMustNotHaveAttr, PVR_CLIENT_INVALID_UID);
if (type)
return type;
}
@@ -166,13 +151,14 @@ std::shared_ptr<CPVRTimerType> CPVRTimerType::CreateFromAttributes(uint64_t iMus
return {};
}
-CPVRTimerType::CPVRTimerType() :
- m_iTypeId(PVR_TIMER_TYPE_NONE),
- m_iAttributes(PVR_TIMER_TYPE_ATTRIBUTE_NONE)
+CPVRTimerType::CPVRTimerType()
+ : m_iTypeId(PVR_TIMER_TYPE_NONE), m_iAttributes(PVR_TIMER_TYPE_ATTRIBUTE_NONE)
{
}
-CPVRTimerType::CPVRTimerType(const PVR_TIMER_TYPE& type, int iClientId)
+CPVRTimerType::CPVRTimerType(const PVR_TIMER_TYPE& type,
+ int iClientId,
+ const ADDON::CAddonVersion& addonApiVersion)
: m_iClientId(iClientId),
m_iTypeId(type.iId),
m_iAttributes(type.iAttributes),
@@ -180,6 +166,13 @@ CPVRTimerType::CPVRTimerType(const PVR_TIMER_TYPE& type, int iClientId)
{
InitDescription();
InitAttributeValues(type);
+
+ //! @todo version check can be removed with next incompatible API bump
+ static const ADDON::CAddonVersion customSettingsMinApiVersion{"9.1.0"};
+ if (addonApiVersion >= customSettingsMinApiVersion)
+ {
+ InitCustomSettingDefinitions(type);
+ }
}
CPVRTimerType::CPVRTimerType(unsigned int iTypeId,
@@ -192,25 +185,19 @@ CPVRTimerType::CPVRTimerType(unsigned int iTypeId,
CPVRTimerType::~CPVRTimerType() = default;
-bool CPVRTimerType::operator ==(const CPVRTimerType& right) const
+bool CPVRTimerType::operator==(const CPVRTimerType& right) const
{
- return (m_iClientId == right.m_iClientId &&
- m_iTypeId == right.m_iTypeId &&
- m_iAttributes == right.m_iAttributes &&
- m_strDescription == right.m_strDescription &&
+ return (m_iClientId == right.m_iClientId && m_iTypeId == right.m_iTypeId &&
+ m_iAttributes == right.m_iAttributes && m_strDescription == right.m_strDescription &&
m_priorityValues == right.m_priorityValues &&
- m_iPriorityDefault == right.m_iPriorityDefault &&
m_lifetimeValues == right.m_lifetimeValues &&
- m_iLifetimeDefault == right.m_iLifetimeDefault &&
m_maxRecordingsValues == right.m_maxRecordingsValues &&
- m_iMaxRecordingsDefault == right.m_iMaxRecordingsDefault &&
m_preventDupEpisodesValues == right.m_preventDupEpisodesValues &&
- m_iPreventDupEpisodesDefault == right.m_iPreventDupEpisodesDefault &&
m_recordingGroupValues == right.m_recordingGroupValues &&
- m_iRecordingGroupDefault == right.m_iRecordingGroupDefault);
+ m_customSettingDefs == right.m_customSettingDefs);
}
-bool CPVRTimerType::operator !=(const CPVRTimerType& right) const
+bool CPVRTimerType::operator!=(const CPVRTimerType& right) const
{
return !(*this == right);
}
@@ -222,15 +209,11 @@ void CPVRTimerType::Update(const CPVRTimerType& type)
m_iAttributes = type.m_iAttributes;
m_strDescription = type.m_strDescription;
m_priorityValues = type.m_priorityValues;
- m_iPriorityDefault = type.m_iPriorityDefault;
m_lifetimeValues = type.m_lifetimeValues;
- m_iLifetimeDefault = type.m_iLifetimeDefault;
m_maxRecordingsValues = type.m_maxRecordingsValues;
- m_iMaxRecordingsDefault = type.m_iMaxRecordingsDefault;
m_preventDupEpisodesValues = type.m_preventDupEpisodesValues;
- m_iPreventDupEpisodesDefault = type.m_iPreventDupEpisodesDefault;
m_recordingGroupValues = type.m_recordingGroupValues;
- m_iRecordingGroupDefault = type.m_iRecordingGroupDefault;
+ m_customSettingDefs = type.m_customSettingDefs;
}
void CPVRTimerType::InitDescription()
@@ -241,23 +224,20 @@ void CPVRTimerType::InitDescription()
int id;
if (m_iAttributes & PVR_TIMER_TYPE_IS_REPEATING)
{
- id = (m_iAttributes & PVR_TIMER_TYPE_IS_MANUAL)
- ? 822 // "Timer rule"
- : 823; // "Timer rule (guide-based)"
+ id = (m_iAttributes & PVR_TIMER_TYPE_IS_MANUAL) ? 822 // "Timer rule"
+ : 823; // "Timer rule (guide-based)"
}
else
{
- id = (m_iAttributes & PVR_TIMER_TYPE_IS_MANUAL)
- ? 820 // "One time"
- : 821; // "One time (guide-based)
+ id = (m_iAttributes & PVR_TIMER_TYPE_IS_MANUAL) ? 820 // "One time"
+ : 821; // "One time (guide-based)
}
m_strDescription = g_localizeStrings.Get(id);
}
// add reminder/recording prefix
- int prefixId = (m_iAttributes & PVR_TIMER_TYPE_IS_REMINDER)
- ? 824 // Reminder: ...
- : 825; // Recording: ...
+ int prefixId = (m_iAttributes & PVR_TIMER_TYPE_IS_REMINDER) ? 824 // Reminder: ...
+ : 825; // Recording: ...
m_strDescription = StringUtils::Format(g_localizeStrings.Get(prefixId), m_strDescription);
}
@@ -275,172 +255,83 @@ void CPVRTimerType::InitPriorityValues(const PVR_TIMER_TYPE& type)
{
if (type.iPrioritiesSize > 0)
{
- for (unsigned int i = 0; i < type.iPrioritiesSize; ++i)
- {
- const int value{type.priorities[i].iValue};
- const char* desc{type.priorities[i].strDescription};
- std::string strDescr{desc ? desc : ""};
- if (strDescr.empty())
- {
- // No description given by addon. Create one from value.
- strDescr = std::to_string(value);
- }
- m_priorityValues.emplace_back(strDescr, value);
- }
-
- m_iPriorityDefault = type.iPrioritiesDefault;
+ m_priorityValues = {type.priorities, type.iPrioritiesSize, type.iPrioritiesDefault};
}
else if (SupportsPriority())
{
// No values given by addon, but priority supported. Use default values 1..100
+ std::vector<SettingIntValue> values;
for (int i = 1; i < 101; ++i)
- m_priorityValues.emplace_back(std::to_string(i), i);
+ values.emplace_back(std::to_string(i), i);
- m_iPriorityDefault = DEFAULT_RECORDING_PRIORITY;
+ m_priorityValues = {values, DEFAULT_RECORDING_PRIORITY};
}
else
{
// No priority supported.
- m_iPriorityDefault = DEFAULT_RECORDING_PRIORITY;
+ m_priorityValues = CPVRIntSettingValues{DEFAULT_RECORDING_PRIORITY};
}
}
-void CPVRTimerType::GetPriorityValues(std::vector<std::pair<std::string, int>>& list) const
-{
- std::copy(m_priorityValues.cbegin(), m_priorityValues.cend(), std::back_inserter(list));
-}
-
void CPVRTimerType::InitLifetimeValues(const PVR_TIMER_TYPE& type)
{
if (type.iLifetimesSize > 0)
{
- for (unsigned int i = 0; i < type.iLifetimesSize; ++i)
- {
- const int value{type.lifetimes[i].iValue};
- const char* desc{type.lifetimes[i].strDescription};
- std::string strDescr{desc ? desc : ""};
- if (strDescr.empty())
- {
- // No description given by addon. Create one from value.
- strDescr = std::to_string(value);
- }
- m_lifetimeValues.emplace_back(strDescr, value);
- }
-
- m_iLifetimeDefault = type.iLifetimesDefault;
+ m_lifetimeValues = {type.lifetimes, type.iLifetimesSize, type.iLifetimesDefault};
}
else if (SupportsLifetime())
{
// No values given by addon, but lifetime supported. Use default values 1..365
+ std::vector<SettingIntValue> values;
for (int i = 1; i < 366; ++i)
{
- m_lifetimeValues.emplace_back(StringUtils::Format(g_localizeStrings.Get(17999), i),
- i); // "{} days"
+ values.emplace_back(StringUtils::Format(g_localizeStrings.Get(17999), i),
+ i); // "{} days"
}
- m_iLifetimeDefault = DEFAULT_RECORDING_LIFETIME;
+ m_lifetimeValues = {values, DEFAULT_RECORDING_LIFETIME};
}
else
{
// No lifetime supported.
- m_iLifetimeDefault = DEFAULT_RECORDING_LIFETIME;
+ m_lifetimeValues = CPVRIntSettingValues{DEFAULT_RECORDING_LIFETIME};
}
}
-void CPVRTimerType::GetLifetimeValues(std::vector<std::pair<std::string, int>>& list) const
-{
- std::copy(m_lifetimeValues.cbegin(), m_lifetimeValues.cend(), std::back_inserter(list));
-}
-
void CPVRTimerType::InitMaxRecordingsValues(const PVR_TIMER_TYPE& type)
{
- if (type.iMaxRecordingsSize > 0)
- {
- for (unsigned int i = 0; i < type.iMaxRecordingsSize; ++i)
- {
- const int value{type.maxRecordings[i].iValue};
- const char* desc{type.maxRecordings[i].strDescription};
- std::string strDescr{desc ? desc : ""};
- if (strDescr.empty())
- {
- // No description given by addon. Create one from value.
- strDescr = std::to_string(value);
- }
- m_maxRecordingsValues.emplace_back(strDescr, value);
- }
-
- m_iMaxRecordingsDefault = type.iMaxRecordingsDefault;
- }
-}
-
-void CPVRTimerType::GetMaxRecordingsValues(std::vector<std::pair<std::string, int>>& list) const
-{
- std::copy(m_maxRecordingsValues.cbegin(), m_maxRecordingsValues.cend(), std::back_inserter(list));
+ m_maxRecordingsValues = {type.maxRecordings, type.iMaxRecordingsSize, type.iMaxRecordingsDefault};
}
void CPVRTimerType::InitPreventDuplicateEpisodesValues(const PVR_TIMER_TYPE& type)
{
if (type.iPreventDuplicateEpisodesSize > 0)
{
- for (unsigned int i = 0; i < type.iPreventDuplicateEpisodesSize; ++i)
- {
- const int value{type.preventDuplicateEpisodes[i].iValue};
- const char* desc{type.preventDuplicateEpisodes[i].strDescription};
- std::string strDescr{desc ? desc : ""};
- if (strDescr.empty())
- {
- // No description given by addon. Create one from value.
- strDescr = std::to_string(value);
- }
- m_preventDupEpisodesValues.emplace_back(strDescr, value);
- }
-
- m_iPreventDupEpisodesDefault = type.iPreventDuplicateEpisodesDefault;
+ m_preventDupEpisodesValues = {type.preventDuplicateEpisodes, type.iPreventDuplicateEpisodesSize,
+ type.iPreventDuplicateEpisodesDefault};
}
else if (SupportsRecordOnlyNewEpisodes())
{
// No values given by addon, but prevent duplicate episodes supported. Use default values 0..1
- m_preventDupEpisodesValues.emplace_back(g_localizeStrings.Get(815), 0); // "Record all episodes"
- m_preventDupEpisodesValues.emplace_back(g_localizeStrings.Get(816), 1); // "Record only new episodes"
- m_iPreventDupEpisodesDefault = DEFAULT_RECORDING_DUPLICATEHANDLING;
+ m_preventDupEpisodesValues = {
+ {{g_localizeStrings.Get(815) /* "Record all episodes" */, 0},
+ {g_localizeStrings.Get(816) /* "Record only new episodes" */, 1}},
+ DEFAULT_RECORDING_DUPLICATEHANDLING};
}
else
{
// No prevent duplicate episodes supported.
- m_iPreventDupEpisodesDefault = DEFAULT_RECORDING_DUPLICATEHANDLING;
+ m_preventDupEpisodesValues = CPVRIntSettingValues{DEFAULT_RECORDING_DUPLICATEHANDLING};
}
}
-void CPVRTimerType::GetPreventDuplicateEpisodesValues(std::vector<std::pair<std::string, int>>& list) const
-{
- std::copy(m_preventDupEpisodesValues.cbegin(), m_preventDupEpisodesValues.cend(),
- std::back_inserter(list));
-}
-
void CPVRTimerType::InitRecordingGroupValues(const PVR_TIMER_TYPE& type)
{
- if (type.iRecordingGroupSize > 0)
- {
- for (unsigned int i = 0; i < type.iRecordingGroupSize; ++i)
- {
- const int value{type.recordingGroup[i].iValue};
- const char* desc{type.recordingGroup[i].strDescription};
- std::string strDescr{desc ? desc : ""};
- if (strDescr.empty())
- {
- // No description given by addon. Create one from value.
- strDescr = StringUtils::Format("{} {}",
- g_localizeStrings.Get(811), // Recording group
- value);
- }
- m_recordingGroupValues.emplace_back(strDescr, value);
- }
-
- m_iRecordingGroupDefault = type.iRecordingGroupDefault;
- }
+ m_recordingGroupValues = {type.recordingGroup, type.iRecordingGroupSize,
+ type.iRecordingGroupDefault, 811 /* Recording group */};
}
-void CPVRTimerType::GetRecordingGroupValues(std::vector< std::pair<std::string, int>>& list) const
+void CPVRTimerType::InitCustomSettingDefinitions(const PVR_TIMER_TYPE& type)
{
- std::copy(m_recordingGroupValues.cbegin(), m_recordingGroupValues.cend(),
- std::back_inserter(list));
+ m_customSettingDefs = CPVRTimerSettingDefinition::CreateSettingDefinitionsList(
+ m_iClientId, m_iTypeId, type.customSettingDefs, type.iCustomSettingDefsSize);
}
diff --git a/xbmc/pvr/timers/PVRTimerType.h b/xbmc/pvr/timers/PVRTimerType.h
index a17cf5f402..717576a774 100644
--- a/xbmc/pvr/timers/PVRTimerType.h
+++ b/xbmc/pvr/timers/PVRTimerType.h
@@ -9,6 +9,8 @@
#pragma once
#include "addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_timers.h"
+#include "pvr/PVRConstants.h" // PVR_CLIENT_INVALID_UID
+#include "pvr/settings/PVRIntSettingValues.h"
#include <memory>
#include <string>
@@ -17,402 +19,485 @@
struct PVR_TIMER_TYPE;
+namespace ADDON
+{
+class CAddonVersion;
+}
+
namespace PVR
{
- class CPVRClient;
+class CPVRClient;
+class CPVRTimerSettingDefinition;
- static const int DEFAULT_RECORDING_PRIORITY = 50;
- static const int DEFAULT_RECORDING_LIFETIME = 99; // days
- static const unsigned int DEFAULT_RECORDING_DUPLICATEHANDLING = 0;
+static const int DEFAULT_RECORDING_PRIORITY = 50;
+static const int DEFAULT_RECORDING_LIFETIME = 99; // days
+static const unsigned int DEFAULT_RECORDING_DUPLICATEHANDLING = 0;
- class CPVRTimerType
+class CPVRTimerType
+{
+public:
+ /*!
+ * @brief Return a list with all known timer types.
+ * @return A list of timer types or an empty list if no types available.
+ */
+ static const std::vector<std::shared_ptr<CPVRTimerType>> GetAllTypes();
+
+ /*!
+ * @brief Return the first available timer type from given client.
+ * @param client the PVR client.
+ * @return A timer type or NULL if none available.
+ */
+ static const std::shared_ptr<CPVRTimerType> GetFirstAvailableType(
+ const std::shared_ptr<const CPVRClient>& client);
+
+ /*!
+ * @brief Create a timer type from given timer type id and client id.
+ * @param iTimerType the timer type id.
+ * @param iClientId the PVR client id.
+ * @return A timer type instance.
+ */
+ static std::shared_ptr<CPVRTimerType> CreateFromIds(unsigned int iTypeId, int iClientId);
+
+ /*!
+ * @brief Create a timer type from given timer type attributes and client id.
+ * @param iMustHaveAttr a combination of PVR_TIMER_TYPE_* attributes the type to create must have.
+ * @param iMustNotHaveAttr a combination of PVR_TIMER_TYPE_* attributes the type to create must not have.
+ * @param iClientId the PVR client id.
+ * @return A timer type instance.
+ */
+ static std::shared_ptr<CPVRTimerType> CreateFromAttributes(uint64_t iMustHaveAttr,
+ uint64_t iMustNotHaveAttr,
+ int iClientId);
+
+ CPVRTimerType();
+ CPVRTimerType(const PVR_TIMER_TYPE& type,
+ int iClientId,
+ const ADDON::CAddonVersion& addonApiVersion);
+ CPVRTimerType(unsigned int iTypeId, uint64_t iAttributes, const std::string& strDescription = "");
+
+ virtual ~CPVRTimerType();
+
+ CPVRTimerType(const CPVRTimerType& type) = delete;
+ CPVRTimerType& operator=(const CPVRTimerType& orig) = delete;
+
+ bool operator==(const CPVRTimerType& right) const;
+ bool operator!=(const CPVRTimerType& right) const;
+
+ /*!
+ * @brief Update the data of this instance with the data given by another type instance.
+ * @param type The instance containing the updated data.
+ */
+ void Update(const CPVRTimerType& type);
+
+ /*!
+ * @brief Get the PVR client id for this type.
+ * @return The PVR client id.
+ */
+ int GetClientId() const { return m_iClientId; }
+
+ /*!
+ * @brief Get the numeric type id of this type.
+ * @return The type id.
+ */
+ unsigned int GetTypeId() const { return m_iTypeId; }
+
+ /*!
+ * @brief Get the plain text (UI) description of this type.
+ * @return The description.
+ */
+ const std::string& GetDescription() const { return m_strDescription; }
+
+ /*!
+ * @brief Get the attributes of this type.
+ * @return The attributes.
+ */
+ uint64_t GetAttributes() const { return m_iAttributes; }
+
+ /*!
+ * @brief Check whether this type is for timer rules or one time timers.
+ * @return True if type represents a timer rule, false otherwise.
+ */
+ bool IsTimerRule() const { return (m_iAttributes & PVR_TIMER_TYPE_IS_REPEATING) > 0; }
+
+ /*!
+ * @brief Check whether this type is for reminder timers or recording timers.
+ * @return True if type represents a reminder timer, false otherwise.
+ */
+ bool IsReminder() const { return (m_iAttributes & PVR_TIMER_TYPE_IS_REMINDER) > 0; }
+
+ /*!
+ * @brief Check whether this type is for timer rules or one time timers.
+ * @return True if type represents a one time timer, false otherwise.
+ */
+ bool IsOnetime() const { return !IsTimerRule(); }
+
+ /*!
+ * @brief Check whether this type is for epg-based or manual timers.
+ * @return True if manual, false otherwise.
+ */
+ bool IsManual() const { return (m_iAttributes & PVR_TIMER_TYPE_IS_MANUAL) > 0; }
+
+ /*!
+ * @brief Check whether this type is for epg-based or manual timers.
+ * @return True if epg-based, false otherwise.
+ */
+ bool IsEpgBased() const { return !IsManual(); }
+
+ /*!
+ * @brief Check whether this type is for epg-based timer rules.
+ * @return True if epg-based timer rule, false otherwise.
+ */
+ bool IsEpgBasedTimerRule() const { return IsEpgBased() && IsTimerRule(); }
+
+ /*!
+ * @brief Check whether this type is for one time epg-based timers.
+ * @return True if one time epg-based, false otherwise.
+ */
+ bool IsEpgBasedOnetime() const { return IsEpgBased() && IsOnetime(); }
+
+ /*!
+ * @brief Check whether this type is for manual timer rules.
+ * @return True if manual timer rule, false otherwise.
+ */
+ bool IsManualTimerRule() const { return IsManual() && IsTimerRule(); }
+
+ /*!
+ * @brief Check whether this type is for one time manual timers.
+ * @return True if one time manual, false otherwise.
+ */
+ bool IsManualOnetime() const { return IsManual() && IsOnetime(); }
+
+ /*!
+ * @brief Check whether this type is readonly (must not be modified after initial creation).
+ * @return True if readonly, false otherwise.
+ */
+ bool IsReadOnly() const { return (m_iAttributes & PVR_TIMER_TYPE_IS_READONLY) > 0; }
+
+ /*!
+ * @brief Check whether this type allows deletion.
+ * @return True if type allows deletion, false otherwise.
+ */
+ bool AllowsDelete() const { return !IsReadOnly() || SupportsReadOnlyDelete(); }
+
+ /*!
+ * @brief Check whether this type forbids creation of new timers of this type.
+ * @return True if new instances are forbidden, false otherwise.
+ */
+ bool ForbidsNewInstances() const
{
- public:
- /*!
- * @brief Return a list with all known timer types.
- * @return A list of timer types or an empty list if no types available.
- */
- static const std::vector<std::shared_ptr<CPVRTimerType>> GetAllTypes();
-
- /*!
- * @brief Return the first available timer type from given client.
- * @param client the PVR client.
- * @return A timer type or NULL if none available.
- */
- static const std::shared_ptr<CPVRTimerType> GetFirstAvailableType(
- const std::shared_ptr<const CPVRClient>& client);
-
- /*!
- * @brief Create a timer type from given timer type id and client id.
- * @param iTimerType the timer type id.
- * @param iClientId the PVR client id.
- * @return A timer type instance.
- */
- static std::shared_ptr<CPVRTimerType> CreateFromIds(unsigned int iTypeId, int iClientId);
-
- /*!
- * @brief Create a timer type from given timer type attributes and client id.
- * @param iMustHaveAttr a combination of PVR_TIMER_TYPE_* attributes the type to create must have.
- * @param iMustNotHaveAttr a combination of PVR_TIMER_TYPE_* attributes the type to create must not have.
- * @param iClientId the PVR client id.
- * @return A timer type instance.
- */
- static std::shared_ptr<CPVRTimerType> CreateFromAttributes(uint64_t iMustHaveAttr,
- uint64_t iMustNotHaveAttr,
- int iClientId);
-
- CPVRTimerType();
- CPVRTimerType(const PVR_TIMER_TYPE& type, int iClientId);
- CPVRTimerType(unsigned int iTypeId,
- uint64_t iAttributes,
- const std::string& strDescription = "");
-
- virtual ~CPVRTimerType();
-
- CPVRTimerType(const CPVRTimerType& type) = delete;
- CPVRTimerType& operator=(const CPVRTimerType& orig) = delete;
-
- bool operator ==(const CPVRTimerType& right) const;
- bool operator !=(const CPVRTimerType& right) const;
-
- /*!
- * @brief Update the data of this instance with the data given by another type instance.
- * @param type The instance containing the updated data.
- */
- void Update(const CPVRTimerType& type);
-
- /*!
- * @brief Get the PVR client id for this type.
- * @return The PVR client id.
- */
- int GetClientId() const { return m_iClientId; }
-
- /*!
- * @brief Get the numeric type id of this type.
- * @return The type id.
- */
- unsigned int GetTypeId() const { return m_iTypeId; }
-
- /*!
- * @brief Get the plain text (UI) description of this type.
- * @return The description.
- */
- const std::string& GetDescription() const { return m_strDescription; }
-
- /*!
- * @brief Get the attributes of this type.
- * @return The attributes.
- */
- uint64_t GetAttributes() const { return m_iAttributes; }
-
- /*!
- * @brief Check whether this type is for timer rules or one time timers.
- * @return True if type represents a timer rule, false otherwise.
- */
- bool IsTimerRule() const { return (m_iAttributes & PVR_TIMER_TYPE_IS_REPEATING) > 0; }
-
- /*!
- * @brief Check whether this type is for reminder timers or recording timers.
- * @return True if type represents a reminder timer, false otherwise.
- */
- bool IsReminder() const { return (m_iAttributes & PVR_TIMER_TYPE_IS_REMINDER) > 0; }
-
- /*!
- * @brief Check whether this type is for timer rules or one time timers.
- * @return True if type represents a one time timer, false otherwise.
- */
- bool IsOnetime() const { return !IsTimerRule(); }
-
- /*!
- * @brief Check whether this type is for epg-based or manual timers.
- * @return True if manual, false otherwise.
- */
- bool IsManual() const { return (m_iAttributes & PVR_TIMER_TYPE_IS_MANUAL) > 0; }
-
- /*!
- * @brief Check whether this type is for epg-based or manual timers.
- * @return True if epg-based, false otherwise.
- */
- bool IsEpgBased() const { return !IsManual(); }
-
- /*!
- * @brief Check whether this type is for epg-based timer rules.
- * @return True if epg-based timer rule, false otherwise.
- */
- bool IsEpgBasedTimerRule() const { return IsEpgBased() && IsTimerRule(); }
-
- /*!
- * @brief Check whether this type is for one time epg-based timers.
- * @return True if one time epg-based, false otherwise.
- */
- bool IsEpgBasedOnetime() const { return IsEpgBased() && IsOnetime(); }
-
- /*!
- * @brief Check whether this type is for manual timer rules.
- * @return True if manual timer rule, false otherwise.
- */
- bool IsManualTimerRule() const { return IsManual() && IsTimerRule(); }
-
- /*!
- * @brief Check whether this type is for one time manual timers.
- * @return True if one time manual, false otherwise.
- */
- bool IsManualOnetime() const { return IsManual() && IsOnetime(); }
-
- /*!
- * @brief Check whether this type is readonly (must not be modified after initial creation).
- * @return True if readonly, false otherwise.
- */
- bool IsReadOnly() const { return (m_iAttributes & PVR_TIMER_TYPE_IS_READONLY) > 0; }
-
- /*!
- * @brief Check whether this type allows deletion.
- * @return True if type allows deletion, false otherwise.
- */
- bool AllowsDelete() const { return !IsReadOnly() || SupportsReadOnlyDelete(); }
-
- /*!
- * @brief Check whether this type forbids creation of new timers of this type.
- * @return True if new instances are forbidden, false otherwise.
- */
- bool ForbidsNewInstances() const { return (m_iAttributes & PVR_TIMER_TYPE_FORBIDS_NEW_INSTANCES) > 0; }
-
- /*!
- * @brief Check whether this timer type is forbidden when epg tag info is present.
- * @return True if new instances are forbidden when epg info is present, false otherwise.
- */
- bool ForbidsEpgTagOnCreate() const { return (m_iAttributes & PVR_TIMER_TYPE_FORBIDS_EPG_TAG_ON_CREATE) > 0; }
-
- /*!
- * @brief Check whether this timer type requires epg tag info to be present.
- * @return True if new instances require EPG info, false otherwise.
- */
- bool RequiresEpgTagOnCreate() const { return (m_iAttributes & (PVR_TIMER_TYPE_REQUIRES_EPG_TAG_ON_CREATE |
- PVR_TIMER_TYPE_REQUIRES_EPG_SERIES_ON_CREATE |
- PVR_TIMER_TYPE_REQUIRES_EPG_SERIESLINK_ON_CREATE)) > 0; }
-
- /*!
- * @brief Check whether this timer type requires epg tag info including series attributes to be present.
- * @return True if new instances require an EPG tag with series attributes, false otherwise.
- */
- bool RequiresEpgSeriesOnCreate() const { return (m_iAttributes & PVR_TIMER_TYPE_REQUIRES_EPG_SERIES_ON_CREATE) > 0; }
-
- /*!
- * @brief Check whether this timer type requires epg tag info including a series link to be present.
- * @return True if new instances require an EPG tag with a series link, false otherwise.
- */
- bool RequiresEpgSeriesLinkOnCreate() const { return (m_iAttributes & PVR_TIMER_TYPE_REQUIRES_EPG_SERIESLINK_ON_CREATE) > 0; }
-
- /*!
- * @brief Check whether this type supports the "enabling/disabling" of timers of its type.
- * @return True if "enabling/disabling" feature is supported, false otherwise.
- */
- bool SupportsEnableDisable() const { return (m_iAttributes & PVR_TIMER_TYPE_SUPPORTS_ENABLE_DISABLE) > 0; }
-
- /*!
- * @brief Check whether this type supports channels.
- * @return True if channels are supported, false otherwise.
- */
- bool SupportsChannels() const { return (m_iAttributes & PVR_TIMER_TYPE_SUPPORTS_CHANNELS) > 0; }
-
- /*!
- * @brief Check whether this type supports start time.
- * @return True if start time values are supported, false otherwise.
- */
- bool SupportsStartTime() const { return (m_iAttributes & PVR_TIMER_TYPE_SUPPORTS_START_TIME) > 0; }
-
- /*!
- * @brief Check whether this type supports end time.
- * @return True if end time values are supported, false otherwise.
- */
- bool SupportsEndTime() const { return (m_iAttributes & PVR_TIMER_TYPE_SUPPORTS_END_TIME) > 0; }
- /*!
- * @brief Check whether this type supports start any time.
- * @return True if start any time is supported, false otherwise.
- */
- bool SupportsStartAnyTime() const { return (m_iAttributes & PVR_TIMER_TYPE_SUPPORTS_START_ANYTIME) > 0; }
-
- /*!
- * @brief Check whether this type supports end any time.
- * @return True if end any time is supported, false otherwise.
- */
- bool SupportsEndAnyTime() const { return (m_iAttributes & PVR_TIMER_TYPE_SUPPORTS_END_ANYTIME) > 0; }
-
- /*!
- * @brief Check whether this type supports matching a search string against epg episode title.
- * @return True if title matching is supported, false otherwise.
- */
- bool SupportsEpgTitleMatch() const { return (m_iAttributes & (PVR_TIMER_TYPE_SUPPORTS_TITLE_EPG_MATCH | PVR_TIMER_TYPE_SUPPORTS_FULLTEXT_EPG_MATCH)) > 0; }
-
- /*!
- * @brief Check whether this type supports matching a search string against extended (fulltext) epg data. This
- includes title matching.
- * @return True if fulltext matching is supported, false otherwise.
- */
- bool SupportsEpgFulltextMatch() const { return (m_iAttributes & PVR_TIMER_TYPE_SUPPORTS_FULLTEXT_EPG_MATCH) > 0; }
-
- /*!
- * @brief Check whether this type supports a first day the timer is active.
- * @return True if first day is supported, false otherwise.
- */
- bool SupportsFirstDay() const { return (m_iAttributes & PVR_TIMER_TYPE_SUPPORTS_FIRST_DAY) > 0; }
-
- /*!
- * @brief Check whether this type supports weekdays for timer schedules.
- * @return True if weekdays are supported, false otherwise.
- */
- bool SupportsWeekdays() const { return (m_iAttributes & PVR_TIMER_TYPE_SUPPORTS_WEEKDAYS) > 0; }
-
- /*!
- * @brief Check whether this type supports the "record only new episodes" feature.
- * @return True if the "record only new episodes" feature is supported, false otherwise.
- */
- bool SupportsRecordOnlyNewEpisodes() const { return (m_iAttributes & PVR_TIMER_TYPE_SUPPORTS_RECORD_ONLY_NEW_EPISODES) > 0; }
-
- /*!
- * @brief Check whether this type supports pre record time.
- * @return True if pre record time is supported, false otherwise.
- */
- bool SupportsStartMargin() const
- {
- return (m_iAttributes & PVR_TIMER_TYPE_SUPPORTS_START_MARGIN) > 0 ||
- (m_iAttributes & PVR_TIMER_TYPE_SUPPORTS_START_END_MARGIN) > 0;
- }
-
- /*!
- * @brief Check whether this type supports post record time.
- * @return True if post record time is supported, false otherwise.
- */
- bool SupportsEndMargin() const
- {
- return (m_iAttributes & PVR_TIMER_TYPE_SUPPORTS_END_MARGIN) > 0 ||
- (m_iAttributes & PVR_TIMER_TYPE_SUPPORTS_START_END_MARGIN) > 0;
- }
-
- /*!
- * @brief Check whether this type supports recording priorities.
- * @return True if recording priority is supported, false otherwise.
- */
- bool SupportsPriority() const { return (m_iAttributes & PVR_TIMER_TYPE_SUPPORTS_PRIORITY) > 0; }
-
- /*!
- * @brief Check whether this type supports lifetime for recordings.
- * @return True if recording lifetime is supported, false otherwise.
- */
- bool SupportsLifetime() const { return (m_iAttributes & PVR_TIMER_TYPE_SUPPORTS_LIFETIME) > 0; }
-
- /*!
- * @brief Check whether this type supports MaxRecordings for recordings.
- * @return True if MaxRecordings is supported, false otherwise.
- */
- bool SupportsMaxRecordings() const { return (m_iAttributes & PVR_TIMER_TYPE_SUPPORTS_MAX_RECORDINGS) > 0; }
-
- /*!
- * @brief Check whether this type supports user specified recording folders.
- * @return True if recording folders are supported, false otherwise.
- */
- bool SupportsRecordingFolders() const { return (m_iAttributes & PVR_TIMER_TYPE_SUPPORTS_RECORDING_FOLDERS) > 0; }
-
- /*!
- * @brief Check whether this type supports recording groups.
- * @return True if recording groups are supported, false otherwise.
- */
- bool SupportsRecordingGroup() const { return (m_iAttributes & PVR_TIMER_TYPE_SUPPORTS_RECORDING_GROUP) > 0; }
-
- /*!
- * @brief Check whether this type supports 'any channel', for example for defining a timer rule that should match any channel instead of a particular channel.
- * @return True if any channel is supported, false otherwise.
- */
- bool SupportsAnyChannel() const { return (m_iAttributes & PVR_TIMER_TYPE_SUPPORTS_ANY_CHANNEL) > 0; }
-
- /*!
- * @brief Check whether this type supports deletion of an otherwise read-only timer.
- * @return True if read-only deletion is supported, false otherwise.
- */
- bool SupportsReadOnlyDelete() const { return (m_iAttributes & PVR_TIMER_TYPE_SUPPORTS_READONLY_DELETE) > 0; }
-
- /*!
- * @brief Obtain a list with all possible values for the priority attribute.
- * @param list out, the list with the values or an empty list, if priority is not supported by this type.
- */
- void GetPriorityValues(std::vector<std::pair<std::string, int>>& list) const;
-
- /*!
- * @brief Obtain the default value for the priority attribute.
- * @return the default value.
- */
- int GetPriorityDefault() const { return m_iPriorityDefault; }
-
- /*!
- * @brief Obtain a list with all possible values for the lifetime attribute.
- * @param list out, the list with the values or an empty list, if lifetime is not supported by this type.
- */
- void GetLifetimeValues(std::vector<std::pair<std::string, int>>& list) const;
-
- /*!
- * @brief Obtain the default value for the lifetime attribute.
- * @return the default value.
- */
- int GetLifetimeDefault() const { return m_iLifetimeDefault; }
-
- /*!
- * @brief Obtain a list with all possible values for the MaxRecordings attribute.
- * @param list out, the list with the values or an empty list, if MaxRecordings is not supported by this type.
- */
- void GetMaxRecordingsValues(std::vector<std::pair<std::string, int>>& list) const;
-
- /*!
- * @brief Obtain the default value for the MaxRecordings attribute.
- * @return the default value.
- */
- int GetMaxRecordingsDefault() const { return m_iMaxRecordingsDefault; }
-
- /*!
- * @brief Obtain a list with all possible values for the duplicate episode prevention attribute.
- * @param list out, the list with the values or an empty list, if duplicate episode prevention is not supported by this type.
- */
- void GetPreventDuplicateEpisodesValues(std::vector<std::pair<std::string, int>>& list) const;
-
- /*!
- * @brief Obtain the default value for the duplicate episode prevention attribute.
- * @return the default value.
- */
- int GetPreventDuplicateEpisodesDefault() const { return m_iPreventDupEpisodesDefault; }
-
- /*!
- * @brief Obtain a list with all possible values for the recording group attribute.
- * @param list out, the list with the values or an empty list, if recording group is not supported by this type.
- */
- void GetRecordingGroupValues(std::vector<std::pair<std::string, int>>& list) const;
-
- /*!
- * @brief Obtain the default value for the Recording Group attribute.
- * @return the default value.
- */
- int GetRecordingGroupDefault() const { return m_iRecordingGroupDefault; }
-
- private:
- void InitDescription();
- void InitAttributeValues(const PVR_TIMER_TYPE& type);
- void InitPriorityValues(const PVR_TIMER_TYPE& type);
- void InitLifetimeValues(const PVR_TIMER_TYPE& type);
- void InitMaxRecordingsValues(const PVR_TIMER_TYPE& type);
- void InitPreventDuplicateEpisodesValues(const PVR_TIMER_TYPE& type);
- void InitRecordingGroupValues(const PVR_TIMER_TYPE& type);
-
- int m_iClientId = -1;
- unsigned int m_iTypeId;
- uint64_t m_iAttributes;
- std::string m_strDescription;
- std::vector< std::pair<std::string, int> > m_priorityValues;
- int m_iPriorityDefault = DEFAULT_RECORDING_PRIORITY;
- std::vector< std::pair<std::string, int> > m_lifetimeValues;
- int m_iLifetimeDefault = DEFAULT_RECORDING_LIFETIME;
- std::vector< std::pair<std::string, int> > m_maxRecordingsValues;
- int m_iMaxRecordingsDefault = 0;
- std::vector< std::pair<std::string, int> > m_preventDupEpisodesValues;
- unsigned int m_iPreventDupEpisodesDefault = DEFAULT_RECORDING_DUPLICATEHANDLING;
- std::vector< std::pair<std::string, int> > m_recordingGroupValues;
- unsigned int m_iRecordingGroupDefault = 0;
- };
-}
+ return (m_iAttributes & PVR_TIMER_TYPE_FORBIDS_NEW_INSTANCES) > 0;
+ }
+
+ /*!
+ * @brief Check whether this timer type is forbidden when epg tag info is present.
+ * @return True if new instances are forbidden when epg info is present, false otherwise.
+ */
+ bool ForbidsEpgTagOnCreate() const
+ {
+ return (m_iAttributes & PVR_TIMER_TYPE_FORBIDS_EPG_TAG_ON_CREATE) > 0;
+ }
+
+ /*!
+ * @brief Check whether this timer type requires epg tag info to be present.
+ * @return True if new instances require EPG info, false otherwise.
+ */
+ bool RequiresEpgTagOnCreate() const
+ {
+ return (m_iAttributes & (PVR_TIMER_TYPE_REQUIRES_EPG_TAG_ON_CREATE |
+ PVR_TIMER_TYPE_REQUIRES_EPG_SERIES_ON_CREATE |
+ PVR_TIMER_TYPE_REQUIRES_EPG_SERIESLINK_ON_CREATE)) > 0;
+ }
+
+ /*!
+ * @brief Check whether this timer type requires epg tag info including series attributes to be present.
+ * @return True if new instances require an EPG tag with series attributes, false otherwise.
+ */
+ bool RequiresEpgSeriesOnCreate() const
+ {
+ return (m_iAttributes & PVR_TIMER_TYPE_REQUIRES_EPG_SERIES_ON_CREATE) > 0;
+ }
+
+ /*!
+ * @brief Check whether this timer type requires epg tag info including a series link to be present.
+ * @return True if new instances require an EPG tag with a series link, false otherwise.
+ */
+ bool RequiresEpgSeriesLinkOnCreate() const
+ {
+ return (m_iAttributes & PVR_TIMER_TYPE_REQUIRES_EPG_SERIESLINK_ON_CREATE) > 0;
+ }
+
+ /*!
+ * @brief Check whether this type supports the "enabling/disabling" of timers of its type.
+ * @return True if "enabling/disabling" feature is supported, false otherwise.
+ */
+ bool SupportsEnableDisable() const
+ {
+ return (m_iAttributes & PVR_TIMER_TYPE_SUPPORTS_ENABLE_DISABLE) > 0;
+ }
+
+ /*!
+ * @brief Check whether this type supports channels.
+ * @return True if channels are supported, false otherwise.
+ */
+ bool SupportsChannels() const { return (m_iAttributes & PVR_TIMER_TYPE_SUPPORTS_CHANNELS) > 0; }
+
+ /*!
+ * @brief Check whether this type supports start time.
+ * @return True if start time values are supported, false otherwise.
+ */
+ bool SupportsStartTime() const
+ {
+ return (m_iAttributes & PVR_TIMER_TYPE_SUPPORTS_START_TIME) > 0;
+ }
+
+ /*!
+ * @brief Check whether this type supports end time.
+ * @return True if end time values are supported, false otherwise.
+ */
+ bool SupportsEndTime() const { return (m_iAttributes & PVR_TIMER_TYPE_SUPPORTS_END_TIME) > 0; }
+ /*!
+ * @brief Check whether this type supports start any time.
+ * @return True if start any time is supported, false otherwise.
+ */
+ bool SupportsStartAnyTime() const
+ {
+ return (m_iAttributes & PVR_TIMER_TYPE_SUPPORTS_START_ANYTIME) > 0;
+ }
+
+ /*!
+ * @brief Check whether this type supports end any time.
+ * @return True if end any time is supported, false otherwise.
+ */
+ bool SupportsEndAnyTime() const
+ {
+ return (m_iAttributes & PVR_TIMER_TYPE_SUPPORTS_END_ANYTIME) > 0;
+ }
+
+ /*!
+ * @brief Check whether this type supports matching a search string against epg episode title.
+ * @return True if title matching is supported, false otherwise.
+ */
+ bool SupportsEpgTitleMatch() const
+ {
+ return (m_iAttributes & (PVR_TIMER_TYPE_SUPPORTS_TITLE_EPG_MATCH |
+ PVR_TIMER_TYPE_SUPPORTS_FULLTEXT_EPG_MATCH)) > 0;
+ }
+
+ /*!
+ * @brief Check whether this type supports matching a search string against extended (fulltext) epg data. This
+ includes title matching.
+ * @return True if fulltext matching is supported, false otherwise.
+ */
+ bool SupportsEpgFulltextMatch() const
+ {
+ return (m_iAttributes & PVR_TIMER_TYPE_SUPPORTS_FULLTEXT_EPG_MATCH) > 0;
+ }
+
+ /*!
+ * @brief Check whether this type supports a first day the timer is active.
+ * @return True if first day is supported, false otherwise.
+ */
+ bool SupportsFirstDay() const { return (m_iAttributes & PVR_TIMER_TYPE_SUPPORTS_FIRST_DAY) > 0; }
+
+ /*!
+ * @brief Check whether this type supports weekdays for timer schedules.
+ * @return True if weekdays are supported, false otherwise.
+ */
+ bool SupportsWeekdays() const { return (m_iAttributes & PVR_TIMER_TYPE_SUPPORTS_WEEKDAYS) > 0; }
+
+ /*!
+ * @brief Check whether this type supports the "record only new episodes" feature.
+ * @return True if the "record only new episodes" feature is supported, false otherwise.
+ */
+ bool SupportsRecordOnlyNewEpisodes() const
+ {
+ return (m_iAttributes & PVR_TIMER_TYPE_SUPPORTS_RECORD_ONLY_NEW_EPISODES) > 0;
+ }
+
+ /*!
+ * @brief Check whether this type supports pre record time.
+ * @return True if pre record time is supported, false otherwise.
+ */
+ bool SupportsStartMargin() const
+ {
+ return (m_iAttributes & PVR_TIMER_TYPE_SUPPORTS_START_MARGIN) > 0 ||
+ (m_iAttributes & PVR_TIMER_TYPE_SUPPORTS_START_END_MARGIN) > 0;
+ }
+
+ /*!
+ * @brief Check whether this type supports post record time.
+ * @return True if post record time is supported, false otherwise.
+ */
+ bool SupportsEndMargin() const
+ {
+ return (m_iAttributes & PVR_TIMER_TYPE_SUPPORTS_END_MARGIN) > 0 ||
+ (m_iAttributes & PVR_TIMER_TYPE_SUPPORTS_START_END_MARGIN) > 0;
+ }
+
+ /*!
+ * @brief Check whether this type supports recording priorities.
+ * @return True if recording priority is supported, false otherwise.
+ */
+ bool SupportsPriority() const { return (m_iAttributes & PVR_TIMER_TYPE_SUPPORTS_PRIORITY) > 0; }
+
+ /*!
+ * @brief Check whether this type supports lifetime for recordings.
+ * @return True if recording lifetime is supported, false otherwise.
+ */
+ bool SupportsLifetime() const { return (m_iAttributes & PVR_TIMER_TYPE_SUPPORTS_LIFETIME) > 0; }
+
+ /*!
+ * @brief Check whether this type supports MaxRecordings for recordings.
+ * @return True if MaxRecordings is supported, false otherwise.
+ */
+ bool SupportsMaxRecordings() const
+ {
+ return (m_iAttributes & PVR_TIMER_TYPE_SUPPORTS_MAX_RECORDINGS) > 0;
+ }
+
+ /*!
+ * @brief Check whether this type supports user specified recording folders.
+ * @return True if recording folders are supported, false otherwise.
+ */
+ bool SupportsRecordingFolders() const
+ {
+ return (m_iAttributes & PVR_TIMER_TYPE_SUPPORTS_RECORDING_FOLDERS) > 0;
+ }
+
+ /*!
+ * @brief Check whether this type supports recording groups.
+ * @return True if recording groups are supported, false otherwise.
+ */
+ bool SupportsRecordingGroup() const
+ {
+ return (m_iAttributes & PVR_TIMER_TYPE_SUPPORTS_RECORDING_GROUP) > 0;
+ }
+
+ /*!
+ * @brief Check whether this type supports 'any channel', for example for defining a timer rule that should match any channel instead of a particular channel.
+ * @return True if any channel is supported, false otherwise.
+ */
+ bool SupportsAnyChannel() const
+ {
+ return (m_iAttributes & PVR_TIMER_TYPE_SUPPORTS_ANY_CHANNEL) > 0;
+ }
+
+ /*!
+ * @brief Check whether this type supports deletion of an otherwise read-only timer.
+ * @return True if read-only deletion is supported, false otherwise.
+ */
+ bool SupportsReadOnlyDelete() const
+ {
+ return (m_iAttributes & PVR_TIMER_TYPE_SUPPORTS_READONLY_DELETE) > 0;
+ }
+
+ /*!
+ * @brief Obtain a list with all possible values for the priority attribute.
+ * @return the list with the values or an empty list, if priority is not supported by this type.
+ */
+ const std::vector<SettingIntValue>& GetPriorityValues() const
+ {
+ return m_priorityValues.GetValues();
+ }
+
+ /*!
+ * @brief Obtain the default value for the priority attribute.
+ * @return the default value.
+ */
+ int GetPriorityDefault() const { return m_priorityValues.GetDefaultValue(); }
+
+ /*!
+ * @brief Obtain a list with all possible values for the lifetime attribute.
+ * @return the list with the values or an empty list, if lifetime is not supported by this type.
+ */
+ const std::vector<SettingIntValue>& GetLifetimeValues() const
+ {
+ return m_lifetimeValues.GetValues();
+ }
+
+ /*!
+ * @brief Obtain the default value for the lifetime attribute.
+ * @return the default value.
+ */
+ int GetLifetimeDefault() const { return m_lifetimeValues.GetDefaultValue(); }
+
+ /*!
+ * @brief Obtain a list with all possible values for the MaxRecordings attribute.
+ * @return the list with the values or an empty list, if MaxRecordings is not supported by this type.
+ */
+ const std::vector<SettingIntValue>& GetMaxRecordingsValues() const
+ {
+ return m_maxRecordingsValues.GetValues();
+ }
+
+ /*!
+ * @brief Obtain the default value for the MaxRecordings attribute.
+ * @return the default value.
+ */
+ int GetMaxRecordingsDefault() const { return m_maxRecordingsValues.GetDefaultValue(); }
+
+ /*!
+ * @brief Obtain a list with all possible values for the duplicate episode prevention attribute.
+ * @return the list with the values or an empty list, if duplicate episode prevention is not supported by this type.
+ */
+ const std::vector<SettingIntValue>& GetPreventDuplicateEpisodesValues() const
+ {
+ return m_preventDupEpisodesValues.GetValues();
+ }
+
+ /*!
+ * @brief Obtain the default value for the duplicate episode prevention attribute.
+ * @return the default value.
+ */
+ int GetPreventDuplicateEpisodesDefault() const
+ {
+ return m_preventDupEpisodesValues.GetDefaultValue();
+ }
+
+ /*!
+ * @brief Obtain a list with all possible values for the recording group attribute.
+ * @return the list with the values or an empty list, if recording group is not supported by this type.
+ */
+ const std::vector<SettingIntValue>& GetRecordingGroupValues() const
+ {
+ return m_recordingGroupValues.GetValues();
+ }
+
+ /*!
+ * @brief Obtain the default value for the Recording Group attribute.
+ * @return the default value.
+ */
+ int GetRecordingGroupDefault() const { return m_recordingGroupValues.GetDefaultValue(); }
+
+ /*!
+ * @brief Get custom setting definitions for this type.
+ * @return The list of settings or an empty list if none present.
+ */
+ const std::vector<std::shared_ptr<const CPVRTimerSettingDefinition>>&
+ GetCustomSettingDefinitions() const
+ {
+ return m_customSettingDefs;
+ }
+
+private:
+ void InitDescription();
+ void InitAttributeValues(const PVR_TIMER_TYPE& type);
+ void InitPriorityValues(const PVR_TIMER_TYPE& type);
+ void InitLifetimeValues(const PVR_TIMER_TYPE& type);
+ void InitMaxRecordingsValues(const PVR_TIMER_TYPE& type);
+ void InitPreventDuplicateEpisodesValues(const PVR_TIMER_TYPE& type);
+ void InitRecordingGroupValues(const PVR_TIMER_TYPE& type);
+ void InitCustomSettingDefinitions(const PVR_TIMER_TYPE& type);
+
+ int m_iClientId = PVR_CLIENT_INVALID_UID;
+ unsigned int m_iTypeId;
+ uint64_t m_iAttributes;
+ std::string m_strDescription;
+ CPVRIntSettingValues m_priorityValues{DEFAULT_RECORDING_PRIORITY};
+ CPVRIntSettingValues m_lifetimeValues{DEFAULT_RECORDING_LIFETIME};
+ CPVRIntSettingValues m_maxRecordingsValues{0};
+ CPVRIntSettingValues m_preventDupEpisodesValues{DEFAULT_RECORDING_DUPLICATEHANDLING};
+ CPVRIntSettingValues m_recordingGroupValues{0};
+ std::vector<std::shared_ptr<const CPVRTimerSettingDefinition>> m_customSettingDefs;
+};
+} // namespace PVR
diff --git a/xbmc/pvr/timers/PVRTimers.cpp b/xbmc/pvr/timers/PVRTimers.cpp
index 8339da75b7..ccf719091a 100644
--- a/xbmc/pvr/timers/PVRTimers.cpp
+++ b/xbmc/pvr/timers/PVRTimers.cpp
@@ -9,6 +9,7 @@
#include "PVRTimers.h"
#include "ServiceBroker.h"
+#include "pvr/PVRConstants.h" // PVR_CLIENT_INVALID_UID
#include "pvr/PVRDatabase.h"
#include "pvr/PVREventLogJob.h"
#include "pvr/PVRManager.h"
@@ -1243,7 +1244,7 @@ std::shared_ptr<CPVRTimerInfoTag> CPVRTimers::GetTimerRule(
const auto it = std::find_if(tagsEntry.second.cbegin(), tagsEntry.second.cend(),
[iClientId, iParentClientIndex](const auto& timersEntry)
{
- return (timersEntry->ClientID() == PVR_ANY_CLIENT_ID ||
+ return (timersEntry->ClientID() == PVR_CLIENT_INVALID_UID ||
timersEntry->ClientID() == iClientId) &&
timersEntry->ClientIndex() == iParentClientIndex;
});
diff --git a/xbmc/pvr/timers/PVRTimersPath.cpp b/xbmc/pvr/timers/PVRTimersPath.cpp
index ea2265ca56..53602c1ec1 100644
--- a/xbmc/pvr/timers/PVRTimersPath.cpp
+++ b/xbmc/pvr/timers/PVRTimersPath.cpp
@@ -8,6 +8,7 @@
#include "PVRTimersPath.h"
+#include "pvr/PVRConstants.h" // PVR_CLIENT_INVALID_UID
#include "utils/StringUtils.h"
#include "utils/URIUtils.h"
@@ -69,7 +70,7 @@ bool CPVRTimersPath::Init(const std::string& strPath)
if (!m_bValid || m_bRoot)
{
- m_iClientId = -1;
+ m_iClientId = PVR_CLIENT_INVALID_UID;
m_iParentId = 0;
}
else
diff --git a/xbmc/pvr/timers/PVRTimersPath.h b/xbmc/pvr/timers/PVRTimersPath.h
index 01fd5f092f..18128fab96 100644
--- a/xbmc/pvr/timers/PVRTimersPath.h
+++ b/xbmc/pvr/timers/PVRTimersPath.h
@@ -8,6 +8,8 @@
#pragma once
+#include "pvr/PVRConstants.h" // PVR_CLIENT_INVALID_UID
+
#include <string>
namespace PVR
@@ -44,7 +46,7 @@ private:
bool m_bRoot = false;
bool m_bRadio = false;
bool m_bTimerRules = false;
- int m_iClientId = -1;
+ int m_iClientId = PVR_CLIENT_INVALID_UID;
int m_iParentId = 0;
};
} // namespace PVR
diff --git a/xbmc/pvr/windows/CMakeLists.txt b/xbmc/pvr/windows/CMakeLists.txt
index 9f5097ea72..7381b07edc 100644
--- a/xbmc/pvr/windows/CMakeLists.txt
+++ b/xbmc/pvr/windows/CMakeLists.txt
@@ -2,6 +2,7 @@ set(SOURCES GUIViewStatePVR.cpp
GUIWindowPVRBase.cpp
GUIWindowPVRChannels.cpp
GUIWindowPVRGuide.cpp
+ GUIWindowPVRProviders.cpp
GUIWindowPVRRecordings.cpp
GUIWindowPVRSearch.cpp
GUIWindowPVRTimers.cpp
@@ -12,6 +13,7 @@ set(HEADERS GUIViewStatePVR.h
GUIWindowPVRBase.h
GUIWindowPVRChannels.h
GUIWindowPVRGuide.h
+ GUIWindowPVRProviders.h
GUIWindowPVRRecordings.h
GUIWindowPVRSearch.h
GUIWindowPVRTimerRules.h
diff --git a/xbmc/pvr/windows/GUIViewStatePVR.cpp b/xbmc/pvr/windows/GUIViewStatePVR.cpp
index 874c98fd89..55eefd0f68 100644
--- a/xbmc/pvr/windows/GUIViewStatePVR.cpp
+++ b/xbmc/pvr/windows/GUIViewStatePVR.cpp
@@ -14,6 +14,7 @@
#include "pvr/PVRManager.h"
#include "pvr/addons/PVRClients.h"
#include "pvr/epg/EpgSearchPath.h"
+#include "pvr/providers/PVRProvidersPath.h"
#include "pvr/recordings/PVRRecordingsPath.h"
#include "pvr/timers/PVRTimersPath.h"
#include "settings/AdvancedSettings.h"
@@ -187,3 +188,37 @@ bool CGUIViewStateWindowPVRSearch::HideParentDirItems()
return (CGUIViewState::HideParentDirItems() ||
CPVREpgSearchPath(m_items.GetPath()).IsSearchRoot());
}
+
+CGUIViewStateWindowPVRProviders::CGUIViewStateWindowPVRProviders(const int windowId,
+ const CFileItemList& items)
+ : CGUIViewStatePVR(windowId, items)
+{
+ AddSortMethod(SortByLabel, 551, // "Name"
+ LABEL_MASKS("%L", "", "%L", "")); // Filename, empty | Foldername, empty
+
+ if (CPVRProvidersPath(m_items.GetPath()).IsProvidersRoot())
+ {
+ AddSortMethod(SortByProvider, 19348, // "Provider"
+ LABEL_MASKS("%L", "", "%L", "")); // Filename, empty | Foldername, empty
+
+ SetSortMethod(SortByProvider, SortOrderAscending);
+ }
+ else
+ {
+ SetSortMethod(SortByLabel, SortOrderAscending);
+ }
+
+ LoadViewState(m_items.GetPath(), m_windowId);
+}
+
+void CGUIViewStateWindowPVRProviders::SaveViewState()
+{
+ SaveViewToDb(m_items.GetPath(), m_windowId,
+ CViewStateSettings::GetInstance().Get("pvrproviders"));
+}
+
+bool CGUIViewStateWindowPVRProviders::HideParentDirItems()
+{
+ return (CGUIViewState::HideParentDirItems() ||
+ CPVRProvidersPath(m_items.GetPath()).IsProvidersRoot());
+}
diff --git a/xbmc/pvr/windows/GUIViewStatePVR.h b/xbmc/pvr/windows/GUIViewStatePVR.h
index ce39dd703b..09c51dff80 100644
--- a/xbmc/pvr/windows/GUIViewStatePVR.h
+++ b/xbmc/pvr/windows/GUIViewStatePVR.h
@@ -75,4 +75,14 @@ protected:
void SaveViewState() override;
bool HideParentDirItems() override;
};
+
+class CGUIViewStateWindowPVRProviders : public CGUIViewStatePVR
+{
+public:
+ CGUIViewStateWindowPVRProviders(const int windowId, const CFileItemList& items);
+
+protected:
+ void SaveViewState() override;
+ bool HideParentDirItems() override;
+};
} // namespace PVR
diff --git a/xbmc/pvr/windows/GUIWindowPVRBase.cpp b/xbmc/pvr/windows/GUIWindowPVRBase.cpp
index 2af8531134..a3dd76cfec 100644
--- a/xbmc/pvr/windows/GUIWindowPVRBase.cpp
+++ b/xbmc/pvr/windows/GUIWindowPVRBase.cpp
@@ -12,6 +12,7 @@
#include "FileItemList.h"
#include "GUIUserMessages.h"
#include "ServiceBroker.h"
+#include "URL.h"
#include "addons/AddonManager.h"
#include "addons/addoninfo/AddonType.h"
#include "dialogs/GUIDialogExtendedProgressBar.h"
@@ -561,6 +562,18 @@ void CGUIWindowPVRBase::SetChannelGroup(std::shared_ptr<CPVRChannelGroup> &&grou
}
}
+void CGUIWindowPVRBase::SetChannelGroupPath(const std::string& path)
+{
+ const CURL url{path};
+ const std::string pathWithoutOptions{url.GetWithoutOptions()};
+
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ if (m_channelGroupPath != pathWithoutOptions)
+ {
+ m_channelGroupPath = pathWithoutOptions;
+ }
+}
+
bool CGUIWindowPVRBase::Update(const std::string& strDirectory, bool updateFilterPath /*= true*/)
{
if (m_bUpdating)
diff --git a/xbmc/pvr/windows/GUIWindowPVRBase.h b/xbmc/pvr/windows/GUIWindowPVRBase.h
index f8c73bfe7e..0e3ab3ce47 100644
--- a/xbmc/pvr/windows/GUIWindowPVRBase.h
+++ b/xbmc/pvr/windows/GUIWindowPVRBase.h
@@ -110,10 +110,11 @@ namespace PVR
*/
void SetChannelGroup(std::shared_ptr<CPVRChannelGroup> &&group, bool bUpdate = true);
+ void SetChannelGroupPath(const std::string& path);
+
virtual void UpdateSelectedItemPath();
CCriticalSection m_critSection;
- std::string m_channelGroupPath;
bool m_bRadio;
std::atomic_bool m_bUpdating = {false};
@@ -135,5 +136,6 @@ namespace PVR
XbmcThreads::EndTime<> m_refreshTimeout;
CGUIDialogProgressBarHandle* m_progressHandle =
nullptr; /*!< progress dialog that is displayed while the pvr manager is loading */
+ std::string m_channelGroupPath;
};
}
diff --git a/xbmc/pvr/windows/GUIWindowPVRChannels.cpp b/xbmc/pvr/windows/GUIWindowPVRChannels.cpp
index 8ccbdd68fa..add693fb2d 100644
--- a/xbmc/pvr/windows/GUIWindowPVRChannels.cpp
+++ b/xbmc/pvr/windows/GUIWindowPVRChannels.cpp
@@ -24,6 +24,7 @@
#include "input/actions/Action.h"
#include "input/actions/ActionIDs.h"
#include "pvr/PVRManager.h"
+#include "pvr/PVRPathUtils.h"
#include "pvr/channels/PVRChannel.h"
#include "pvr/channels/PVRChannelGroup.h"
#include "pvr/channels/PVRChannelGroupMember.h"
@@ -37,6 +38,7 @@
#include "pvr/guilib/PVRGUIActionsEPG.h"
#include "pvr/guilib/PVRGUIActionsPlayback.h"
#include "utils/StringUtils.h"
+#include "utils/URIUtils.h"
#include "utils/Variant.h"
#include <mutex>
@@ -58,6 +60,15 @@ CGUIWindowPVRChannelsBase::~CGUIWindowPVRChannelsBase()
this);
}
+std::string CGUIWindowPVRChannelsBase::GetDirectoryPath()
+{
+ const std::string basePath{CPVRChannelsPath(m_bRadio, m_bShowHiddenChannels,
+ GetChannelGroup()->GroupName(),
+ GetChannelGroup()->GetClientID())};
+ return URIUtils::PathHasParent(m_vecItems->GetPath(), basePath) ? m_vecItems->GetPath()
+ : basePath;
+}
+
std::string CGUIWindowPVRChannelsBase::GetRootPath() const
{
//! @todo Would it make sense to change GetRootPath() declaration in CGUIMediaWindow
@@ -121,8 +132,12 @@ void CGUIWindowPVRChannelsBase::UpdateButtons()
}
CGUIWindowPVRBase::UpdateButtons();
+
SET_CONTROL_LABEL(CONTROL_LABEL_HEADER1, m_bShowHiddenChannels ? g_localizeStrings.Get(19022)
: GetChannelGroup()->GroupName());
+
+ // If we are filtering by client id / provider id, expose provider's name.
+ SET_CONTROL_LABEL(CONTROL_LABEL_HEADER2, UTILS::GetProviderNameFromPath(m_vecItems->GetPath()));
}
bool CGUIWindowPVRChannelsBase::OnAction(const CAction& action)
@@ -167,11 +182,11 @@ bool CGUIWindowPVRChannelsBase::OnMessage(CGUIMessage& message)
// Replace wildcard with real group name
const auto group =
CServiceBroker::GetPVRManager().ChannelGroups()->GetGroupAll(path.IsRadio());
- m_channelGroupPath = group->GetPath();
+ SetChannelGroupPath(group->GetPath());
}
else
{
- m_channelGroupPath = message.GetStringParam(0);
+ SetChannelGroupPath(message.GetStringParam(0));
}
}
break;
@@ -411,19 +426,7 @@ CGUIWindowPVRTVChannels::CGUIWindowPVRTVChannels()
{
}
-std::string CGUIWindowPVRTVChannels::GetDirectoryPath()
-{
- return CPVRChannelsPath(false, m_bShowHiddenChannels, GetChannelGroup()->GroupName(),
- GetChannelGroup()->GetClientID());
-}
-
CGUIWindowPVRRadioChannels::CGUIWindowPVRRadioChannels()
: CGUIWindowPVRChannelsBase(true, WINDOW_RADIO_CHANNELS, "MyPVRChannels.xml")
{
}
-
-std::string CGUIWindowPVRRadioChannels::GetDirectoryPath()
-{
- return CPVRChannelsPath(true, m_bShowHiddenChannels, GetChannelGroup()->GroupName(),
- GetChannelGroup()->GetClientID());
-}
diff --git a/xbmc/pvr/windows/GUIWindowPVRChannels.h b/xbmc/pvr/windows/GUIWindowPVRChannels.h
index 257b595264..d4684ac068 100644
--- a/xbmc/pvr/windows/GUIWindowPVRChannels.h
+++ b/xbmc/pvr/windows/GUIWindowPVRChannels.h
@@ -34,6 +34,9 @@ public:
void GetChannelNumbers(std::vector<std::string>& channelNumbers) override;
void OnInputDone() override;
+protected:
+ std::string GetDirectoryPath() override;
+
private:
bool OnContextButtonManage(const CFileItemPtr& item, CONTEXT_BUTTON button);
@@ -49,17 +52,11 @@ class CGUIWindowPVRTVChannels : public CGUIWindowPVRChannelsBase
{
public:
CGUIWindowPVRTVChannels();
-
-protected:
- std::string GetDirectoryPath() override;
};
class CGUIWindowPVRRadioChannels : public CGUIWindowPVRChannelsBase
{
public:
CGUIWindowPVRRadioChannels();
-
-protected:
- std::string GetDirectoryPath() override;
};
} // namespace PVR
diff --git a/xbmc/pvr/windows/GUIWindowPVRGuide.cpp b/xbmc/pvr/windows/GUIWindowPVRGuide.cpp
index 6ce75fd5f2..05f7669f85 100644
--- a/xbmc/pvr/windows/GUIWindowPVRGuide.cpp
+++ b/xbmc/pvr/windows/GUIWindowPVRGuide.cpp
@@ -283,7 +283,7 @@ CFileItemPtr CGUIWindowPVRGuideBase::GetCurrentListItem(int offset /*= 0*/)
int CGUIWindowPVRGuideBase::GetCurrentListItemIndex(const std::shared_ptr<const CFileItem>& item)
{
- return item ? item->GetProperty("TimelineIndex").asInteger() : -1;
+ return item ? item->GetProperty("TimelineIndex").asInteger32() : -1;
}
bool CGUIWindowPVRGuideBase::ShouldNavigateToGridContainer(int iAction)
@@ -402,7 +402,7 @@ bool CGUIWindowPVRGuideBase::OnMessage(CGUIMessage& message)
{
// if a path to a channel group is given we must init
// that group instead of last played/selected group
- m_channelGroupPath = message.GetStringParam(0);
+ SetChannelGroupPath(message.GetStringParam(0));
}
break;
}
@@ -630,7 +630,7 @@ public:
void Add(bool (A::*function)(), unsigned int resId)
{
- CContextButtons::Add(size(), resId);
+ CContextButtons::Add(static_cast<unsigned int>(size()), resId);
m_functions.emplace_back(std::bind(function, m_instance));
}
diff --git a/xbmc/pvr/windows/GUIWindowPVRProviders.cpp b/xbmc/pvr/windows/GUIWindowPVRProviders.cpp
new file mode 100644
index 0000000000..5f544fa3dd
--- /dev/null
+++ b/xbmc/pvr/windows/GUIWindowPVRProviders.cpp
@@ -0,0 +1,191 @@
+/*
+ * Copyright (C) 2024 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "GUIWindowPVRProviders.h"
+
+#include "FileItemList.h"
+#include "ServiceBroker.h"
+#include "guilib/GUIComponent.h"
+#include "guilib/GUIWindowManager.h"
+#include "input/actions/Action.h"
+#include "input/actions/ActionIDs.h"
+#include "pvr/PVRManager.h"
+#include "pvr/channels/PVRChannelGroup.h"
+#include "pvr/channels/PVRChannelGroupsContainer.h"
+#include "pvr/channels/PVRChannelsPath.h"
+#include "pvr/providers/PVRProvider.h"
+#include "pvr/providers/PVRProviders.h"
+#include "pvr/providers/PVRProvidersPath.h"
+#include "pvr/recordings/PVRRecordingsPath.h"
+#include "utils/StringUtils.h"
+#include "utils/URIUtils.h"
+
+#include <memory>
+#include <string>
+
+using namespace PVR;
+
+CGUIWindowPVRProvidersBase::CGUIWindowPVRProvidersBase(bool isRadio,
+ int id,
+ const std::string& xmlFile)
+ : CGUIWindowPVRBase(isRadio, id, xmlFile)
+{
+}
+
+CGUIWindowPVRProvidersBase::~CGUIWindowPVRProvidersBase() = default;
+
+bool CGUIWindowPVRProvidersBase::OnAction(const CAction& action)
+{
+ if (action.GetID() == ACTION_PARENT_DIR || action.GetID() == ACTION_NAV_BACK)
+ {
+ const CPVRProvidersPath path{m_vecItems->GetPath()};
+ if (path.IsValid() && !path.IsProvidersRoot())
+ {
+ GoParentFolder();
+ return true;
+ }
+ }
+
+ return CGUIWindowPVRBase::OnAction(action);
+}
+
+void CGUIWindowPVRProvidersBase::UpdateButtons()
+{
+ CGUIWindowPVRBase::UpdateButtons();
+
+ // Update window breadcrumb.
+ std::string header1;
+ const CPVRProvidersPath path{m_vecItems->GetPath()};
+ if (path.IsProvider())
+ {
+ const std::shared_ptr<const CPVRProvider> provider{
+ CServiceBroker::GetPVRManager().Providers()->GetByClient(path.GetClientId(),
+ path.GetProviderUid())};
+ if (provider)
+ header1 = provider->GetName();
+ }
+ SET_CONTROL_LABEL(CONTROL_LABEL_HEADER1, header1);
+}
+
+bool CGUIWindowPVRProvidersBase::OnMessage(CGUIMessage& message)
+{
+ bool ret{false};
+ switch (message.GetMessage())
+ {
+ case GUI_MSG_CLICKED:
+ {
+ if (message.GetSenderId() == m_viewControl.GetCurrentControl())
+ {
+ const int selectedItem{m_viewControl.GetSelectedItem()};
+ if (selectedItem >= 0 && selectedItem < m_vecItems->Size())
+ {
+ const std::shared_ptr<const CFileItem> item{m_vecItems->Get(selectedItem)};
+ switch (message.GetParam1())
+ {
+ case ACTION_SELECT_ITEM:
+ case ACTION_MOUSE_LEFT_CLICK:
+ {
+ const CPVRProvidersPath path{m_vecItems->GetPath()};
+ if (path.IsValid())
+ {
+ if (path.IsProvidersRoot())
+ {
+ if (item->IsParentFolder())
+ {
+ // Handle .. item, which is only visible if list of providers is empty.
+ CServiceBroker::GetGUI()->GetWindowManager().ActivateWindow(WINDOW_HOME);
+ ret = true;
+ break;
+ }
+ }
+ else if (path.IsProvider())
+ {
+ const CPVRProvidersPath selectedPath{item->GetPath()};
+ if (selectedPath.IsChannels())
+ {
+ ActivateChannelsWindow(selectedPath);
+ ret = true;
+ break;
+ }
+ else if (selectedPath.IsRecordings())
+ {
+ ActivateRecordingsWindow(selectedPath);
+ ret = true;
+ break;
+ }
+ }
+ }
+
+ if (item->m_bIsFolder)
+ {
+ // Folders and ".." folders in subfolders are handled by base class.
+ ret = false;
+ }
+ }
+ }
+ }
+ }
+ break;
+ }
+ }
+
+ return ret || CGUIWindowPVRBase::OnMessage(message);
+}
+
+void CGUIWindowPVRProvidersBase::ActivateChannelsWindow(const CPVRProvidersPath& selectedPath)
+{
+ const CPVRManager& pvrMgr{CServiceBroker::GetPVRManager()};
+ const std::shared_ptr<CPVRChannelGroup> allChannelsGroup{
+ pvrMgr.ChannelGroups()->GetGroupAll(selectedPath.IsRadio())};
+
+ std::string targetPath{CPVRChannelsPath(selectedPath.IsRadio(), allChannelsGroup->GroupName(),
+ allChannelsGroup->GetClientID())};
+ targetPath = StringUtils::Format("{}?clientid={}&providerid={}", targetPath,
+ selectedPath.GetClientId(), selectedPath.GetProviderUid());
+
+ // Activate channels window.
+ CServiceBroker::GetGUI()->GetWindowManager().ActivateWindow(
+ selectedPath.IsRadio() ? WINDOW_RADIO_CHANNELS : WINDOW_TV_CHANNELS, targetPath);
+}
+
+void CGUIWindowPVRProvidersBase::ActivateRecordingsWindow(const CPVRProvidersPath& selectedPath)
+{
+ std::string targetPath{CPVRRecordingsPath(selectedPath.IsRadio()
+ ? CPVRRecordingsPath::PATH_ACTIVE_RADIO_RECORDINGS
+ : CPVRRecordingsPath::PATH_ACTIVE_TV_RECORDINGS)};
+ targetPath = StringUtils::Format("{}?clientid={}&providerid={}", targetPath,
+ selectedPath.GetClientId(), selectedPath.GetProviderUid());
+
+ // Activate recordings window.
+ CServiceBroker::GetGUI()->GetWindowManager().ActivateWindow(
+ selectedPath.IsRadio() ? WINDOW_RADIO_RECORDINGS : WINDOW_TV_RECORDINGS, targetPath);
+}
+
+std::string CGUIWindowPVRTVProviders::GetRootPath() const
+{
+ return CPVRProvidersPath::PATH_TV_PROVIDERS;
+}
+
+std::string CGUIWindowPVRTVProviders::GetDirectoryPath()
+{
+ return URIUtils::PathHasParent(m_vecItems->GetPath(), CPVRProvidersPath::PATH_TV_PROVIDERS)
+ ? m_vecItems->GetPath()
+ : CPVRProvidersPath::PATH_TV_PROVIDERS;
+}
+
+std::string CGUIWindowPVRRadioProviders::GetRootPath() const
+{
+ return CPVRProvidersPath::PATH_RADIO_PROVIDERS;
+}
+
+std::string CGUIWindowPVRRadioProviders::GetDirectoryPath()
+{
+ return URIUtils::PathHasParent(m_vecItems->GetPath(), CPVRProvidersPath::PATH_RADIO_PROVIDERS)
+ ? m_vecItems->GetPath()
+ : CPVRProvidersPath::PATH_RADIO_PROVIDERS;
+}
diff --git a/xbmc/pvr/windows/GUIWindowPVRProviders.h b/xbmc/pvr/windows/GUIWindowPVRProviders.h
new file mode 100644
index 0000000000..9ad29a0d84
--- /dev/null
+++ b/xbmc/pvr/windows/GUIWindowPVRProviders.h
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2024 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "pvr/windows/GUIWindowPVRBase.h"
+
+#include <string>
+
+namespace PVR
+{
+class CPVRProvidersPath;
+
+class CGUIWindowPVRProvidersBase : public CGUIWindowPVRBase
+{
+public:
+ CGUIWindowPVRProvidersBase(bool isRadio, int id, const std::string& xmlFile);
+ ~CGUIWindowPVRProvidersBase() override;
+
+ bool OnMessage(CGUIMessage& message) override;
+ bool OnAction(const CAction& action) override;
+ void UpdateButtons() override;
+
+private:
+ void ActivateChannelsWindow(const CPVRProvidersPath& selectedPath);
+ void ActivateRecordingsWindow(const CPVRProvidersPath& selectedPath);
+};
+
+class CGUIWindowPVRTVProviders : public CGUIWindowPVRProvidersBase
+{
+public:
+ CGUIWindowPVRTVProviders()
+ : CGUIWindowPVRProvidersBase(false, WINDOW_TV_PROVIDERS, "MyPVRProviders.xml")
+ {
+ }
+ std::string GetRootPath() const override;
+ std::string GetDirectoryPath() override;
+};
+
+class CGUIWindowPVRRadioProviders : public CGUIWindowPVRProvidersBase
+{
+public:
+ CGUIWindowPVRRadioProviders()
+ : CGUIWindowPVRProvidersBase(true, WINDOW_RADIO_PROVIDERS, "MyPVRProviders.xml")
+ {
+ }
+ std::string GetRootPath() const override;
+ std::string GetDirectoryPath() override;
+};
+} // namespace PVR
diff --git a/xbmc/pvr/windows/GUIWindowPVRRecordings.cpp b/xbmc/pvr/windows/GUIWindowPVRRecordings.cpp
index 0e5886a52b..4238e07d56 100644
--- a/xbmc/pvr/windows/GUIWindowPVRRecordings.cpp
+++ b/xbmc/pvr/windows/GUIWindowPVRRecordings.cpp
@@ -11,6 +11,7 @@
#include "FileItemList.h"
#include "GUIInfoManager.h"
#include "ServiceBroker.h"
+#include "URL.h"
#include "guilib/GUIComponent.h"
#include "guilib/GUIMessage.h"
#include "guilib/GUIRadioButtonControl.h"
@@ -19,6 +20,7 @@
#include "input/actions/Action.h"
#include "input/actions/ActionIDs.h"
#include "pvr/PVRManager.h"
+#include "pvr/PVRPathUtils.h"
#include "pvr/guilib/PVRGUIActionsPlayback.h"
#include "pvr/guilib/PVRGUIActionsRecordings.h"
#include "pvr/recordings/PVRRecording.h"
@@ -56,6 +58,22 @@ void CGUIWindowPVRRecordingsBase::OnWindowLoaded()
CONTROL_SELECT(CONTROL_BTNGROUPITEMS);
}
+void CGUIWindowPVRRecordingsBase::OnDeinitWindow(int nextWindowID)
+{
+ if (UTILS::HasClientAndProvider(m_vecItems->GetPath()))
+ m_vecItems->SetPath(""); // Open default listing next time.
+
+ CGUIWindowPVRBase::OnDeinitWindow(nextWindowID);
+}
+
+std::string CGUIWindowPVRRecordingsBase::GetRootPath() const
+{
+ const CURL url{m_vecItems->GetPath()};
+ std::string rootPath{CPVRRecordingsPath(m_bShowDeletedRecordings, m_bRadio)};
+ rootPath += url.GetOptions();
+ return rootPath;
+}
+
std::string CGUIWindowPVRRecordingsBase::GetDirectoryPath()
{
const std::string basePath = CPVRRecordingsPath(m_bShowDeletedRecordings, m_bRadio);
@@ -214,9 +232,9 @@ void CGUIWindowPVRRecordingsBase::UpdateButtons()
}
CGUIWindowPVRBase::UpdateButtons();
- SET_CONTROL_LABEL(CONTROL_LABEL_HEADER1, m_bShowDeletedRecordings
- ? g_localizeStrings.Get(19179)
- : ""); /* Deleted recordings trash */
+
+ // If we are filtering by client id / provider id, expose provider's name.
+ SET_CONTROL_LABEL(CONTROL_LABEL_HEADER1, UTILS::GetProviderNameFromPath(m_vecItems->GetPath()));
const CPVRRecordingsPath path(m_vecItems->GetPath());
SET_CONTROL_LABEL(CONTROL_LABEL_HEADER2,
@@ -268,7 +286,7 @@ protected:
return true;
}
- bool OnMoreSelected() override
+ bool OnChooseSelected() override
{
m_window.OnPopupMenu(m_itemIndex);
return true;
@@ -509,13 +527,3 @@ bool CGUIWindowPVRRecordingsBase::GetFilteredItems(const std::string& filter, CF
return listchanged;
}
-
-std::string CGUIWindowPVRTVRecordings::GetRootPath() const
-{
- return CPVRRecordingsPath(m_bShowDeletedRecordings, false);
-}
-
-std::string CGUIWindowPVRRadioRecordings::GetRootPath() const
-{
- return CPVRRecordingsPath(m_bShowDeletedRecordings, true);
-}
diff --git a/xbmc/pvr/windows/GUIWindowPVRRecordings.h b/xbmc/pvr/windows/GUIWindowPVRRecordings.h
index 21dc269772..4cb069fd81 100644
--- a/xbmc/pvr/windows/GUIWindowPVRRecordings.h
+++ b/xbmc/pvr/windows/GUIWindowPVRRecordings.h
@@ -27,6 +27,7 @@ public:
~CGUIWindowPVRRecordingsBase() override;
void OnWindowLoaded() override;
+ void OnDeinitWindow(int nextWindowID) override;
bool OnMessage(CGUIMessage& message) override;
bool OnAction(const CAction& action) override;
void GetContextButtons(int itemNumber, CContextButtons& buttons) override;
@@ -36,15 +37,15 @@ public:
void UpdateButtons() override;
protected:
+ std::string GetRootPath() const override;
std::string GetDirectoryPath() override;
void OnPrepareFileItems(CFileItemList& items) override;
bool GetFilteredItems(const std::string& filter, CFileItemList& items) override;
- bool m_bShowDeletedRecordings{false};
-
private:
bool OnContextButtonDeleteAll(CFileItem* item, CONTEXT_BUTTON button);
+ bool m_bShowDeletedRecordings{false};
CVideoThumbLoader m_thumbLoader;
CVideoDatabase m_database;
CPVRSettings m_settings;
@@ -57,7 +58,6 @@ public:
: CGUIWindowPVRRecordingsBase(false, WINDOW_TV_RECORDINGS, "MyPVRRecordings.xml")
{
}
- std::string GetRootPath() const override;
};
class CGUIWindowPVRRadioRecordings : public CGUIWindowPVRRecordingsBase
@@ -67,6 +67,5 @@ public:
: CGUIWindowPVRRecordingsBase(true, WINDOW_RADIO_RECORDINGS, "MyPVRRecordings.xml")
{
}
- std::string GetRootPath() const override;
};
} // namespace PVR
diff --git a/xbmc/pvr/windows/GUIWindowPVRSearch.cpp b/xbmc/pvr/windows/GUIWindowPVRSearch.cpp
index 4a9ad6b10e..50504e4628 100644
--- a/xbmc/pvr/windows/GUIWindowPVRSearch.cpp
+++ b/xbmc/pvr/windows/GUIWindowPVRSearch.cpp
@@ -26,6 +26,7 @@
#include "pvr/epg/Epg.h"
#include "pvr/epg/EpgContainer.h"
#include "pvr/epg/EpgInfoTag.h"
+#include "pvr/epg/EpgSearch.h"
#include "pvr/epg/EpgSearchFilter.h"
#include "pvr/epg/EpgSearchPath.h"
#include "pvr/guilib/PVRGUIActionsEPG.h"
@@ -71,27 +72,10 @@ bool AsyncSearchAction::Execute()
void AsyncSearchAction::Run()
{
- std::vector<std::shared_ptr<CPVREpgInfoTag>> results =
- CServiceBroker::GetPVRManager().EpgContainer().GetTags(m_filter->GetEpgSearchData());
- m_filter->SetEpgSearchDataFiltered();
-
- // Tags can still contain false positives, for search criteria that cannot be handled via
- // database. So, run extended search filters on what we got from the database.
- for (auto it = results.begin(); it != results.end();)
- {
- it = results.erase(std::remove_if(results.begin(), results.end(),
- [this](const std::shared_ptr<CPVREpgInfoTag>& entry) {
- return !m_filter->FilterEntry(entry);
- }),
- results.end());
- }
-
- if (m_filter->ShouldRemoveDuplicates())
- m_filter->RemoveDuplicates(results);
-
- m_filter->SetLastExecutedDateTime(CDateTime::GetUTCDateTime());
-
- for (const auto& tag : results)
+ CPVREpgSearch search(*m_filter);
+ search.Execute();
+ const auto tags{search.GetResults()};
+ for (const auto& tag : tags)
{
m_items->Add(std::make_shared<CFileItem>(tag));
}
@@ -471,7 +455,7 @@ void CGUIWindowPVRSearchBase::ExecuteSearch()
}
// Save if not a transient search
- if (m_searchfilter->GetDatabaseId() != -1)
+ if (m_searchfilter->GetDatabaseId() != PVR_EPG_SEARCH_INVALID_DATABASE_ID)
CServiceBroker::GetPVRManager().EpgContainer().UpdateSavedSearchLastExecuted(*m_searchfilter);
}
diff --git a/xbmc/rendering/gles/RenderSystemGLES.cpp b/xbmc/rendering/gles/RenderSystemGLES.cpp
index 471938a714..ef2261ff18 100644
--- a/xbmc/rendering/gles/RenderSystemGLES.cpp
+++ b/xbmc/rendering/gles/RenderSystemGLES.cpp
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2005-2018 Team Kodi
+ * Copyright (C) 2005-2024 Team Kodi
* This file is part of Kodi - https://kodi.tv
*
* SPDX-License-Identifier: GPL-2.0-or-later
@@ -8,11 +8,13 @@
#include "RenderSystemGLES.h"
+#include "URL.h"
#include "guilib/DirtyRegion.h"
#include "guilib/GUITextureGLES.h"
#include "rendering/MatrixGL.h"
#include "settings/AdvancedSettings.h"
#include "settings/SettingsComponent.h"
+#include "utils/FileUtils.h"
#include "utils/GLUtils.h"
#include "utils/MathUtils.h"
#include "utils/SystemInfo.h"
@@ -774,3 +776,18 @@ GLint CRenderSystemGLES::GUIShaderGetCoordStep()
return -1;
}
+
+std::string CRenderSystemGLES::GetShaderPath(const std::string& filename)
+{
+ std::string path = "GLES/2.0/";
+
+ if (m_RenderVersionMajor >= 3 && m_RenderVersionMinor >= 1)
+ {
+ std::string file = "special://xbmc/system/shaders/GLES/3.1/" + filename;
+ const CURL pathToUrl(file);
+ if (CFileUtils::Exists(pathToUrl.Get()))
+ return "GLES/3.1/";
+ }
+
+ return path;
+}
diff --git a/xbmc/rendering/gles/RenderSystemGLES.h b/xbmc/rendering/gles/RenderSystemGLES.h
index 9c19bf6c28..1ea0ea60a5 100644
--- a/xbmc/rendering/gles/RenderSystemGLES.h
+++ b/xbmc/rendering/gles/RenderSystemGLES.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2005-2018 Team Kodi
+ * Copyright (C) 2005-2024 Team Kodi
* This file is part of Kodi - https://kodi.tv
*
* SPDX-License-Identifier: GPL-2.0-or-later
@@ -111,7 +111,7 @@ public:
void Project(float &x, float &y, float &z) override;
- std::string GetShaderPath(const std::string &filename) override { return "GLES/2.0/"; }
+ std::string GetShaderPath(const std::string& filename) override;
void InitialiseShaders();
void ReleaseShaders();
diff --git a/xbmc/settings/AdvancedSettings.h b/xbmc/settings/AdvancedSettings.h
index 0f4c5a7871..051636cb05 100644
--- a/xbmc/settings/AdvancedSettings.h
+++ b/xbmc/settings/AdvancedSettings.h
@@ -13,6 +13,7 @@
#include "settings/lib/ISettingsHandler.h"
#include "utils/SortUtils.h"
+#include <cstdint>
#include <set>
#include <string>
#include <utility>
diff --git a/xbmc/settings/Settings.h b/xbmc/settings/Settings.h
index dfc87db9f2..f600c315f5 100644
--- a/xbmc/settings/Settings.h
+++ b/xbmc/settings/Settings.h
@@ -233,6 +233,7 @@ public:
static constexpr auto SETTING_PVRRECORD_MARGINSTART = "pvrrecord.marginstart";
static constexpr auto SETTING_PVRRECORD_MARGINEND = "pvrrecord.marginend";
static constexpr auto SETTING_PVRRECORD_TIMERNOTIFICATIONS = "pvrrecord.timernotifications";
+ static constexpr auto SETTING_PVRRECORD_DELETEAFTERWATCH = "pvrrecord.deleteafterwatch";
static constexpr auto SETTING_PVRRECORD_GROUPRECORDINGS = "pvrrecord.grouprecordings";
static constexpr auto SETTING_PVRREMINDERS_AUTOCLOSEDELAY = "pvrreminders.autoclosedelay";
static constexpr auto SETTING_PVRREMINDERS_AUTORECORD = "pvrreminders.autorecord";
@@ -397,6 +398,7 @@ public:
static constexpr auto SETTING_AUDIOOUTPUT_ATEMPOTHRESHOLD = "audiooutput.atempothreshold";
static constexpr auto SETTING_AUDIOOUTPUT_STREAMSILENCE = "audiooutput.streamsilence";
static constexpr auto SETTING_AUDIOOUTPUT_STREAMNOISE = "audiooutput.streamnoise";
+ static constexpr auto SETTING_AUDIOOUTPUT_MIXSUBLEVEL = "audiooutput.mixsublevel";
static constexpr auto SETTING_AUDIOOUTPUT_GUISOUNDMODE = "audiooutput.guisoundmode";
static constexpr auto SETTING_AUDIOOUTPUT_GUISOUNDVOLUME = "audiooutput.guisoundvolume";
static constexpr auto SETTING_AUDIOOUTPUT_PASSTHROUGH = "audiooutput.passthrough";
diff --git a/xbmc/storage/DetectDVDType.cpp b/xbmc/storage/DetectDVDType.cpp
index 3a3a3a08a8..91fd7deecb 100644
--- a/xbmc/storage/DetectDVDType.cpp
+++ b/xbmc/storage/DetectDVDType.cpp
@@ -389,13 +389,6 @@ void CDetectDVDMedia::WaitMediaReady()
std::unique_lock<CCriticalSection> waitLock(m_muReadingMedia);
}
-// Static function
-// Returns status of the DVD Drive
-bool CDetectDVDMedia::DriveReady()
-{
- return m_DriveState == DriveState::READY;
-}
-
DriveState CDetectDVDMedia::GetDriveState()
{
return m_DriveState;
diff --git a/xbmc/storage/DetectDVDType.h b/xbmc/storage/DetectDVDType.h
index 500a10f574..a97944509f 100644
--- a/xbmc/storage/DetectDVDType.h
+++ b/xbmc/storage/DetectDVDType.h
@@ -42,7 +42,6 @@ public:
static void WaitMediaReady();
static bool IsDiscInDrive();
- static bool DriveReady();
static DriveState GetDriveState();
static CCdInfo* GetCdInfo();
static CEvent m_evAutorun;
diff --git a/xbmc/test/TestFileItem.cpp b/xbmc/test/TestFileItem.cpp
index e78ba27676..e5ca906ab8 100644
--- a/xbmc/test/TestFileItem.cpp
+++ b/xbmc/test/TestFileItem.cpp
@@ -42,63 +42,6 @@ AdvancedSettingsResetBase::AdvancedSettingsResetBase()
settings->GetAdvancedSettings()->Initialize(*settingsMgr);
}
-class TestFileItemSpecifiedArtJpg : public AdvancedSettingsResetBase,
- public WithParamInterface<TestFileData>
-{
-};
-
-
-TEST_P(TestFileItemSpecifiedArtJpg, GetLocalArt)
-{
- CFileItem item;
- item.SetPath(GetParam().file);
- std::string path = CURL(item.GetLocalArt("art.jpg", GetParam().use_folder)).Get();
- std::string compare = CURL(GetParam().base).Get();
- EXPECT_EQ(compare, path);
-}
-
-const TestFileData MovieFiles[] = {{ "c:\\dir\\filename.avi", false, "c:\\dir\\filename-art.jpg" },
- { "c:\\dir\\filename.avi", true, "c:\\dir\\art.jpg" },
- { "/dir/filename.avi", false, "/dir/filename-art.jpg" },
- { "/dir/filename.avi", true, "/dir/art.jpg" },
- { "smb://somepath/file.avi", false, "smb://somepath/file-art.jpg" },
- { "smb://somepath/file.avi", true, "smb://somepath/art.jpg" },
- { "stack:///path/to/movie-cd1.avi , /path/to/movie-cd2.avi", false, "/path/to/movie-art.jpg" },
- { "stack:///path/to/movie-cd1.avi , /path/to/movie-cd2.avi", true, "/path/to/art.jpg" },
- { "stack:///path/to/movie_name/cd1/some_file1.avi , /path/to/movie_name/cd2/some_file2.avi", true, "/path/to/movie_name/art.jpg" },
- { "/home/user/TV Shows/Dexter/S1/1x01.avi", false, "/home/user/TV Shows/Dexter/S1/1x01-art.jpg" },
- { "/home/user/TV Shows/Dexter/S1/1x01.avi", true, "/home/user/TV Shows/Dexter/S1/art.jpg" },
- { "zip://g%3a%5cmultimedia%5cmovies%5cSphere%2ezip/Sphere.avi", false, "g:\\multimedia\\movies\\Sphere-art.jpg" },
- { "zip://g%3a%5cmultimedia%5cmovies%5cSphere%2ezip/Sphere.avi", true, "g:\\multimedia\\movies\\art.jpg" },
- { "/home/user/movies/movie_name/video_ts/VIDEO_TS.IFO", false, "/home/user/movies/movie_name/art.jpg" },
- { "/home/user/movies/movie_name/video_ts/VIDEO_TS.IFO", true, "/home/user/movies/movie_name/art.jpg" },
- { "/home/user/movies/movie_name/BDMV/index.bdmv", false, "/home/user/movies/movie_name/art.jpg" },
- { "/home/user/movies/movie_name/BDMV/index.bdmv", true, "/home/user/movies/movie_name/art.jpg" }};
-
-INSTANTIATE_TEST_SUITE_P(MovieFiles, TestFileItemSpecifiedArtJpg, ValuesIn(MovieFiles));
-
-class TestFileItemFallbackArt : public AdvancedSettingsResetBase,
- public WithParamInterface<TestFileData>
-{
-};
-
-TEST_P(TestFileItemFallbackArt, GetLocalArt)
-{
- CFileItem item;
- item.SetPath(GetParam().file);
- std::string path = CURL(item.GetLocalArt("", GetParam().use_folder)).Get();
- std::string compare = CURL(GetParam().base).Get();
- EXPECT_EQ(compare, path);
-}
-
-const TestFileData NoArtFiles[] = {{ "c:\\dir\\filename.avi", false, "c:\\dir\\filename.tbn" },
- { "/dir/filename.avi", false, "/dir/filename.tbn" },
- { "smb://somepath/file.avi", false, "smb://somepath/file.tbn" },
- { "/home/user/TV Shows/Dexter/S1/1x01.avi", false, "/home/user/TV Shows/Dexter/S1/1x01.tbn" },
- { "zip://g%3a%5cmultimedia%5cmovies%5cSphere%2ezip/Sphere.avi", false, "g:\\multimedia\\movies\\Sphere.tbn" }};
-
-INSTANTIATE_TEST_SUITE_P(NoArt, TestFileItemFallbackArt, ValuesIn(NoArtFiles));
-
class TestFileItemBasePath : public AdvancedSettingsResetBase,
public WithParamInterface<TestFileData>
{
diff --git a/xbmc/utils/Archive.h b/xbmc/utils/Archive.h
index a1af0c3cf8..341c07d779 100644
--- a/xbmc/utils/Archive.h
+++ b/xbmc/utils/Archive.h
@@ -8,6 +8,7 @@
#pragma once
+#include <cstdint>
#include <cstring>
#include <memory>
#include <string>
diff --git a/xbmc/utils/ArtUtils.cpp b/xbmc/utils/ArtUtils.cpp
index 80f09c3e8e..6394f6c7c8 100644
--- a/xbmc/utils/ArtUtils.cpp
+++ b/xbmc/utils/ArtUtils.cpp
@@ -13,8 +13,11 @@
#include "ServiceBroker.h"
#include "filesystem/Directory.h"
#include "filesystem/File.h"
+#include "filesystem/MultiPathDirectory.h"
#include "filesystem/StackDirectory.h"
+#include "music/MusicFileItemClassify.h"
#include "network/NetworkFileItemClassify.h"
+#include "playlists/PlayListFileItemClassify.h"
#include "settings/AdvancedSettings.h"
#include "settings/SettingsComponent.h"
#include "utils/FileExtensionProvider.h"
@@ -28,6 +31,218 @@ using namespace XFILE;
namespace KODI::ART
{
+void FillInDefaultIcon(CFileItem& item)
+{
+ if (URIUtils::IsPVRGuideItem(item.GetPath()))
+ {
+ // epg items never have a default icon. no need to execute this expensive method.
+ // when filling epg grid window, easily tens of thousands of epg items are processed.
+ return;
+ }
+
+ // find the default icon for a file or folder item
+ // for files this can be the (depending on the file type)
+ // default picture for photo's
+ // default picture for songs
+ // default picture for videos
+ // default picture for shortcuts
+ // default picture for playlists
+ //
+ // for folders
+ // for .. folders the default picture for parent folder
+ // for other folders the defaultFolder.png
+
+ if (item.GetArt("icon").empty())
+ {
+ if (!item.m_bIsFolder)
+ {
+ /* To reduce the average runtime of this code, this list should
+ * be ordered with most frequently seen types first. Also bear
+ * in mind the complexity of the code behind the check in the
+ * case of IsWhatever() returns false.
+ */
+ if (item.IsPVRChannel())
+ {
+ if (URIUtils::IsPVRRadioChannel(item.GetPath()))
+ item.SetArt("icon", "DefaultMusicSongs.png");
+ else
+ item.SetArt("icon", "DefaultTVShows.png");
+ }
+ else if (item.IsLiveTV())
+ {
+ // Live TV Channel
+ item.SetArt("icon", "DefaultTVShows.png");
+ }
+ else if (URIUtils::IsArchive(item.GetPath()))
+ { // archive
+ item.SetArt("icon", "DefaultFile.png");
+ }
+ else if (item.IsUsablePVRRecording())
+ {
+ // PVR recording
+ item.SetArt("icon", "DefaultVideo.png");
+ }
+ else if (item.IsDeletedPVRRecording())
+ {
+ // PVR deleted recording
+ item.SetArt("icon", "DefaultVideoDeleted.png");
+ }
+ else if (item.IsPVRProvider())
+ {
+ item.SetArt("icon", "DefaultPVRProvider.png");
+ }
+ else if (PLAYLIST::IsPlayList(item) || PLAYLIST::IsSmartPlayList(item))
+ {
+ item.SetArt("icon", "DefaultPlaylist.png");
+ }
+ else if (MUSIC::IsAudio(item))
+ {
+ // audio
+ item.SetArt("icon", "DefaultAudio.png");
+ }
+ else if (VIDEO::IsVideo(item))
+ {
+ // video
+ item.SetArt("icon", "DefaultVideo.png");
+ }
+ else if (item.IsPVRTimer())
+ {
+ item.SetArt("icon", "DefaultVideo.png");
+ }
+ else if (item.IsPicture())
+ {
+ // picture
+ item.SetArt("icon", "DefaultPicture.png");
+ }
+ else if (item.IsPythonScript())
+ {
+ item.SetArt("icon", "DefaultScript.png");
+ }
+ else if (item.IsFavourite())
+ {
+ item.SetArt("icon", "DefaultFavourites.png");
+ }
+ else
+ {
+ // default icon for unknown file type
+ item.SetArt("icon", "DefaultFile.png");
+ }
+ }
+ else
+ {
+ if (PLAYLIST::IsPlayList(item) || PLAYLIST::IsSmartPlayList(item))
+ {
+ item.SetArt("icon", "DefaultPlaylist.png");
+ }
+ else if (item.IsParentFolder())
+ {
+ item.SetArt("icon", "DefaultFolderBack.png");
+ }
+ else
+ {
+ item.SetArt("icon", "DefaultFolder.png");
+ }
+ }
+ }
+ // Set the icon overlays (if applicable)
+ if (!item.HasOverlay() && !item.HasProperty("icon_never_overlay"))
+ {
+ if (URIUtils::IsInRAR(item.GetPath()))
+ item.SetOverlayImage(CGUIListItem::ICON_OVERLAY_RAR);
+ else if (URIUtils::IsInZIP(item.GetPath()))
+ item.SetOverlayImage(CGUIListItem::ICON_OVERLAY_ZIP);
+ }
+}
+
+std::string GetFolderThumb(const CFileItem& item, const std::string& folderJPG /* = "folder.jpg" */)
+{
+ std::string strFolder = item.GetPath();
+
+ if (item.IsStack())
+ {
+ URIUtils::GetParentPath(item.GetPath(), strFolder);
+ }
+
+ if (URIUtils::IsInRAR(strFolder) || URIUtils::IsInZIP(strFolder))
+ {
+ const CURL url(strFolder);
+ strFolder = URIUtils::GetDirectory(url.GetHostName());
+ }
+
+ if (item.IsMultiPath())
+ strFolder = CMultiPathDirectory::GetFirstPath(item.GetPath());
+
+ if (item.IsPlugin())
+ return "";
+
+ return URIUtils::AddFileToFolder(strFolder, folderJPG);
+}
+
+std::string GetLocalArt(const CFileItem& item, const std::string& artFile, bool useFolder)
+{
+ // no retrieving of empty art files from folders
+ if (useFolder && artFile.empty())
+ return "";
+
+ std::string strFile = GetLocalArtBaseFilename(item, useFolder);
+ if (strFile.empty()) // empty filepath -> nothing to find
+ return "";
+
+ if (useFolder)
+ {
+ if (!artFile.empty())
+ return URIUtils::AddFileToFolder(strFile, artFile);
+ }
+ else
+ {
+ if (artFile.empty()) // old thumbnail matching
+ return URIUtils::ReplaceExtension(strFile, ".tbn");
+ else
+ return URIUtils::ReplaceExtension(strFile, "-" + artFile);
+ }
+ return "";
+}
+
+std::string GetLocalArtBaseFilename(const CFileItem& item, bool& useFolder)
+{
+ std::string strFile;
+ if (item.IsStack())
+ {
+ std::string strPath;
+ URIUtils::GetParentPath(item.GetPath(), strPath);
+ strFile = URIUtils::AddFileToFolder(
+ strPath, URIUtils::GetFileName(CStackDirectory::GetStackedTitlePath(item.GetPath())));
+ }
+
+ std::string file = strFile.empty() ? item.GetPath() : strFile;
+ if (URIUtils::IsInRAR(file) || URIUtils::IsInZIP(file))
+ {
+ std::string strPath = URIUtils::GetDirectory(file);
+ std::string strParent;
+ URIUtils::GetParentPath(strPath, strParent);
+ strFile = URIUtils::AddFileToFolder(strParent, URIUtils::GetFileName(file));
+ }
+
+ if (item.IsMultiPath())
+ strFile = CMultiPathDirectory::GetFirstPath(item.GetPath());
+
+ if (item.IsOpticalMediaFile())
+ { // optical media files should be treated like folders
+ useFolder = true;
+ strFile = item.GetLocalMetadataPath();
+ }
+ else if (useFolder && !(item.m_bIsFolder && !item.IsFileFolder()))
+ {
+ file = strFile.empty() ? item.GetPath() : strFile;
+ strFile = URIUtils::GetDirectory(file);
+ }
+
+ if (strFile.empty())
+ strFile = item.GetDynPath();
+
+ return strFile;
+}
+
std::string GetLocalFanart(const CFileItem& item)
{
if (VIDEO::IsVideoDb(item))
diff --git a/xbmc/utils/ArtUtils.h b/xbmc/utils/ArtUtils.h
index 564b42b056..99848308b2 100644
--- a/xbmc/utils/ArtUtils.h
+++ b/xbmc/utils/ArtUtils.h
@@ -15,6 +15,35 @@ class CFileItem;
namespace KODI::ART
{
+//! \brief Set default icon for item.
+void FillInDefaultIcon(CFileItem& item);
+
+/*!
+ * \brief Get the folder image associated with item.
+ * \param item Item to get folder image for
+ * \param folderJPG Thumb file to use
+ * \return Folder thumb file appropriate for item
+ */
+std::string GetFolderThumb(const CFileItem& item, const std::string& folderJPG = "folder.jpg");
+
+/*! \brief Assemble the filename of a particular piece of local artwork for an item.
+ No file existence check is typically performed.
+ \param artFile the art file to search for.
+ \param useFolder whether to look in the folder for the art file. Defaults to false.
+ \return the path to the local artwork.
+ \sa FindLocalArt
+ */
+std::string GetLocalArt(const CFileItem& item, const std::string& artFile, bool useFolder = false);
+
+/*!
+ \brief Assemble the base filename of local artwork for an item,
+ accounting for archives, stacks and multi-paths, and BDMV/VIDEO_TS folders.
+ \param useFolder whether to look in the folder for the art file. Defaults to false.
+ \return the path to the base filename for artwork lookup.
+ \sa GetLocalArt
+ */
+std::string GetLocalArtBaseFilename(const CFileItem& item, bool& useFolder);
+
/*!
\brief Get the local fanart for item if it exists
\return path to the local fanart for this item, or empty if none exists
@@ -22,7 +51,7 @@ namespace KODI::ART
*/
std::string GetLocalFanart(const CFileItem& item);
-// Gets the .tbn file associated with an item
+//! \brief Get the .tbn file associated with an item
std::string GetTBNFile(const CFileItem& item);
} // namespace KODI::ART
diff --git a/xbmc/utils/CPUInfo.h b/xbmc/utils/CPUInfo.h
index a53ec29c6e..2d3c044248 100644
--- a/xbmc/utils/CPUInfo.h
+++ b/xbmc/utils/CPUInfo.h
@@ -115,7 +115,7 @@ protected:
std::size_t m_totalTime{0};
int m_cpuCount;
- unsigned int m_cpuFeatures;
+ unsigned int m_cpuFeatures{0};
std::vector<CoreInfo> m_cores;
};
diff --git a/xbmc/utils/CharArrayParser.cpp b/xbmc/utils/CharArrayParser.cpp
index 5aeec2040b..ec5af70a9c 100644
--- a/xbmc/utils/CharArrayParser.cpp
+++ b/xbmc/utils/CharArrayParser.cpp
@@ -156,11 +156,19 @@ bool CCharArrayParser::ReadNextLine(std::string& line)
line.assign(m_data + m_position, lineLimit - m_position);
m_position = lineLimit;
+ // Skip EOL chars
if (m_data[m_position] == '\r')
{
m_position++;
+
+ if (m_data[m_position] == '\n')
+ m_position++;
+ // Malformed EOL as \r\r\n
+ else if (m_position + 1 <= m_limit && m_data[m_position] == '\r' &&
+ m_data[m_position + 1] == '\n')
+ m_position += 2;
}
- if (m_data[m_position] == '\n')
+ else if (m_data[m_position] == '\n')
{
m_position++;
}
diff --git a/xbmc/utils/SaveFileStateJob.cpp b/xbmc/utils/SaveFileStateJob.cpp
index b71620769d..27653ce1c1 100644
--- a/xbmc/utils/SaveFileStateJob.cpp
+++ b/xbmc/utils/SaveFileStateJob.cpp
@@ -26,6 +26,8 @@
#include "music/MusicFileItemClassify.h"
#include "music/tags/MusicInfoTag.h"
#include "network/upnp/UPnP.h"
+#include "pvr/PVRManager.h"
+#include "pvr/guilib/PVRGUIActionsRecordings.h"
#include "utils/Variant.h"
#include "video/Bookmark.h"
#include "video/VideoDatabase.h"
@@ -119,7 +121,11 @@ void CSaveFileState::DoWork(CFileItem& item,
if (item.HasVideoInfoTag())
{
- item.GetVideoInfoTag()->IncrementPlayCount();
+ if (item.IsPVRRecording())
+ CServiceBroker::GetPVRManager().Get<PVR::GUI::Recordings>().IncrementPlayCount(
+ item);
+ else
+ item.GetVideoInfoTag()->IncrementPlayCount();
if (newLastPlayed.IsValid())
item.GetVideoInfoTag()->m_lastPlayed = newLastPlayed;
diff --git a/xbmc/utils/URIUtils.cpp b/xbmc/utils/URIUtils.cpp
index 9b862a7f43..6ba6a6c6f2 100644
--- a/xbmc/utils/URIUtils.cpp
+++ b/xbmc/utils/URIUtils.cpp
@@ -998,6 +998,19 @@ bool URIUtils::IsPVRChannel(const std::string& strFile)
return IsProtocol(strFile, "pvr") && CPVRChannelsPath(strFile).IsChannel();
}
+bool URIUtils::IsPVRRadioChannel(const std::string& strFile)
+{
+ if (IsStack(strFile))
+ return IsPVRRadioChannel(CStackDirectory::GetFirstStackedFile(strFile));
+
+ if (IsProtocol(strFile, "pvr"))
+ {
+ const CPVRChannelsPath path{strFile};
+ return path.IsChannel() && path.IsRadio();
+ }
+ return false;
+}
+
bool URIUtils::IsPVRChannelGroup(const std::string& strFile)
{
if (IsStack(strFile))
diff --git a/xbmc/utils/URIUtils.h b/xbmc/utils/URIUtils.h
index 10f9d3995e..171157ed3d 100644
--- a/xbmc/utils/URIUtils.h
+++ b/xbmc/utils/URIUtils.h
@@ -184,6 +184,7 @@ public:
static bool IsLibraryContent(const std::string& strFile);
static bool IsPVR(const std::string& strFile);
static bool IsPVRChannel(const std::string& strFile);
+ static bool IsPVRRadioChannel(const std::string& strFile);
static bool IsPVRChannelGroup(const std::string& strFile);
static bool IsPVRGuideItem(const std::string& strFile);
diff --git a/xbmc/utils/test/TestArtUtils.cpp b/xbmc/utils/test/TestArtUtils.cpp
index 324836b233..6f9f1967db 100644
--- a/xbmc/utils/test/TestArtUtils.cpp
+++ b/xbmc/utils/test/TestArtUtils.cpp
@@ -7,9 +7,14 @@
*/
#include "FileItem.h"
+#include "ServiceBroker.h"
#include "URL.h"
#include "filesystem/Directory.h"
#include "platform/Filesystem.h"
+#include "settings/AdvancedSettings.h"
+#include "settings/Settings.h"
+#include "settings/SettingsComponent.h"
+#include "settings/lib/SettingsManager.h"
#include "utils/ArtUtils.h"
#include "utils/FileUtils.h"
#include "utils/StringUtils.h"
@@ -47,6 +52,47 @@ std::string unique_path(const std::string& input)
return ret;
}
+class AdvancedSettingsResetBase : public testing::Test
+{
+public:
+ AdvancedSettingsResetBase();
+};
+
+AdvancedSettingsResetBase::AdvancedSettingsResetBase()
+{
+ // Force all advanced settings to be reset to defaults
+ const auto settings = CServiceBroker::GetSettingsComponent();
+ CSettingsManager* settingsMgr = settings->GetSettings()->GetSettingsManager();
+ settings->GetAdvancedSettings()->Uninitialize(*settingsMgr);
+ settings->GetAdvancedSettings()->Initialize(*settingsMgr);
+}
+
+struct ArtFilenameTest
+{
+ std::string path;
+ std::string result;
+ bool isFolder = false;
+ bool result_folder = false;
+ bool force_use_folder = false;
+};
+
+class GetLocalArtBaseFilenameTest : public testing::WithParamInterface<ArtFilenameTest>,
+ public testing::Test
+{
+};
+
+const auto local_art_filename_tests = std::array{
+ ArtFilenameTest{"/home/user/foo.avi", "/home/user/foo.avi"},
+ ArtFilenameTest{"stack:///home/user/foo-cd1.avi , /home/user/foo-cd2.avi",
+ "/home/user/foo.avi"},
+ ArtFilenameTest{"zip://%2fhome%2fuser%2fbar.zip/foo.avi", "/home/user/foo.avi"},
+ ArtFilenameTest{"multipath://%2fhome%2fuser%2fbar%2f/%2fhome%2fuser%2ffoo%2f",
+ "/home/user/bar/", true, true},
+ ArtFilenameTest{"/home/user/VIDEO_TS/VIDEO_TS.IFO", "/home/user/", false, true},
+ ArtFilenameTest{"/home/user/BDMV/index.bdmv", "/home/user/", false, true},
+ ArtFilenameTest{"/home/user/foo.avi", "/home/user/", false, true, true},
+};
+
struct FanartTest
{
std::string path;
@@ -76,6 +122,42 @@ const auto local_fanart_tests = std::array{
FanartTest{"videodb://movies/1", "foo-fanart.jpg"},
};
+struct IconTest
+{
+ std::string path;
+ std::string icon;
+ std::string overlay{};
+ bool isFolder = false;
+ bool valid = true;
+ bool no_overlay = false;
+};
+
+class FillInDefaultIconTest : public testing::WithParamInterface<IconTest>, public testing::Test
+{
+};
+
+const auto icon_tests = std::array{
+ IconTest{"pvr://guide", "", "", false, false},
+ IconTest{"/home/user/test.pvr", "DefaultTVShows.png"},
+ IconTest{"/home/user/test.zip", "DefaultFile.png"},
+ IconTest{"/home/user/test.mp3", "DefaultAudio.png"},
+ IconTest{"/home/user/test.avi", "DefaultVideo.png"},
+ IconTest{"/home/user/test.jpg", "DefaultPicture.png"},
+ IconTest{"/home/user/test.m3u", "DefaultPlaylist.png"},
+ IconTest{"/home/user/test.xsp", "DefaultPlaylist.png"},
+ IconTest{"/home/user/test.py", "DefaultScript.png"},
+ IconTest{"favourites://1", "DefaultFavourites.png"},
+ IconTest{"/home/user/test.fil", "DefaultFile.png"},
+ IconTest{"/home/user/test.m3u", "DefaultPlaylist.png", "", true},
+ IconTest{"/home/user/test.xsp", "DefaultPlaylist.png", "", true},
+ IconTest{"..", "DefaultFolderBack.png", "", true},
+ IconTest{"/home/user/test/", "DefaultFolder.png", "", true},
+ IconTest{"zip://%2fhome%2fuser%2fbar.zip/foo.avi", "DefaultVideo.png", "OverlayZIP.png"},
+ IconTest{"zip://%2fhome%2fuser%2fbar.zip/foo.avi", "DefaultVideo.png", "", false, true, true},
+ IconTest{"rar://%2fhome%2fuser%2fbar.rar/foo.avi", "DefaultVideo.png", "OverlayRAR.png"},
+ IconTest{"rar://%2fhome%2fuser%2fbar.rar/foo.avi", "DefaultVideo.png", "", false, true, true},
+};
+
struct TbnTest
{
std::string path;
@@ -87,8 +169,132 @@ class GetTbnTest : public testing::WithParamInterface<TbnTest>, public testing::
{
};
+struct FolderTest
+{
+ std::string path;
+ std::string thumb;
+ std::string result;
+};
+
+const auto folder_thumb_tests = std::array{
+ FolderTest{"c:\\dir\\", "art.jpg", "c:\\dir\\art.jpg"},
+ FolderTest{"/home/user/", "folder.jpg", "/home/user/folder.jpg"},
+ FolderTest{"plugin://plugin.video.foo/", "folder.jpg", ""},
+ FolderTest{"stack:///home/user/bar/foo-cd1.avi , /home/user/bar/foo-cd2.avi", "folder.jpg",
+ "/home/user/bar/folder.jpg"},
+ FolderTest{"stack:///home/user/cd1/foo-cd1.avi , /home/user/cd2/foo-cd2.avi", "artist.jpg",
+ "/home/user/artist.jpg"},
+ FolderTest{"zip://%2fhome%2fuser%2fbar.zip/foo.avi", "cover.png", "/home/user/cover.png"},
+ FolderTest{"multipath://%2fhome%2fuser%2fbar%2f/%2fhome%2fuser%2ffoo%2f", "folder.jpg",
+ "/home/user/bar/folder.jpg"},
+};
+
+class FolderThumbTest : public testing::WithParamInterface<FolderTest>, public testing::Test
+{
+};
+
+struct LocalArtTest
+{
+ std::string file;
+ std::string art;
+ bool use_folder;
+ std::string base;
+};
+
+const auto local_art_tests = std::array{
+ LocalArtTest{"c:\\dir\\filename.avi", "art.jpg", false, "c:\\dir\\filename-art.jpg"},
+ LocalArtTest{"c:\\dir\\filename.avi", "art.jpg", true, "c:\\dir\\art.jpg"},
+ LocalArtTest{"/dir/filename.avi", "art.jpg", false, "/dir/filename-art.jpg"},
+ LocalArtTest{"/dir/filename.avi", "art.jpg", true, "/dir/art.jpg"},
+ LocalArtTest{"smb://somepath/file.avi", "art.jpg", false, "smb://somepath/file-art.jpg"},
+ LocalArtTest{"smb://somepath/file.avi", "art.jpg", true, "smb://somepath/art.jpg"},
+ LocalArtTest{"stack:///path/to/movie-cd1.avi , /path/to/movie-cd2.avi", "art.jpg", false,
+ "/path/to/movie-art.jpg"},
+ LocalArtTest{"stack:///path/to/movie-cd1.avi , /path/to/movie-cd2.avi", "art.jpg", true,
+ "/path/to/art.jpg"},
+ LocalArtTest{
+ "stack:///path/to/movie_name/cd1/some_file1.avi , /path/to/movie_name/cd2/some_file2.avi",
+ "art.jpg", true, "/path/to/movie_name/art.jpg"},
+ LocalArtTest{"/home/user/TV Shows/Dexter/S1/1x01.avi", "art.jpg", false,
+ "/home/user/TV Shows/Dexter/S1/1x01-art.jpg"},
+ LocalArtTest{"/home/user/TV Shows/Dexter/S1/1x01.avi", "art.jpg", true,
+ "/home/user/TV Shows/Dexter/S1/art.jpg"},
+ LocalArtTest{"zip://g%3a%5cmultimedia%5cmovies%5cSphere%2ezip/Sphere.avi", "art.jpg", false,
+ "g:\\multimedia\\movies\\Sphere-art.jpg"},
+ LocalArtTest{"zip://g%3a%5cmultimedia%5cmovies%5cSphere%2ezip/Sphere.avi", "art.jpg", true,
+ "g:\\multimedia\\movies\\art.jpg"},
+ LocalArtTest{"/home/user/movies/movie_name/video_ts/VIDEO_TS.IFO", "art.jpg", false,
+ "/home/user/movies/movie_name/art.jpg"},
+ LocalArtTest{"/home/user/movies/movie_name/video_ts/VIDEO_TS.IFO", "art.jpg", true,
+ "/home/user/movies/movie_name/art.jpg"},
+ LocalArtTest{"/home/user/movies/movie_name/BDMV/index.bdmv", "art.jpg", false,
+ "/home/user/movies/movie_name/art.jpg"},
+ LocalArtTest{"/home/user/movies/movie_name/BDMV/index.bdmv", "art.jpg", true,
+ "/home/user/movies/movie_name/art.jpg"},
+ LocalArtTest{"c:\\dir\\filename.avi", "", false, "c:\\dir\\filename.tbn"},
+ LocalArtTest{"/dir/filename.avi", "", false, "/dir/filename.tbn"},
+ LocalArtTest{"smb://somepath/file.avi", "", false, "smb://somepath/file.tbn"},
+ LocalArtTest{"/home/user/TV Shows/Dexter/S1/1x01.avi", "", false,
+ "/home/user/TV Shows/Dexter/S1/1x01.tbn"},
+ LocalArtTest{"zip://g%3a%5cmultimedia%5cmovies%5cSphere%2ezip/Sphere.avi", "", false,
+ "g:\\multimedia\\movies\\Sphere.tbn"},
+};
+
+class TestLocalArt : public AdvancedSettingsResetBase,
+ public testing::WithParamInterface<LocalArtTest>
+{
+};
+
} // namespace
+TEST_P(FillInDefaultIconTest, FillInDefaultIcon)
+{
+ CFileItem item(GetParam().path, GetParam().isFolder);
+ if (!GetParam().valid)
+ item.SetArt("icon", "InvalidImage.png");
+ item.SetLabel(GetParam().path);
+ if (GetParam().no_overlay)
+ item.SetProperty("icon_never_overlay", true);
+ ART::FillInDefaultIcon(item);
+ EXPECT_EQ(item.GetArt("icon"), GetParam().valid ? GetParam().icon : "InvalidImage.png");
+ EXPECT_EQ(item.GetOverlayImage(), GetParam().overlay);
+}
+
+INSTANTIATE_TEST_SUITE_P(TestArtUtils, FillInDefaultIconTest, testing::ValuesIn(icon_tests));
+
+TEST_P(FolderThumbTest, GetFolderThumb)
+{
+ CFileItem item(GetParam().path, true);
+ const std::string thumb = ART::GetFolderThumb(item, GetParam().thumb);
+ EXPECT_EQ(thumb, GetParam().result);
+}
+
+INSTANTIATE_TEST_SUITE_P(TestArtUtils, FolderThumbTest, testing::ValuesIn(folder_thumb_tests));
+
+TEST_P(TestLocalArt, GetLocalArt)
+{
+ CFileItem item;
+ item.SetPath(GetParam().file);
+ std::string path = CURL(ART::GetLocalArt(item, GetParam().art, GetParam().use_folder)).Get();
+ std::string compare = CURL(GetParam().base).Get();
+ EXPECT_EQ(compare, path);
+}
+
+INSTANTIATE_TEST_SUITE_P(TestArtUtils, TestLocalArt, testing::ValuesIn(local_art_tests));
+
+TEST_P(GetLocalArtBaseFilenameTest, GetLocalArtBaseFilename)
+{
+ CFileItem item(GetParam().path, GetParam().isFolder);
+ bool useFolder = GetParam().force_use_folder ? true : GetParam().isFolder;
+ const std::string res = ART::GetLocalArtBaseFilename(item, useFolder);
+ EXPECT_EQ(res, GetParam().result);
+ EXPECT_EQ(useFolder, GetParam().result_folder);
+}
+
+INSTANTIATE_TEST_SUITE_P(TestArtUtils,
+ GetLocalArtBaseFilenameTest,
+ testing::ValuesIn(local_art_filename_tests));
+
TEST_P(GetLocalFanartTest, GetLocalFanart)
{
std::string path, file_path, uniq;
diff --git a/xbmc/utils/test/TestURIUtils.cpp b/xbmc/utils/test/TestURIUtils.cpp
index 3c720b4980..95b66b639b 100644
--- a/xbmc/utils/test/TestURIUtils.cpp
+++ b/xbmc/utils/test/TestURIUtils.cpp
@@ -322,6 +322,19 @@ TEST_F(TestURIUtils, IsLiveTV)
EXPECT_TRUE(URIUtils::IsLiveTV("whatever://path/to/file.pvr"));
}
+TEST_F(TestURIUtils, IsPVRRadioChannel)
+{
+ // pvr://channels/(tv|radio)/<groupname>@<clientid>/<instanceid>@<addonid>_<channeluid>.pvr
+ EXPECT_TRUE(
+ URIUtils::IsPVRRadioChannel("pvr://channels/radio/groupname@0815/1@pvr.demo_4711.pvr"));
+ EXPECT_FALSE(URIUtils::IsPVRRadioChannel(
+ "pvr://channels/tv/groupname@0815/1@pvr.demo_4711.pvr")); // a tv channel
+ EXPECT_FALSE(
+ URIUtils::IsPVRRadioChannel("pvr://channels/radio/")); // root folder for all radio channels
+ EXPECT_FALSE(
+ URIUtils::IsPVRRadioChannel("pvr://channels/radio/groupname@0815/")); // a radio channel group
+}
+
TEST_F(TestURIUtils, IsMultiPath)
{
EXPECT_TRUE(URIUtils::IsMultiPath("multipath://path/to/file"));
diff --git a/xbmc/video/ContextMenus.cpp b/xbmc/video/ContextMenus.cpp
index 0d463b8105..3b502e1479 100644
--- a/xbmc/video/ContextMenus.cpp
+++ b/xbmc/video/ContextMenus.cpp
@@ -228,7 +228,7 @@ protected:
return true;
}
- bool OnMoreSelected() override
+ bool OnChooseSelected() override
{
CONTEXTMENU::ShowFor(m_item, CContextMenuManager::MAIN);
return true;
diff --git a/xbmc/video/VideoDatabase.cpp b/xbmc/video/VideoDatabase.cpp
index 994cf34a68..f333bb334b 100644
--- a/xbmc/video/VideoDatabase.cpp
+++ b/xbmc/video/VideoDatabase.cpp
@@ -382,45 +382,42 @@ void CVideoDatabase::CreateAnalytics()
void CVideoDatabase::CreateViews()
{
CLog::Log(LOGINFO, "create episode_view");
- std::string episodeview = PrepareSQL("CREATE VIEW episode_view AS SELECT "
- " episode.*,"
- " files.strFileName AS strFileName,"
- " path.strPath AS strPath,"
- " files.playCount AS playCount,"
- " files.lastPlayed AS lastPlayed,"
- " files.dateAdded AS dateAdded,"
- " tvshow.c%02d AS strTitle,"
- " tvshow.c%02d AS genre,"
- " tvshow.c%02d AS studio,"
- " tvshow.c%02d AS premiered,"
- " tvshow.c%02d AS mpaa,"
- " bookmark.timeInSeconds AS resumeTimeInSeconds, "
- " bookmark.totalTimeInSeconds AS totalTimeInSeconds, "
- " bookmark.playerState AS playerState, "
- " rating.rating AS rating, "
- " rating.votes AS votes, "
- " rating.rating_type AS rating_type, "
- " uniqueid.value AS uniqueid_value, "
- " uniqueid.type AS uniqueid_type "
- "FROM episode"
- " JOIN files ON"
- " files.idFile=episode.idFile"
- " JOIN tvshow ON"
- " tvshow.idShow=episode.idShow"
- " JOIN seasons ON"
- " seasons.idSeason=episode.idSeason"
- " JOIN path ON"
- " files.idPath=path.idPath"
- " LEFT JOIN bookmark ON"
- " bookmark.idFile=episode.idFile AND bookmark.type=1"
- " LEFT JOIN rating ON"
- " rating.rating_id=episode.c%02d"
- " LEFT JOIN uniqueid ON"
- " uniqueid.uniqueid_id=episode.c%02d",
- VIDEODB_ID_TV_TITLE, VIDEODB_ID_TV_GENRE,
- VIDEODB_ID_TV_STUDIOS, VIDEODB_ID_TV_PREMIERED,
- VIDEODB_ID_TV_MPAA, VIDEODB_ID_EPISODE_RATING_ID,
- VIDEODB_ID_EPISODE_IDENT_ID);
+ std::string episodeview = PrepareSQL(
+ "CREATE VIEW episode_view AS SELECT "
+ " episode.*,"
+ " files.strFileName AS strFileName,"
+ " path.strPath AS strPath,"
+ " files.playCount AS playCount,"
+ " files.lastPlayed AS lastPlayed,"
+ " files.dateAdded AS dateAdded,"
+ " tvshow.c%02d AS strTitle,"
+ " tvshow.c%02d AS genre,"
+ " tvshow.c%02d AS studio,"
+ " tvshow.c%02d AS premiered,"
+ " tvshow.c%02d AS mpaa,"
+ " bookmark.timeInSeconds AS resumeTimeInSeconds, "
+ " bookmark.totalTimeInSeconds AS totalTimeInSeconds, "
+ " bookmark.playerState AS playerState, "
+ " rating.rating AS rating, "
+ " rating.votes AS votes, "
+ " rating.rating_type AS rating_type, "
+ " uniqueid.value AS uniqueid_value, "
+ " uniqueid.type AS uniqueid_type "
+ "FROM episode"
+ " JOIN files ON"
+ " files.idFile=episode.idFile"
+ " JOIN tvshow ON"
+ " tvshow.idShow=episode.idShow"
+ " JOIN path ON"
+ " files.idPath=path.idPath"
+ " LEFT JOIN bookmark ON"
+ " bookmark.idFile=episode.idFile AND bookmark.type=1"
+ " LEFT JOIN rating ON"
+ " rating.rating_id=episode.c%02d"
+ " LEFT JOIN uniqueid ON"
+ " uniqueid.uniqueid_id=episode.c%02d",
+ VIDEODB_ID_TV_TITLE, VIDEODB_ID_TV_GENRE, VIDEODB_ID_TV_STUDIOS, VIDEODB_ID_TV_PREMIERED,
+ VIDEODB_ID_TV_MPAA, VIDEODB_ID_EPISODE_RATING_ID, VIDEODB_ID_EPISODE_IDENT_ID);
m_pDS->exec(episodeview);
CLog::Log(LOGINFO, "create tvshowcounts");
@@ -6444,11 +6441,20 @@ void CVideoDatabase::UpdateTables(int iVersion)
}
m_pDS->close();
}
+
+ if (iVersion < 133)
+ {
+ // Remove episodes with invalid idSeason values.
+ // Since 2015 they were masked from episode_view and are not going to be missed.
+ // Those records would be misses in database converted in 2015 (see videodb version 99).
+
+ m_pDS->exec("DELETE FROM episode WHERE idSeason NOT IN (SELECT idSeason from seasons)");
+ }
}
int CVideoDatabase::GetSchemaVersion() const
{
- return 132;
+ return 133;
}
bool CVideoDatabase::LookupByFolders(const std::string &path, bool shows)
@@ -10742,7 +10748,7 @@ void CVideoDatabase::ExportToXML(const std::string &path, bool singleFile /* = t
}
for (const auto &i : artwork)
{
- std::string savedThumb = item.GetLocalArt(i.first, false);
+ std::string savedThumb = ART::GetLocalArt(item, i.first, false);
CServiceBroker::GetTextureCache()->Export(i.second, savedThumb, overwrite);
}
if (actorThumbs)
@@ -10888,7 +10894,7 @@ void CVideoDatabase::ExportToXML(const std::string &path, bool singleFile /* = t
}
for (const auto &i : artwork)
{
- std::string savedThumb = item.GetLocalArt(i.first, false);
+ std::string savedThumb = ART::GetLocalArt(item, i.first, false);
CServiceBroker::GetTextureCache()->Export(i.second, savedThumb, overwrite);
}
}
@@ -10986,7 +10992,7 @@ void CVideoDatabase::ExportToXML(const std::string &path, bool singleFile /* = t
for (const auto &i : artwork)
{
- std::string savedThumb = item.GetLocalArt(i.first, true);
+ std::string savedThumb = ART::GetLocalArt(item, i.first, true);
CServiceBroker::GetTextureCache()->Export(i.second, savedThumb, overwrite);
}
@@ -11005,7 +11011,7 @@ void CVideoDatabase::ExportToXML(const std::string &path, bool singleFile /* = t
seasonThumb = StringUtils::Format("season{:02}", i.first);
for (const auto &j : i.second)
{
- std::string savedThumb(item.GetLocalArt(seasonThumb + "-" + j.first, true));
+ std::string savedThumb(ART::GetLocalArt(item, seasonThumb + "-" + j.first, true));
if (!i.second.empty())
CServiceBroker::GetTextureCache()->Export(j.second, savedThumb, overwrite);
}
@@ -11088,7 +11094,7 @@ void CVideoDatabase::ExportToXML(const std::string &path, bool singleFile /* = t
}
for (const auto &i : artwork)
{
- std::string savedThumb = item.GetLocalArt(i.first, false);
+ std::string savedThumb = ART::GetLocalArt(item, i.first, false);
CServiceBroker::GetTextureCache()->Export(i.second, savedThumb, overwrite);
}
if (actorThumbs)
diff --git a/xbmc/video/VideoInfoScanner.cpp b/xbmc/video/VideoInfoScanner.cpp
index 9dc3588a5c..10cc30a6c9 100644
--- a/xbmc/video/VideoInfoScanner.cpp
+++ b/xbmc/video/VideoInfoScanner.cpp
@@ -38,6 +38,7 @@
#include "settings/Settings.h"
#include "settings/SettingsComponent.h"
#include "tags/VideoInfoTagLoaderFactory.h"
+#include "utils/ArtUtils.h"
#include "utils/Digest.h"
#include "utils/FileExtensionProvider.h"
#include "utils/RegExp.h"
@@ -48,6 +49,7 @@
#include "video/VideoFileItemClassify.h"
#include "video/VideoManagerTypes.h"
#include "video/VideoThumbLoader.h"
+#include "video/VideoUtils.h"
#include "video/dialogs/GUIDialogVideoManagerExtras.h"
#include "video/dialogs/GUIDialogVideoManagerVersions.h"
@@ -58,7 +60,7 @@
using namespace XFILE;
using namespace ADDON;
using namespace KODI::MESSAGING;
-using namespace KODI::VIDEO;
+using namespace KODI;
using KODI::MESSAGING::HELPERS::DialogResponse;
using KODI::UTILITY::CDigest;
@@ -1526,7 +1528,7 @@ namespace KODI::VIDEO
if (content == CONTENT_MOVIES)
{
// find local trailer first
- std::string strTrailer = pItem->FindTrailer();
+ std::string strTrailer = UTILS::FindTrailer(*pItem);
if (!strTrailer.empty())
movieDetails.m_strTrailer = strTrailer;
@@ -1792,14 +1794,21 @@ namespace KODI::VIDEO
{
if (!pItem->SkipLocalArt())
{
+ bool useFolder = false;
if (bApplyToDir && (content == CONTENT_MOVIES || content == CONTENT_MUSICVIDEOS))
{
- std::string filename = pItem->GetLocalArtBaseFilename();
+ std::string filename = ART::GetLocalArtBaseFilename(*pItem, useFolder);
std::string directory = URIUtils::GetDirectory(filename);
if (filename != directory)
AddLocalItemArtwork(art, artTypes, directory, addAll, exactName);
}
- AddLocalItemArtwork(art, artTypes, pItem->GetLocalArtBaseFilename(), addAll, exactName);
+
+ // Reset useFolder to false as GetLocalArtBaseFilename may modify it in
+ // the previous call.
+ useFolder = false;
+
+ AddLocalItemArtwork(art, artTypes, ART::GetLocalArtBaseFilename(*pItem, useFolder), addAll,
+ exactName);
}
if (moviePartOfSet)
diff --git a/xbmc/video/VideoUtils.cpp b/xbmc/video/VideoUtils.cpp
index eb6ab9674d..73f38e4ead 100644
--- a/xbmc/video/VideoUtils.cpp
+++ b/xbmc/video/VideoUtils.cpp
@@ -13,12 +13,18 @@
#include "ServiceBroker.h"
#include "Util.h"
#include "filesystem/Directory.h"
+#include "filesystem/StackDirectory.h"
#include "filesystem/VideoDatabaseDirectory/QueryParams.h"
+#include "network/NetworkFileItemClassify.h"
#include "playlists/PlayListFileItemClassify.h"
+#include "pvr/filesystem/PVRGUIDirectory.h"
+#include "settings/AdvancedSettings.h"
#include "settings/SettingUtils.h"
#include "settings/Settings.h"
#include "settings/SettingsComponent.h"
#include "settings/lib/Setting.h"
+#include "utils/ArtUtils.h"
+#include "utils/FileExtensionProvider.h"
#include "utils/FileUtils.h"
#include "utils/URIUtils.h"
#include "utils/log.h"
@@ -41,15 +47,17 @@ KODI::VIDEO::UTILS::ResumeInformation GetFolderItemResumeInformation(const CFile
return {};
CFileItem folderItem(item);
- if ((!folderItem.HasProperty("inprogressepisodes") || // season/show
- (folderItem.GetProperty("inprogressepisodes").asInteger() == 0)) &&
- (!folderItem.HasProperty("inprogress") || // movie set
- (folderItem.GetProperty("inprogress").asInteger() == 0)))
+ if (!folderItem.HasProperty("inprogressepisodes") && // season/show/recordings
+ !folderItem.HasProperty("inprogress")) // movie set
{
- CVideoDatabase db;
- if (db.Open())
+ if (URIUtils::IsPVRRecordingFileOrFolder(folderItem.GetPath()))
+ {
+ PVR::CPVRGUIDirectory::GetRecordingsDirectoryInfo(folderItem);
+ }
+ else
{
- if (!folderItem.HasProperty("inprogressepisodes") && !folderItem.HasProperty("inprogress"))
+ CVideoDatabase db;
+ if (db.Open())
{
XFILE::VIDEODATABASEDIRECTORY::CQueryParams params;
XFILE::VIDEODATABASEDIRECTORY::CDirectoryNode::GetDatabaseInfo(item.GetPath(), params);
@@ -79,7 +87,6 @@ KODI::VIDEO::UTILS::ResumeInformation GetFolderItemResumeInformation(const CFile
db.GetSetInfo(static_cast<int>(params.GetSetId()), details, &folderItem);
}
}
- db.Close();
}
}
@@ -185,6 +192,86 @@ KODI::VIDEO::UTILS::ResumeInformation GetNonFolderItemResumeInformation(const CF
namespace KODI::VIDEO::UTILS
{
+
+std::string FindTrailer(const CFileItem& item)
+{
+ std::string strFile2;
+ std::string strFile = item.GetPath();
+ if (item.IsStack())
+ {
+ std::string strPath;
+ URIUtils::GetParentPath(item.GetPath(), strPath);
+ XFILE::CStackDirectory dir;
+ std::string strPath2;
+ strPath2 = dir.GetStackedTitlePath(strFile);
+ strFile = URIUtils::AddFileToFolder(strPath, URIUtils::GetFileName(strPath2));
+ CFileItem sitem(dir.GetFirstStackedFile(item.GetPath()), false);
+ std::string strTBNFile(URIUtils::ReplaceExtension(ART::GetTBNFile(sitem), "-trailer"));
+ strFile2 = URIUtils::AddFileToFolder(strPath, URIUtils::GetFileName(strTBNFile));
+ }
+ if (URIUtils::IsInRAR(strFile) || URIUtils::IsInZIP(strFile))
+ {
+ std::string strPath = URIUtils::GetDirectory(strFile);
+ std::string strParent;
+ URIUtils::GetParentPath(strPath, strParent);
+ strFile = URIUtils::AddFileToFolder(strParent, URIUtils::GetFileName(item.GetPath()));
+ }
+
+ // no local trailer available for these
+ if (NETWORK::IsInternetStream(item) || URIUtils::IsUPnP(strFile) || URIUtils::IsBluray(strFile) ||
+ item.IsLiveTV() || item.IsPlugin() || item.IsDVD())
+ return "";
+
+ std::string strDir = URIUtils::GetDirectory(strFile);
+ CFileItemList items;
+ XFILE::CDirectory::GetDirectory(
+ strDir, items, CServiceBroker::GetFileExtensionProvider().GetVideoExtensions(),
+ XFILE::DIR_FLAG_READ_CACHE | XFILE::DIR_FLAG_NO_FILE_INFO | XFILE::DIR_FLAG_NO_FILE_DIRS);
+ URIUtils::RemoveExtension(strFile);
+ strFile += "-trailer";
+ std::string strFile3 = URIUtils::AddFileToFolder(strDir, "movie-trailer");
+
+ // Precompile our REs
+ VECCREGEXP matchRegExps;
+ CRegExp tmpRegExp(true, CRegExp::autoUtf8);
+ const std::vector<std::string>& strMatchRegExps =
+ CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_trailerMatchRegExps;
+
+ for (const auto& strRegExp : strMatchRegExps)
+ {
+ if (tmpRegExp.RegComp(strRegExp))
+ matchRegExps.push_back(tmpRegExp);
+ }
+
+ std::string strTrailer;
+ for (int i = 0; i < items.Size(); i++)
+ {
+ std::string strCandidate = items[i]->GetPath();
+ URIUtils::RemoveExtension(strCandidate);
+ if (StringUtils::EqualsNoCase(strCandidate, strFile) ||
+ StringUtils::EqualsNoCase(strCandidate, strFile2) ||
+ StringUtils::EqualsNoCase(strCandidate, strFile3))
+ {
+ strTrailer = items[i]->GetPath();
+ break;
+ }
+ else
+ {
+ for (auto& expr : matchRegExps)
+ {
+ if (expr.RegFind(strCandidate) != -1)
+ {
+ strTrailer = items[i]->GetPath();
+ i = items.Size();
+ break;
+ }
+ }
+ }
+ }
+
+ return strTrailer;
+}
+
std::string GetOpticalMediaPath(const CFileItem& item)
{
auto exists = [&item](const std::string& file)
diff --git a/xbmc/video/VideoUtils.h b/xbmc/video/VideoUtils.h
index c344072ffe..2ee611c4e8 100644
--- a/xbmc/video/VideoUtils.h
+++ b/xbmc/video/VideoUtils.h
@@ -14,6 +14,13 @@ class CFileItem;
namespace KODI::VIDEO::UTILS
{
+
+/*! \brief
+ * Find a local trailer file for a given file item
+ * \return non-empty string with path of trailer if found
+ */
+std::string FindTrailer(const CFileItem& item);
+
/*!
\brief Check whether an item is an optical media folder or its parent.
This will return the non-empty path to the playable entry point of the media
diff --git a/xbmc/video/dialogs/GUIDialogVideoInfo.cpp b/xbmc/video/dialogs/GUIDialogVideoInfo.cpp
index 7d93996ba5..f7c931e4b0 100644
--- a/xbmc/video/dialogs/GUIDialogVideoInfo.cpp
+++ b/xbmc/video/dialogs/GUIDialogVideoInfo.cpp
@@ -59,6 +59,7 @@
#include "video/VideoItemArtworkHandler.h"
#include "video/VideoLibraryQueue.h"
#include "video/VideoThumbLoader.h"
+#include "video/VideoUtils.h"
#include "video/dialogs/GUIDialogVideoManagerExtras.h"
#include "video/dialogs/GUIDialogVideoManagerVersions.h"
#include "video/guilib/VideoGUIUtils.h"
@@ -453,7 +454,7 @@ void CGUIDialogVideoInfo::SetMovie(const CFileItem *item)
if (m_movieItem->GetVideoInfoTag()->m_strTrailer.empty() ||
URIUtils::IsInternetStream(m_movieItem->GetVideoInfoTag()->m_strTrailer))
{
- std::string localTrailer = m_movieItem->FindTrailer();
+ std::string localTrailer = VIDEO::UTILS::FindTrailer(*m_movieItem);
if (!localTrailer.empty())
{
m_movieItem->GetVideoInfoTag()->m_strTrailer = localTrailer;
diff --git a/xbmc/video/guilib/VideoAction.h b/xbmc/video/guilib/VideoAction.h
index d8d0ccb782..d2b060434b 100644
--- a/xbmc/video/guilib/VideoAction.h
+++ b/xbmc/video/guilib/VideoAction.h
@@ -18,7 +18,7 @@ enum Action
ACTION_PLAY_OR_RESUME = 1, // if resume is possible, ask user. play from beginning otherwise
ACTION_RESUME = 2, // resume if possibly, play from beginning otherwise
ACTION_INFO = 3,
- ACTION_MORE = 4,
+ // 4 unused
ACTION_PLAY_FROM_BEGINNING = 5, // play from beginning, also if resume would be possible
ACTION_PLAYPART = 6,
ACTION_QUEUE = 7,
diff --git a/xbmc/video/guilib/VideoGUIUtils.cpp b/xbmc/video/guilib/VideoGUIUtils.cpp
index 70c6003751..e12f072016 100644
--- a/xbmc/video/guilib/VideoGUIUtils.cpp
+++ b/xbmc/video/guilib/VideoGUIUtils.cpp
@@ -141,12 +141,15 @@ void CAsyncGetItemsForPlaylist::GetItemsForPlaylist(const std::shared_ptr<CFileI
if (item->m_bIsFolder)
{
- // check if it's a folder with dvd or bluray files, then just add the relevant file
- const std::string mediapath = VIDEO::UTILS::GetOpticalMediaPath(*item);
- if (!mediapath.empty())
+ if (!item->IsPlugin())
{
- m_queuedItems.Add(std::make_shared<CFileItem>(mediapath, false));
- return;
+ // check if it's a folder with dvd or bluray files, then just add the relevant file
+ const std::string mediapath = VIDEO::UTILS::GetOpticalMediaPath(*item);
+ if (!mediapath.empty())
+ {
+ m_queuedItems.Add(std::make_shared<CFileItem>(mediapath, false));
+ return;
+ }
}
// Check if we add a locked share
diff --git a/xbmc/video/guilib/VideoSelectActionProcessor.cpp b/xbmc/video/guilib/VideoSelectActionProcessor.cpp
index 4935d62f04..5f9e9dab33 100644
--- a/xbmc/video/guilib/VideoSelectActionProcessor.cpp
+++ b/xbmc/video/guilib/VideoSelectActionProcessor.cpp
@@ -11,7 +11,6 @@
#include "FileItem.h"
#include "FileItemList.h"
#include "ServiceBroker.h"
-#include "dialogs/GUIDialogContextMenu.h"
#include "dialogs/GUIDialogSelect.h"
#include "filesystem/Directory.h"
#include "guilib/GUIComponent.h"
@@ -22,7 +21,6 @@
#include "utils/StringUtils.h"
#include "utils/Variant.h"
#include "video/VideoFileItemClassify.h"
-#include "video/VideoInfoTag.h"
#include "video/guilib/VideoGUIUtils.h"
namespace KODI::VIDEO::GUILIB
@@ -47,16 +45,7 @@ bool CVideoSelectActionProcessorBase::Process(Action action)
switch (action)
{
case ACTION_CHOOSE:
- {
- const Action selectedAction = ChooseVideoItemSelectAction();
- if (selectedAction < 0)
- {
- m_userCancelled = true;
- return true; // User cancelled the select menu. We're done.
- }
-
- return Process(selectedAction);
- }
+ return OnChooseSelected();
case ACTION_PLAYPART:
{
@@ -83,9 +72,6 @@ bool CVideoSelectActionProcessorBase::Process(Action action)
return OnInfoSelected();
}
- case ACTION_MORE:
- return OnMoreSelected();
-
default:
break;
}
@@ -115,26 +101,4 @@ unsigned int CVideoSelectActionProcessorBase::ChooseStackItemPartNumber() const
return dialog->GetSelectedItem() + 1; // part numbers are 1-based
}
-Action CVideoSelectActionProcessorBase::ChooseVideoItemSelectAction() const
-{
- CContextButtons choices;
-
- const std::string resumeString = UTILS::GetResumeString(*m_item);
- if (!resumeString.empty())
- {
- choices.Add(ACTION_RESUME, resumeString);
- choices.Add(ACTION_PLAY_FROM_BEGINNING, 12021); // Play from beginning
- }
- else
- {
- choices.Add(ACTION_PLAY_FROM_BEGINNING, 208); // Play
- }
-
- choices.Add(ACTION_INFO, 22081); // Show information
- choices.Add(ACTION_QUEUE, 13347); // Queue item
- choices.Add(ACTION_MORE, 22082); // More
-
- return static_cast<Action>(CGUIDialogContextMenu::ShowAndGetChoice(choices));
-}
-
} // namespace KODI::VIDEO::GUILIB
diff --git a/xbmc/video/guilib/VideoSelectActionProcessor.h b/xbmc/video/guilib/VideoSelectActionProcessor.h
index 30e2dc1049..565546a19c 100644
--- a/xbmc/video/guilib/VideoSelectActionProcessor.h
+++ b/xbmc/video/guilib/VideoSelectActionProcessor.h
@@ -35,11 +35,10 @@ protected:
virtual bool OnPlayPartSelected(unsigned int part) = 0;
virtual bool OnQueueSelected() = 0;
virtual bool OnInfoSelected() = 0;
- virtual bool OnMoreSelected() = 0;
+ virtual bool OnChooseSelected() = 0;
private:
CVideoSelectActionProcessorBase() = delete;
- Action ChooseVideoItemSelectAction() const;
unsigned int ChooseStackItemPartNumber() const;
};
} // namespace KODI::VIDEO::GUILIB
diff --git a/xbmc/video/jobs/VideoLibraryMarkWatchedJob.cpp b/xbmc/video/jobs/VideoLibraryMarkWatchedJob.cpp
index ef2e9253ad..3d4e335545 100644
--- a/xbmc/video/jobs/VideoLibraryMarkWatchedJob.cpp
+++ b/xbmc/video/jobs/VideoLibraryMarkWatchedJob.cpp
@@ -18,7 +18,7 @@
#endif
#include "profiles/ProfileManager.h"
#include "pvr/PVRManager.h"
-#include "pvr/recordings/PVRRecordings.h"
+#include "pvr/guilib/PVRGUIActionsRecordings.h"
#include "settings/SettingsComponent.h"
#include "utils/URIUtils.h"
#include "video/VideoDatabase.h"
@@ -70,8 +70,8 @@ bool CVideoLibraryMarkWatchedJob::Work(CVideoDatabase &db)
continue;
#endif
- if (item->HasPVRRecordingInfoTag() &&
- CServiceBroker::GetPVRManager().Recordings()->MarkWatched(item->GetPVRRecordingInfoTag(), m_mark))
+ if (item->IsPVRRecording() &&
+ CServiceBroker::GetPVRManager().Get<PVR::GUI::Recordings>().MarkWatched(*item, m_mark))
{
CDateTime newLastPlayed;
if (m_mark)
diff --git a/xbmc/video/test/TestVideoUtils.cpp b/xbmc/video/test/TestVideoUtils.cpp
index 579fff0180..9feb832e5b 100644
--- a/xbmc/video/test/TestVideoUtils.cpp
+++ b/xbmc/video/test/TestVideoUtils.cpp
@@ -7,6 +7,7 @@
*/
#include "FileItem.h"
+#include "URL.h"
#include "Util.h"
#include "filesystem/Directory.h"
#include "platform/Filesystem.h"
@@ -22,12 +23,23 @@
using namespace KODI;
namespace fs = KODI::PLATFORM::FILESYSTEM;
+namespace
+{
+
using OptDef = std::pair<std::string, bool>;
class OpticalMediaPathTest : public testing::WithParamInterface<OptDef>, public testing::Test
{
};
+using TrailerDef = std::pair<std::string, std::string>;
+
+class TrailerTest : public testing::WithParamInterface<TrailerDef>, public testing::Test
+{
+};
+
+} // namespace
+
TEST_P(OpticalMediaPathTest, GetOpticalMediaPath)
{
std::error_code ec;
@@ -57,3 +69,53 @@ const auto mediapath_tests = std::array{
};
INSTANTIATE_TEST_SUITE_P(TestVideoUtils, OpticalMediaPathTest, testing::ValuesIn(mediapath_tests));
+
+TEST_P(TrailerTest, FindTrailer)
+{
+ std::string temp_path;
+ if (!GetParam().second.empty())
+ {
+ std::error_code ec;
+ temp_path = fs::create_temp_directory(ec);
+ EXPECT_FALSE(ec);
+ XFILE::CDirectory::Create(temp_path);
+ const std::string file_path = URIUtils::AddFileToFolder(temp_path, GetParam().second);
+ {
+ std::ofstream of(file_path);
+ }
+ URIUtils::AddSlashAtEnd(temp_path);
+ }
+
+ std::string input_path = GetParam().first;
+ if (!temp_path.empty())
+ {
+ StringUtils::Replace(input_path, "#DIRECTORY#", temp_path);
+ StringUtils::Replace(input_path, "#URLENCODED_DIRECTORY#", CURL::Encode(temp_path));
+ }
+
+ CFileItem item(input_path, false);
+ EXPECT_EQ(VIDEO::UTILS::FindTrailer(item),
+ GetParam().second.empty() ? ""
+ : URIUtils::AddFileToFolder(temp_path, GetParam().second));
+
+ if (!temp_path.empty())
+ XFILE::CDirectory::RemoveRecursive(temp_path);
+}
+
+const auto trailer_tests = std::array{
+ TrailerDef{"https://some.where/foo", ""},
+ TrailerDef{"upnp://1/2/3", ""},
+ TrailerDef{"bluray://1", ""},
+ TrailerDef{"pvr://foobar.pvr", ""},
+ TrailerDef{"plugin://plugin.video.foo/foo?param=1", ""},
+ TrailerDef{"dvd://1", ""},
+ TrailerDef{"stack://#DIRECTORY#foo-cd1.avi , #DIRECTORY#foo-cd2.avi", "foo-trailer.mkv"},
+ TrailerDef{"stack://#DIRECTORY#foo-cd1.avi , #DIRECTORY#foo-cd2.avi", "foo-cd1-trailer.avi"},
+ TrailerDef{"stack://#DIRECTORY#foo-cd1.avi , #DIRECTORY#foo-cd2.avi", "movie-trailer.mp4"},
+ TrailerDef{"zip://#URLENCODED_DIRECTORY#bar.zip/bar.avi", "bar-trailer.mov"},
+ TrailerDef{"zip://#URLENCODED_DIRECTORY#bar.zip/bar.mkv", "movie-trailer.ogm"},
+ TrailerDef{"#DIRECTORY#bar.mkv", "bar-trailer.mkv"},
+ TrailerDef{"#DIRECTORY#bar.mkv", "movie-trailer.avi"},
+};
+
+INSTANTIATE_TEST_SUITE_P(TestVideoUtils, TrailerTest, testing::ValuesIn(trailer_tests));
diff --git a/xbmc/video/windows/GUIWindowVideoBase.cpp b/xbmc/video/windows/GUIWindowVideoBase.cpp
index e93b2b02aa..a875ae4bfa 100644
--- a/xbmc/video/windows/GUIWindowVideoBase.cpp
+++ b/xbmc/video/windows/GUIWindowVideoBase.cpp
@@ -605,7 +605,7 @@ protected:
bool OnInfoSelected() override { return m_window.OnItemInfo(*m_item); }
- bool OnMoreSelected() override
+ bool OnChooseSelected() override
{
// window only shows the default version, so no window specific context menu items available
if (m_item->HasVideoVersions() && !m_item->GetVideoInfoTag()->IsDefaultVideoVersion())
diff --git a/xbmc/video/windows/GUIWindowVideoNav.cpp b/xbmc/video/windows/GUIWindowVideoNav.cpp
index 17c51cd46f..d85294bbd9 100644
--- a/xbmc/video/windows/GUIWindowVideoNav.cpp
+++ b/xbmc/video/windows/GUIWindowVideoNav.cpp
@@ -818,13 +818,9 @@ void CGUIWindowVideoNav::GetContextButtons(int itemNumber, CContextButtons &butt
{
buttons.Add(CONTEXT_BUTTON_SCAN, 13349);
}
-
if (node == NODE_TYPE_ACTOR && !dir.IsAllItem(item->GetPath()) && item->m_bIsFolder)
{
- if (StringUtils::StartsWithNoCase(m_vecItems->GetPath(), "videodb://musicvideos")) // mvids
- buttons.Add(CONTEXT_BUTTON_SET_ARTIST_THUMB, 13359);
- else
- buttons.Add(CONTEXT_BUTTON_SET_ACTOR_THUMB, 20403);
+ buttons.Add(CONTEXT_BUTTON_SET_ART, 13511); // Choose art
}
}
@@ -907,19 +903,11 @@ bool CGUIWindowVideoNav::OnContextButton(int itemNumber, CONTEXT_BUTTON button)
}
return true;
}
-
- case CONTEXT_BUTTON_SET_ACTOR_THUMB:
- case CONTEXT_BUTTON_SET_ARTIST_THUMB:
+ case CONTEXT_BUTTON_SET_ART:
{
- std::string type = MediaTypeSeason;
- if (button == CONTEXT_BUTTON_SET_ACTOR_THUMB)
- type = "actor";
- else if (button == CONTEXT_BUTTON_SET_ARTIST_THUMB)
- type = MediaTypeArtist;
-
- bool result = CGUIDialogVideoInfo::ManageVideoItemArtwork(m_vecItems->Get(itemNumber), type);
+ const bool result{
+ CGUIDialogVideoInfo::ChooseAndManageVideoItemArtwork(m_vecItems->Get(itemNumber))};
Refresh();
-
return result;
}
case CONTEXT_BUTTON_GO_TO_ARTIST:
diff --git a/xbmc/view/GUIViewState.cpp b/xbmc/view/GUIViewState.cpp
index 3bd48bd64b..b60f43d64e 100644
--- a/xbmc/view/GUIViewState.cpp
+++ b/xbmc/view/GUIViewState.cpp
@@ -146,6 +146,9 @@ CGUIViewState* CGUIViewState::GetViewState(int windowId, const CFileItemList& it
if (windowId == WINDOW_TV_SEARCH)
return new CGUIViewStateWindowPVRSearch(windowId, items);
+ if (windowId == WINDOW_TV_PROVIDERS)
+ return new CGUIViewStateWindowPVRProviders(windowId, items);
+
if (windowId == WINDOW_RADIO_CHANNELS)
return new CGUIViewStateWindowPVRChannels(windowId, items);
@@ -164,6 +167,9 @@ CGUIViewState* CGUIViewState::GetViewState(int windowId, const CFileItemList& it
if (windowId == WINDOW_RADIO_SEARCH)
return new CGUIViewStateWindowPVRSearch(windowId, items);
+ if (windowId == WINDOW_RADIO_PROVIDERS)
+ return new CGUIViewStateWindowPVRProviders(windowId, items);
+
if (windowId == WINDOW_PICTURES)
return new CGUIViewStateWindowPictures(items);
diff --git a/xbmc/windowing/gbm/drm/DRMObject.cpp b/xbmc/windowing/gbm/drm/DRMObject.cpp
index 5ffce40fa3..99dda24490 100644
--- a/xbmc/windowing/gbm/drm/DRMObject.cpp
+++ b/xbmc/windowing/gbm/drm/DRMObject.cpp
@@ -105,6 +105,25 @@ std::optional<uint64_t> CDRMObject::GetPropertyValue(std::string_view name,
return {};
}
+std::optional<std::span<uint64_t, 2>> CDRMObject::GetRangePropertyLimits(std::string_view name)
+{
+ auto property = std::find_if(m_propsInfo.begin(), m_propsInfo.end(),
+ [&name](const auto& prop) { return prop->name == name; });
+
+ if (property == m_propsInfo.end())
+ return {};
+
+ auto prop = property->get();
+
+ if (!static_cast<bool>(drm_property_type_is(prop, DRM_MODE_PROP_RANGE)))
+ return {};
+
+ if (prop->count_values != 2)
+ return {};
+
+ return std::make_optional<std::span<uint64_t, 2>>(prop->values, 2);
+}
+
bool CDRMObject::SetProperty(const std::string& name, uint64_t value)
{
auto property = std::find_if(m_propsInfo.begin(), m_propsInfo.end(),
@@ -130,3 +149,14 @@ bool CDRMObject::SupportsProperty(const std::string& name)
return false;
}
+
+std::optional<bool> CDRMObject::IsPropertyImmutable(std::string_view name)
+{
+ auto property = std::find_if(m_propsInfo.begin(), m_propsInfo.end(),
+ [&name](const auto& prop) { return prop->name == name; });
+
+ if (property == m_propsInfo.end())
+ return {};
+
+ return static_cast<bool>(drm_property_type_is(property->get(), DRM_MODE_PROP_IMMUTABLE));
+}
diff --git a/xbmc/windowing/gbm/drm/DRMObject.h b/xbmc/windowing/gbm/drm/DRMObject.h
index c4200b1a86..39ba28a004 100644
--- a/xbmc/windowing/gbm/drm/DRMObject.h
+++ b/xbmc/windowing/gbm/drm/DRMObject.h
@@ -12,6 +12,7 @@
#include <cstdint>
#include <memory>
#include <optional>
+#include <span>
#include <string_view>
#include <vector>
@@ -40,6 +41,8 @@ public:
bool SetProperty(const std::string& name, uint64_t value);
bool SupportsProperty(const std::string& name);
+ std::optional<bool> IsPropertyImmutable(std::string_view name);
+ std::optional<std::span<uint64_t, 2>> GetRangePropertyLimits(std::string_view name);
protected:
explicit CDRMObject(int fd);
diff --git a/xbmc/windowing/gbm/drm/DRMUtils.cpp b/xbmc/windowing/gbm/drm/DRMUtils.cpp
index 3dd4ee9783..22db758ab6 100644
--- a/xbmc/windowing/gbm/drm/DRMUtils.cpp
+++ b/xbmc/windowing/gbm/drm/DRMUtils.cpp
@@ -181,77 +181,130 @@ bool CDRMUtils::FindPreferredMode()
return true;
}
-bool CDRMUtils::FindPlanes()
+bool CDRMUtils::FindGuiPlane()
{
- for (size_t i = 0; i < m_crtcs.size(); i++)
- {
- if (!(m_encoder->GetPossibleCrtcs() & (1 << i)))
+ /* find the gui plane which support ARGB and 8bit or 10 bit XRGB
+ * prefer the one which does not support NV12, because it can be re-used in future for video
+ * prefer the highest id number because they are listed on top where zpos is not available
+ * and use the gui plane crtc as the crtc
+ * */
+ CDRMPlane* gui_plane_nv12{nullptr};
+ CDRMPlane* gui_plane{nullptr};
+ CDRMCrtc* gui_crtc_nv12{nullptr};
+ CDRMCrtc* gui_crtc{nullptr};
+
+ for (size_t crtc_offset = 0; crtc_offset < m_crtcs.size(); crtc_offset++)
+ {
+ if (!(m_encoder->GetPossibleCrtcs() & (1 << crtc_offset)))
continue;
- auto videoPlane = std::find_if(m_planes.begin(), m_planes.end(), [&i](auto& plane) {
- if (plane->GetPossibleCrtcs() & (1 << i))
- {
- return plane->SupportsFormat(DRM_FORMAT_NV12);
- }
- return false;
- });
-
- uint32_t videoPlaneId{0};
-
- if (videoPlane != m_planes.end())
- videoPlaneId = videoPlane->get()->GetPlaneId();
-
- auto guiPlane =
- std::find_if(m_planes.begin(), m_planes.end(), [&i, &videoPlaneId](auto& plane) {
- if (plane->GetPossibleCrtcs() & (1 << i))
- {
- return (plane->GetPlaneId() != videoPlaneId &&
- (videoPlaneId == 0 || plane->SupportsFormat(DRM_FORMAT_ARGB8888)) &&
- (plane->SupportsFormat(DRM_FORMAT_XRGB2101010) ||
- plane->SupportsFormat(DRM_FORMAT_XRGB8888)));
- }
- return false;
- });
-
- if (videoPlane != m_planes.end() && guiPlane != m_planes.end())
+ for (auto& plane : m_planes)
{
- m_crtc = m_crtcs[i].get();
- m_video_plane = videoPlane->get();
- m_gui_plane = guiPlane->get();
- break;
- }
+ if (!(plane.get()->GetPossibleCrtcs() & (1 << crtc_offset)))
+ continue;
- if (guiPlane != m_planes.end())
- {
- if (!m_crtc && m_encoder->GetCrtcId() == m_crtcs[i]->GetCrtcId())
+ if (plane.get()->SupportsFormat(DRM_FORMAT_ARGB8888) &&
+ (plane.get()->SupportsFormat(DRM_FORMAT_XRGB2101010) ||
+ plane.get()->SupportsFormat(DRM_FORMAT_XRGB8888)))
{
- m_crtc = m_crtcs[i].get();
- m_gui_plane = guiPlane->get();
- m_video_plane = nullptr;
+ if (plane.get()->SupportsFormat(DRM_FORMAT_NV12) &&
+ (gui_plane_nv12 == nullptr || gui_plane_nv12->GetId() < plane.get()->GetId()))
+ {
+ gui_plane_nv12 = plane.get();
+ gui_crtc_nv12 = m_crtcs[crtc_offset].get();
+ }
+ else if (!plane.get()->SupportsFormat(DRM_FORMAT_NV12) &&
+ (gui_plane == nullptr || gui_plane->GetId() < plane.get()->GetId()))
+ {
+ gui_plane = plane.get();
+ gui_crtc = m_crtcs[crtc_offset].get();
+ }
}
}
}
- CLog::Log(LOGINFO, "CDRMUtils::{} - using crtc: {}", __FUNCTION__, m_crtc->GetCrtcId());
-
- // video plane may not be available
- if (m_video_plane)
- CLog::Log(LOGDEBUG, "CDRMUtils::{} - using video plane {}", __FUNCTION__,
- m_video_plane->GetPlaneId());
-
- if (m_gui_plane->SupportsFormat(DRM_FORMAT_XRGB2101010))
+ // fallback to NV12 supporting plane
+ if (gui_plane == nullptr)
{
- m_gui_plane->SetFormat(DRM_FORMAT_XRGB2101010);
- CLog::Log(LOGDEBUG, "CDRMUtils::{} - using 10bit gui plane {}", __FUNCTION__,
- m_gui_plane->GetPlaneId());
+ gui_crtc = gui_crtc_nv12;
+ gui_plane = gui_plane_nv12;
}
- else
+
+ if (gui_plane != nullptr)
{
- m_gui_plane->SetFormat(DRM_FORMAT_XRGB8888);
- CLog::Log(LOGDEBUG, "CDRMUtils::{} - using gui plane {}", __FUNCTION__,
- m_gui_plane->GetPlaneId());
+ m_crtc = gui_crtc;
+ m_gui_plane = gui_plane;
+
+ CLog::Log(LOGINFO, "CDRMUtils::{} - using crtc: {}", __FUNCTION__, m_crtc->GetCrtcId());
+ if (m_gui_plane->SupportsFormat(DRM_FORMAT_XRGB2101010))
+ {
+ m_gui_plane->SetFormat(DRM_FORMAT_XRGB2101010);
+ CLog::Log(LOGDEBUG, "CDRMUtils::{} - using 10bit gui plane {}", __FUNCTION__,
+ m_gui_plane->GetPlaneId());
+ }
+ else
+ {
+ m_gui_plane->SetFormat(DRM_FORMAT_XRGB8888);
+ CLog::Log(LOGDEBUG, "CDRMUtils::{} - using gui plane {}", __FUNCTION__,
+ m_gui_plane->GetPlaneId());
+ }
+ return true;
}
+ CLog::Log(LOGERROR, "CDRMUtils::{} - Can not find a GUI plane", __FUNCTION__);
+ return false;
+}
+
+bool CDRMUtils::FindVideoPlane(uint32_t format, uint64_t modifier)
+{
+ bool supports_zpos = m_gui_plane->SupportsProperty("zpos");
+ bool zpos_immutable = supports_zpos && m_gui_plane->IsPropertyImmutable("zpos").value();
+
+ auto crtc_offset = std::distance(
+ m_crtcs.begin(),
+ std::find_if(m_crtcs.begin(), m_crtcs.end(),
+ [this](auto& crtc) { return crtc->GetCrtcId() == m_crtc->GetCrtcId(); }));
+
+ auto guiplane_id = m_gui_plane->GetId();
+ auto videoPlane = std::find_if(m_planes.begin(), m_planes.end(),
+ [&crtc_offset, &format, &modifier, &guiplane_id](auto& plane)
+ {
+ if (plane->GetPossibleCrtcs() & (1 << crtc_offset))
+ {
+ return (guiplane_id != plane->GetPlaneId() &&
+ plane->SupportsFormatAndModifier(format, modifier));
+ }
+ return false;
+ });
+
+ if (videoPlane == m_planes.end())
+ {
+ CLog::Log(LOGERROR,
+ "CDRMUtils::{} - Can not find a Video Plane plane for format {}, modifier {}",
+ __FUNCTION__, format, modifier);
+ return false;
+ }
+
+ m_video_plane = videoPlane->get();
+ CLog::Log(LOGDEBUG, "CDRMUtils::{} - using video plane {}", __FUNCTION__,
+ m_video_plane->GetPlaneId());
+
+ if (!supports_zpos || zpos_immutable)
+ return true;
+
+ // re-sort the video and gui planes
+ auto limits = m_gui_plane->GetRangePropertyLimits("zpos");
+
+ if (!limits)
+ return true;
+
+ m_gui_plane->SetProperty("zpos", limits.value()[1]);
+ m_video_plane->SetProperty("zpos", limits.value()[0]);
+ CLog::Log(LOGDEBUG, "CDRMUtils::{} - gui plane id,zpos: {}, {}", __FUNCTION__,
+ m_gui_plane->GetId(), limits.value()[1]);
+ CLog::Log(LOGDEBUG, "CDRMUtils::{} - video plane id,zpos: {}, {}", __FUNCTION__,
+ m_video_plane->GetId(), limits.value()[0]);
+
return true;
}
@@ -467,9 +520,11 @@ bool CDRMUtils::InitDrm()
if (!FindCrtc())
return false;
- if (!FindPlanes())
+ if (!FindGuiPlane())
return false;
+ FindVideoPlane(DRM_FORMAT_NV12, DRM_FORMAT_MOD_LINEAR);
+
if (!FindPreferredMode())
return false;
diff --git a/xbmc/windowing/gbm/drm/DRMUtils.h b/xbmc/windowing/gbm/drm/DRMUtils.h
index f92f716fc4..b99a6dc4fe 100644
--- a/xbmc/windowing/gbm/drm/DRMUtils.h
+++ b/xbmc/windowing/gbm/drm/DRMUtils.h
@@ -64,6 +64,8 @@ public:
static uint32_t FourCCWithoutAlpha(uint32_t fourcc);
void SetInFenceFd(int fd) { m_inFenceFd = fd; }
+ bool FindVideoPlane(uint32_t format, uint64_t modifier);
+ bool FindGuiPlane();
int TakeOutFenceFd()
{
int fd{-1};
@@ -89,13 +91,13 @@ protected:
int m_inFenceFd{-1};
int m_outFenceFd{-1};
+ std::vector<std::unique_ptr<CDRMCrtc>> m_crtcs;
std::vector<std::unique_ptr<CDRMPlane>> m_planes;
private:
bool FindConnector();
bool FindEncoder();
bool FindCrtc();
- bool FindPlanes();
bool FindPreferredMode();
bool RestoreOriginalMode();
RESOLUTION_INFO GetResolutionInfo(drmModeModeInfoPtr mode);
@@ -106,7 +108,6 @@ private:
std::vector<std::unique_ptr<CDRMConnector>> m_connectors;
std::vector<std::unique_ptr<CDRMEncoder>> m_encoders;
- std::vector<std::unique_ptr<CDRMCrtc>> m_crtcs;
};
}
diff --git a/xbmc/windowing/windows/WinSystemWin32.cpp b/xbmc/windowing/windows/WinSystemWin32.cpp
index 74bacc0f0a..4f408f9b38 100644
--- a/xbmc/windowing/windows/WinSystemWin32.cpp
+++ b/xbmc/windowing/windows/WinSystemWin32.cpp
@@ -16,6 +16,7 @@
#include "cores/AudioEngine/AESinkFactory.h"
#include "cores/AudioEngine/Sinks/AESinkDirectSound.h"
#include "cores/AudioEngine/Sinks/AESinkWASAPI.h"
+#include "cores/AudioEngine/Sinks/AESinkXaudio.h"
#include "filesystem/File.h"
#include "filesystem/SpecialProtocol.h"
#include "messaging/ApplicationMessenger.h"
@@ -75,6 +76,7 @@ CWinSystemWin32::CWinSystemWin32()
AE::CAESinkFactory::ClearSinks();
CAESinkDirectSound::Register();
CAESinkWASAPI::Register();
+ CAESinkXAudio::Register();
CScreenshotSurfaceWindows::Register();
if (CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_bScanIRServer)
diff --git a/xbmc/windows/GUIMediaWindow.cpp b/xbmc/windows/GUIMediaWindow.cpp
index afb236c205..9c1f8e48df 100644
--- a/xbmc/windows/GUIMediaWindow.cpp
+++ b/xbmc/windows/GUIMediaWindow.cpp
@@ -47,6 +47,8 @@
#include "input/actions/ActionIDs.h"
#include "interfaces/generic/ScriptInvocationManager.h"
#include "messaging/helpers/DialogOKHelper.h"
+#include "music/MusicFileItemClassify.h"
+#include "music/tags/MusicInfoTag.h"
#include "network/Network.h"
#include "playlists/PlayList.h"
#include "profiles/ProfileManager.h"
@@ -769,6 +771,12 @@ bool CGUIMediaWindow::GetDirectory(const std::string &strDirectory, CFileItemLis
m_history.RemoveParentPath();
}
+ // Store parent path along with item as parent path cannot safely be calculated from item's path.
+ for (const auto& item : items)
+ {
+ item->SetProperty("ParentPath", m_vecItems->GetPath());
+ }
+
// update the view state's reference to the current items
m_guiState.reset(CGUIViewState::GetViewState(GetID(), items));
@@ -1540,6 +1548,19 @@ bool CGUIMediaWindow::OnPlayAndQueueMedia(const CFileItemPtr& item, const std::s
std::distance(playlist.begin(), std::find_if(playlist.begin(), playlist.end(),
[&item](const std::shared_ptr<CFileItem>& i)
{ return i->GetPath() == item->GetPath(); }));
+ /* For .mka albums, all tracks are in the same file so using path as above will always play the
+ * first track. Use the track and disk number to ensure we start playback on the correct track.
+ * This only applies to mka or m4b items played back via files view. Music library takes
+ * a different play path.
+ */
+ if (MUSIC::IsAudioBook(*item))
+ mediaToPlay = std::distance(
+ playlist.begin(), std::find_if(playlist.begin(), playlist.end(),
+ [&item](const std::shared_ptr<CFileItem>& i)
+ {
+ return i->GetMusicInfoTag()->GetTrackAndDiscNumber() ==
+ item->GetMusicInfoTag()->GetTrackAndDiscNumber();
+ }));
// Add to playlist
CServiceBroker::GetPlaylistPlayer().ClearPlaylist(playlistId);
@@ -1743,8 +1764,6 @@ bool CGUIMediaWindow::OnPopupMenu(int itemIdx)
if (!item)
return false;
- item->SetProperty("ParentPath", m_vecItems->GetPath());
-
CContextButtons buttons;
//Add items from plugin
diff --git a/xbmc/windows/GUIWindowScreensaverDim.cpp b/xbmc/windows/GUIWindowScreensaverDim.cpp
index 7bc360f18d..2a3fdb1473 100644
--- a/xbmc/windows/GUIWindowScreensaverDim.cpp
+++ b/xbmc/windows/GUIWindowScreensaverDim.cpp
@@ -68,6 +68,11 @@ void CGUIWindowScreensaverDim::Process(unsigned int currentTime, CDirtyRegionLis
void CGUIWindowScreensaverDim::Render()
{
+ RENDER_ORDER renderOrder = CServiceBroker::GetWinSystem()->GetGfxContext().GetRenderOrder();
+ if (renderOrder == RENDER_ORDER_FRONT_TO_BACK)
+ return;
+ else if (renderOrder == RENDER_ORDER_BACK_TO_FRONT)
+ CServiceBroker::GetWinSystem()->GetGfxContext().SetRenderOrder(RENDER_ORDER_ALL_BACK_TO_FRONT);
// draw a translucent black quad - fading is handled by the window animation
KODI::UTILS::COLOR::Color color =
(static_cast<KODI::UTILS::COLOR::Color>(m_dimLevel * 2.55f) & 0xff) << 24;
@@ -75,4 +80,5 @@ void CGUIWindowScreensaverDim::Render()
CRect rect(0, 0, (float)CServiceBroker::GetWinSystem()->GetGfxContext().GetWidth(), (float)CServiceBroker::GetWinSystem()->GetGfxContext().GetHeight());
CGUITexture::DrawQuad(rect, color);
CGUIDialog::Render();
+ CServiceBroker::GetWinSystem()->GetGfxContext().SetRenderOrder(renderOrder);
}