diff options
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 Binary files differindex 5acfff911e..fb17dae43c 100644 --- a/addons/skin.estuary/extras/backgrounds/pattern1.png +++ b/addons/skin.estuary/extras/backgrounds/pattern1.png diff --git a/addons/skin.estuary/extras/backgrounds/pattern2.png b/addons/skin.estuary/extras/backgrounds/pattern2.png Binary files differindex c6af819c11..b6c4b61215 100644 --- a/addons/skin.estuary/extras/backgrounds/pattern2.png +++ b/addons/skin.estuary/extras/backgrounds/pattern2.png diff --git a/addons/skin.estuary/extras/backgrounds/pattern3.png b/addons/skin.estuary/extras/backgrounds/pattern3.png Binary files differindex 75588b9d95..2ac483467b 100644 --- a/addons/skin.estuary/extras/backgrounds/pattern3.png +++ b/addons/skin.estuary/extras/backgrounds/pattern3.png diff --git a/addons/skin.estuary/extras/backgrounds/pattern4.png b/addons/skin.estuary/extras/backgrounds/pattern4.png Binary files differindex 3d2737144c..8e2ba5f100 100644 --- a/addons/skin.estuary/extras/backgrounds/pattern4.png +++ b/addons/skin.estuary/extras/backgrounds/pattern4.png diff --git a/addons/skin.estuary/extras/backgrounds/pattern5.png b/addons/skin.estuary/extras/backgrounds/pattern5.png Binary files differindex 487e597904..a0552bfbe3 100644 --- a/addons/skin.estuary/extras/backgrounds/pattern5.png +++ b/addons/skin.estuary/extras/backgrounds/pattern5.png diff --git a/addons/skin.estuary/extras/backgrounds/pattern6.png b/addons/skin.estuary/extras/backgrounds/pattern6.png Binary files differindex 2ba32fab1a..425e434b59 100644 --- a/addons/skin.estuary/extras/backgrounds/pattern6.png +++ b/addons/skin.estuary/extras/backgrounds/pattern6.png diff --git a/addons/skin.estuary/extras/backgrounds/pattern7.png b/addons/skin.estuary/extras/backgrounds/pattern7.png Binary files differindex 6204b3a8f3..c55427735e 100644 --- a/addons/skin.estuary/extras/backgrounds/pattern7.png +++ b/addons/skin.estuary/extras/backgrounds/pattern7.png diff --git a/addons/skin.estuary/media/DefaultPVRProvider.png b/addons/skin.estuary/media/DefaultPVRProvider.png Binary files differnew file mode 100644 index 0000000000..b5dcd1a8cc --- /dev/null +++ b/addons/skin.estuary/media/DefaultPVRProvider.png diff --git a/addons/skin.estuary/media/DefaultPVRProviders.png b/addons/skin.estuary/media/DefaultPVRProviders.png Binary files differnew file mode 100644 index 0000000000..b9f7008f93 --- /dev/null +++ b/addons/skin.estuary/media/DefaultPVRProviders.png diff --git a/addons/skin.estuary/media/flags/aspectratio/1.00.png b/addons/skin.estuary/media/flags/aspectratio/1.00.png Binary files differdeleted file mode 100644 index eb41c15f7f..0000000000 --- a/addons/skin.estuary/media/flags/aspectratio/1.00.png +++ /dev/null diff --git a/addons/skin.estuary/media/flags/aspectratio/1.19.png b/addons/skin.estuary/media/flags/aspectratio/1.19.png Binary files differdeleted file mode 100644 index 80289ae466..0000000000 --- a/addons/skin.estuary/media/flags/aspectratio/1.19.png +++ /dev/null diff --git a/addons/skin.estuary/media/flags/aspectratio/1.33.png b/addons/skin.estuary/media/flags/aspectratio/1.33.png Binary files differdeleted file mode 100644 index 43e079495a..0000000000 --- a/addons/skin.estuary/media/flags/aspectratio/1.33.png +++ /dev/null diff --git a/addons/skin.estuary/media/flags/aspectratio/1.37.png b/addons/skin.estuary/media/flags/aspectratio/1.37.png Binary files differdeleted file mode 100644 index e86c6f7a1d..0000000000 --- a/addons/skin.estuary/media/flags/aspectratio/1.37.png +++ /dev/null diff --git a/addons/skin.estuary/media/flags/aspectratio/1.66.png b/addons/skin.estuary/media/flags/aspectratio/1.66.png Binary files differdeleted file mode 100644 index 80ca726bcc..0000000000 --- a/addons/skin.estuary/media/flags/aspectratio/1.66.png +++ /dev/null diff --git a/addons/skin.estuary/media/flags/aspectratio/1.78.png b/addons/skin.estuary/media/flags/aspectratio/1.78.png Binary files differdeleted file mode 100644 index 01d70b832d..0000000000 --- a/addons/skin.estuary/media/flags/aspectratio/1.78.png +++ /dev/null diff --git a/addons/skin.estuary/media/flags/aspectratio/1.85.png b/addons/skin.estuary/media/flags/aspectratio/1.85.png Binary files differdeleted file mode 100644 index 479804fde6..0000000000 --- a/addons/skin.estuary/media/flags/aspectratio/1.85.png +++ /dev/null diff --git a/addons/skin.estuary/media/flags/aspectratio/2.00.png b/addons/skin.estuary/media/flags/aspectratio/2.00.png Binary files differdeleted file mode 100644 index cd8ff2569f..0000000000 --- a/addons/skin.estuary/media/flags/aspectratio/2.00.png +++ /dev/null diff --git a/addons/skin.estuary/media/flags/aspectratio/2.20.png b/addons/skin.estuary/media/flags/aspectratio/2.20.png Binary files differdeleted file mode 100644 index d0cebe276e..0000000000 --- a/addons/skin.estuary/media/flags/aspectratio/2.20.png +++ /dev/null diff --git a/addons/skin.estuary/media/flags/aspectratio/2.35.png b/addons/skin.estuary/media/flags/aspectratio/2.35.png Binary files differdeleted file mode 100644 index cacb088692..0000000000 --- a/addons/skin.estuary/media/flags/aspectratio/2.35.png +++ /dev/null diff --git a/addons/skin.estuary/media/flags/aspectratio/2.40.png b/addons/skin.estuary/media/flags/aspectratio/2.40.png Binary files differdeleted file mode 100644 index 35aff17350..0000000000 --- a/addons/skin.estuary/media/flags/aspectratio/2.40.png +++ /dev/null diff --git a/addons/skin.estuary/media/flags/aspectratio/2.55.png b/addons/skin.estuary/media/flags/aspectratio/2.55.png Binary files differdeleted file mode 100644 index a592e04fbe..0000000000 --- a/addons/skin.estuary/media/flags/aspectratio/2.55.png +++ /dev/null diff --git a/addons/skin.estuary/media/flags/aspectratio/2.76.png b/addons/skin.estuary/media/flags/aspectratio/2.76.png Binary files differdeleted file mode 100644 index 051e671b6a..0000000000 --- a/addons/skin.estuary/media/flags/aspectratio/2.76.png +++ /dev/null diff --git a/addons/skin.estuary/media/flags/audiochannel/0.png b/addons/skin.estuary/media/flags/audiochannel/0.png Binary files differdeleted file mode 100644 index a5be90821a..0000000000 --- a/addons/skin.estuary/media/flags/audiochannel/0.png +++ /dev/null diff --git a/addons/skin.estuary/media/flags/audiochannel/1.png b/addons/skin.estuary/media/flags/audiochannel/1.png Binary files differdeleted file mode 100644 index 87f541c99c..0000000000 --- a/addons/skin.estuary/media/flags/audiochannel/1.png +++ /dev/null diff --git a/addons/skin.estuary/media/flags/audiochannel/10.png b/addons/skin.estuary/media/flags/audiochannel/10.png Binary files differdeleted file mode 100644 index 49ed3ec3ab..0000000000 --- a/addons/skin.estuary/media/flags/audiochannel/10.png +++ /dev/null diff --git a/addons/skin.estuary/media/flags/audiochannel/2.png b/addons/skin.estuary/media/flags/audiochannel/2.png Binary files differdeleted file mode 100644 index c7102b6c22..0000000000 --- a/addons/skin.estuary/media/flags/audiochannel/2.png +++ /dev/null diff --git a/addons/skin.estuary/media/flags/audiochannel/3.png b/addons/skin.estuary/media/flags/audiochannel/3.png Binary files differdeleted file mode 100644 index 5f9b0cca6d..0000000000 --- a/addons/skin.estuary/media/flags/audiochannel/3.png +++ /dev/null diff --git a/addons/skin.estuary/media/flags/audiochannel/4.png b/addons/skin.estuary/media/flags/audiochannel/4.png Binary files differdeleted file mode 100644 index 67c04c0e03..0000000000 --- a/addons/skin.estuary/media/flags/audiochannel/4.png +++ /dev/null diff --git a/addons/skin.estuary/media/flags/audiochannel/5.png b/addons/skin.estuary/media/flags/audiochannel/5.png Binary files differdeleted file mode 100644 index a7f5f895ac..0000000000 --- a/addons/skin.estuary/media/flags/audiochannel/5.png +++ /dev/null diff --git a/addons/skin.estuary/media/flags/audiochannel/6.png b/addons/skin.estuary/media/flags/audiochannel/6.png Binary files differdeleted file mode 100644 index d35e28d8e4..0000000000 --- a/addons/skin.estuary/media/flags/audiochannel/6.png +++ /dev/null diff --git a/addons/skin.estuary/media/flags/audiochannel/7.png b/addons/skin.estuary/media/flags/audiochannel/7.png Binary files differdeleted file mode 100644 index e026a26753..0000000000 --- a/addons/skin.estuary/media/flags/audiochannel/7.png +++ /dev/null diff --git a/addons/skin.estuary/media/flags/audiochannel/8.png b/addons/skin.estuary/media/flags/audiochannel/8.png Binary files differdeleted file mode 100644 index b32fc36ab4..0000000000 --- a/addons/skin.estuary/media/flags/audiochannel/8.png +++ /dev/null diff --git a/addons/skin.estuary/media/flags/audiocodec/aac.png b/addons/skin.estuary/media/flags/audiocodec/aac.png Binary files differdeleted file mode 100644 index 55e81409b7..0000000000 --- a/addons/skin.estuary/media/flags/audiocodec/aac.png +++ /dev/null diff --git a/addons/skin.estuary/media/flags/audiocodec/aac_latm.png b/addons/skin.estuary/media/flags/audiocodec/aac_latm.png Binary files differdeleted file mode 100644 index 55e81409b7..0000000000 --- a/addons/skin.estuary/media/flags/audiocodec/aac_latm.png +++ /dev/null diff --git a/addons/skin.estuary/media/flags/audiocodec/ac3.png b/addons/skin.estuary/media/flags/audiocodec/ac3.png Binary files differdeleted file mode 100644 index d01a87739e..0000000000 --- a/addons/skin.estuary/media/flags/audiocodec/ac3.png +++ /dev/null diff --git a/addons/skin.estuary/media/flags/audiocodec/aif.png b/addons/skin.estuary/media/flags/audiocodec/aif.png Binary files differdeleted file mode 100644 index ce4677858b..0000000000 --- a/addons/skin.estuary/media/flags/audiocodec/aif.png +++ /dev/null diff --git a/addons/skin.estuary/media/flags/audiocodec/aifc.png b/addons/skin.estuary/media/flags/audiocodec/aifc.png Binary files differdeleted file mode 100644 index ed9a26c6bb..0000000000 --- a/addons/skin.estuary/media/flags/audiocodec/aifc.png +++ /dev/null diff --git a/addons/skin.estuary/media/flags/audiocodec/aiff.png b/addons/skin.estuary/media/flags/audiocodec/aiff.png Binary files differdeleted file mode 100644 index ce4677858b..0000000000 --- a/addons/skin.estuary/media/flags/audiocodec/aiff.png +++ /dev/null diff --git a/addons/skin.estuary/media/flags/audiocodec/alac.png b/addons/skin.estuary/media/flags/audiocodec/alac.png Binary files differdeleted file mode 100644 index a49527cf6a..0000000000 --- a/addons/skin.estuary/media/flags/audiocodec/alac.png +++ /dev/null diff --git a/addons/skin.estuary/media/flags/audiocodec/ape.png b/addons/skin.estuary/media/flags/audiocodec/ape.png Binary files differdeleted file mode 100644 index 94e01abf20..0000000000 --- a/addons/skin.estuary/media/flags/audiocodec/ape.png +++ /dev/null diff --git a/addons/skin.estuary/media/flags/audiocodec/avc.png b/addons/skin.estuary/media/flags/audiocodec/avc.png Binary files differdeleted file mode 100644 index 91aa179870..0000000000 --- a/addons/skin.estuary/media/flags/audiocodec/avc.png +++ /dev/null diff --git a/addons/skin.estuary/media/flags/audiocodec/cdda.png b/addons/skin.estuary/media/flags/audiocodec/cdda.png Binary files differdeleted file mode 100644 index 3f257dd567..0000000000 --- a/addons/skin.estuary/media/flags/audiocodec/cdda.png +++ /dev/null diff --git a/addons/skin.estuary/media/flags/audiocodec/dca.png b/addons/skin.estuary/media/flags/audiocodec/dca.png Binary files differdeleted file mode 100644 index 1dc52ec67f..0000000000 --- a/addons/skin.estuary/media/flags/audiocodec/dca.png +++ /dev/null diff --git a/addons/skin.estuary/media/flags/audiocodec/dolbydigital.png b/addons/skin.estuary/media/flags/audiocodec/dolbydigital.png Binary files differdeleted file mode 100644 index d01a87739e..0000000000 --- a/addons/skin.estuary/media/flags/audiocodec/dolbydigital.png +++ /dev/null diff --git a/addons/skin.estuary/media/flags/audiocodec/dts.png b/addons/skin.estuary/media/flags/audiocodec/dts.png Binary files differdeleted file mode 100644 index 1dc52ec67f..0000000000 --- a/addons/skin.estuary/media/flags/audiocodec/dts.png +++ /dev/null diff --git a/addons/skin.estuary/media/flags/audiocodec/dtshd_hra.png b/addons/skin.estuary/media/flags/audiocodec/dtshd_hra.png Binary files differdeleted file mode 100644 index 53ffb9002b..0000000000 --- a/addons/skin.estuary/media/flags/audiocodec/dtshd_hra.png +++ /dev/null diff --git a/addons/skin.estuary/media/flags/audiocodec/dtshd_ma.png b/addons/skin.estuary/media/flags/audiocodec/dtshd_ma.png Binary files differdeleted file mode 100644 index f20256e591..0000000000 --- a/addons/skin.estuary/media/flags/audiocodec/dtshd_ma.png +++ /dev/null diff --git a/addons/skin.estuary/media/flags/audiocodec/dtsma.png b/addons/skin.estuary/media/flags/audiocodec/dtsma.png Binary files differdeleted file mode 100644 index f20256e591..0000000000 --- a/addons/skin.estuary/media/flags/audiocodec/dtsma.png +++ /dev/null diff --git a/addons/skin.estuary/media/flags/audiocodec/eac3.png b/addons/skin.estuary/media/flags/audiocodec/eac3.png Binary files differdeleted file mode 100644 index d01a87739e..0000000000 --- a/addons/skin.estuary/media/flags/audiocodec/eac3.png +++ /dev/null diff --git a/addons/skin.estuary/media/flags/audiocodec/flac.png b/addons/skin.estuary/media/flags/audiocodec/flac.png Binary files differdeleted file mode 100644 index f173541ebd..0000000000 --- a/addons/skin.estuary/media/flags/audiocodec/flac.png +++ /dev/null diff --git a/addons/skin.estuary/media/flags/audiocodec/mp1.png b/addons/skin.estuary/media/flags/audiocodec/mp1.png Binary files differdeleted file mode 100644 index d3065f1b95..0000000000 --- a/addons/skin.estuary/media/flags/audiocodec/mp1.png +++ /dev/null diff --git a/addons/skin.estuary/media/flags/audiocodec/mp2.png b/addons/skin.estuary/media/flags/audiocodec/mp2.png Binary files differdeleted file mode 100644 index ed4e21eefe..0000000000 --- a/addons/skin.estuary/media/flags/audiocodec/mp2.png +++ /dev/null diff --git a/addons/skin.estuary/media/flags/audiocodec/mp3.png b/addons/skin.estuary/media/flags/audiocodec/mp3.png Binary files differdeleted file mode 100644 index 258d161f5a..0000000000 --- a/addons/skin.estuary/media/flags/audiocodec/mp3.png +++ /dev/null diff --git a/addons/skin.estuary/media/flags/audiocodec/mp3float.png b/addons/skin.estuary/media/flags/audiocodec/mp3float.png Binary files differdeleted file mode 100644 index 258d161f5a..0000000000 --- a/addons/skin.estuary/media/flags/audiocodec/mp3float.png +++ /dev/null diff --git a/addons/skin.estuary/media/flags/audiocodec/ogg.png b/addons/skin.estuary/media/flags/audiocodec/ogg.png Binary files differdeleted file mode 100644 index 208200a63e..0000000000 --- a/addons/skin.estuary/media/flags/audiocodec/ogg.png +++ /dev/null diff --git a/addons/skin.estuary/media/flags/audiocodec/opus.png b/addons/skin.estuary/media/flags/audiocodec/opus.png Binary files differdeleted file mode 100644 index df856a6d57..0000000000 --- a/addons/skin.estuary/media/flags/audiocodec/opus.png +++ /dev/null diff --git a/addons/skin.estuary/media/flags/audiocodec/pcm.png b/addons/skin.estuary/media/flags/audiocodec/pcm.png Binary files differdeleted file mode 100644 index 0c7a5bdeee..0000000000 --- a/addons/skin.estuary/media/flags/audiocodec/pcm.png +++ /dev/null diff --git a/addons/skin.estuary/media/flags/audiocodec/pcm_bluray.png b/addons/skin.estuary/media/flags/audiocodec/pcm_bluray.png Binary files differdeleted file mode 100644 index 30b4f8b138..0000000000 --- a/addons/skin.estuary/media/flags/audiocodec/pcm_bluray.png +++ /dev/null diff --git a/addons/skin.estuary/media/flags/audiocodec/pcm_s16le.png b/addons/skin.estuary/media/flags/audiocodec/pcm_s16le.png Binary files differdeleted file mode 100644 index dc514806e3..0000000000 --- a/addons/skin.estuary/media/flags/audiocodec/pcm_s16le.png +++ /dev/null diff --git a/addons/skin.estuary/media/flags/audiocodec/pcm_s24le.png b/addons/skin.estuary/media/flags/audiocodec/pcm_s24le.png Binary files differdeleted file mode 100644 index 81ceacc06d..0000000000 --- a/addons/skin.estuary/media/flags/audiocodec/pcm_s24le.png +++ /dev/null diff --git a/addons/skin.estuary/media/flags/audiocodec/truehd.png b/addons/skin.estuary/media/flags/audiocodec/truehd.png Binary files differdeleted file mode 100644 index 4bcf8c1fda..0000000000 --- a/addons/skin.estuary/media/flags/audiocodec/truehd.png +++ /dev/null diff --git a/addons/skin.estuary/media/flags/audiocodec/vorbis.png b/addons/skin.estuary/media/flags/audiocodec/vorbis.png Binary files differdeleted file mode 100644 index e7ec2c5361..0000000000 --- a/addons/skin.estuary/media/flags/audiocodec/vorbis.png +++ /dev/null diff --git a/addons/skin.estuary/media/flags/audiocodec/wav.png b/addons/skin.estuary/media/flags/audiocodec/wav.png Binary files differdeleted file mode 100644 index 76cd02d3fc..0000000000 --- a/addons/skin.estuary/media/flags/audiocodec/wav.png +++ /dev/null diff --git a/addons/skin.estuary/media/flags/audiocodec/wavpack.png b/addons/skin.estuary/media/flags/audiocodec/wavpack.png Binary files differdeleted file mode 100644 index 6501af9942..0000000000 --- a/addons/skin.estuary/media/flags/audiocodec/wavpack.png +++ /dev/null diff --git a/addons/skin.estuary/media/flags/audiocodec/wma.png b/addons/skin.estuary/media/flags/audiocodec/wma.png Binary files differdeleted file mode 100644 index 20093c15d5..0000000000 --- a/addons/skin.estuary/media/flags/audiocodec/wma.png +++ /dev/null diff --git a/addons/skin.estuary/media/flags/audiocodec/wmapro.png b/addons/skin.estuary/media/flags/audiocodec/wmapro.png Binary files differdeleted file mode 100644 index 20093c15d5..0000000000 --- a/addons/skin.estuary/media/flags/audiocodec/wmapro.png +++ /dev/null diff --git a/addons/skin.estuary/media/flags/audiocodec/wmav2.png b/addons/skin.estuary/media/flags/audiocodec/wmav2.png Binary files differdeleted file mode 100644 index 20093c15d5..0000000000 --- a/addons/skin.estuary/media/flags/audiocodec/wmav2.png +++ /dev/null diff --git a/addons/skin.estuary/media/flags/rds/rds.png b/addons/skin.estuary/media/flags/rds/rds.png Binary files differdeleted file mode 100644 index e5de4ccfcf..0000000000 --- a/addons/skin.estuary/media/flags/rds/rds.png +++ /dev/null diff --git a/addons/skin.estuary/media/flags/videocodec/av1.png b/addons/skin.estuary/media/flags/videocodec/av1.png Binary files differdeleted file mode 100644 index f594b9ea9c..0000000000 --- a/addons/skin.estuary/media/flags/videocodec/av1.png +++ /dev/null diff --git a/addons/skin.estuary/media/flags/videocodec/avc1.png b/addons/skin.estuary/media/flags/videocodec/avc1.png Binary files differdeleted file mode 100644 index 78da5d8936..0000000000 --- a/addons/skin.estuary/media/flags/videocodec/avc1.png +++ /dev/null diff --git a/addons/skin.estuary/media/flags/videocodec/bluray.png b/addons/skin.estuary/media/flags/videocodec/bluray.png Binary files differdeleted file mode 100644 index b8fe922c6d..0000000000 --- a/addons/skin.estuary/media/flags/videocodec/bluray.png +++ /dev/null diff --git a/addons/skin.estuary/media/flags/videocodec/div3.png b/addons/skin.estuary/media/flags/videocodec/div3.png Binary files differdeleted file mode 100644 index 65a9a6517c..0000000000 --- a/addons/skin.estuary/media/flags/videocodec/div3.png +++ /dev/null diff --git a/addons/skin.estuary/media/flags/videocodec/divx.png b/addons/skin.estuary/media/flags/videocodec/divx.png Binary files differdeleted file mode 100644 index 65a9a6517c..0000000000 --- a/addons/skin.estuary/media/flags/videocodec/divx.png +++ /dev/null diff --git a/addons/skin.estuary/media/flags/videocodec/dvd.png b/addons/skin.estuary/media/flags/videocodec/dvd.png Binary files differdeleted file mode 100644 index 9e9bd97ab0..0000000000 --- a/addons/skin.estuary/media/flags/videocodec/dvd.png +++ /dev/null diff --git a/addons/skin.estuary/media/flags/videocodec/dx50.png b/addons/skin.estuary/media/flags/videocodec/dx50.png Binary files differdeleted file mode 100644 index 65a9a6517c..0000000000 --- a/addons/skin.estuary/media/flags/videocodec/dx50.png +++ /dev/null diff --git a/addons/skin.estuary/media/flags/videocodec/flv.png b/addons/skin.estuary/media/flags/videocodec/flv.png Binary files differdeleted file mode 100644 index 8b1b5775d5..0000000000 --- a/addons/skin.estuary/media/flags/videocodec/flv.png +++ /dev/null diff --git a/addons/skin.estuary/media/flags/videocodec/h264.png b/addons/skin.estuary/media/flags/videocodec/h264.png Binary files differdeleted file mode 100644 index 64cfa04ec9..0000000000 --- a/addons/skin.estuary/media/flags/videocodec/h264.png +++ /dev/null diff --git a/addons/skin.estuary/media/flags/videocodec/hddvd.png b/addons/skin.estuary/media/flags/videocodec/hddvd.png Binary files differdeleted file mode 100644 index 4a170a91b6..0000000000 --- a/addons/skin.estuary/media/flags/videocodec/hddvd.png +++ /dev/null diff --git a/addons/skin.estuary/media/flags/videocodec/hdmv.png b/addons/skin.estuary/media/flags/videocodec/hdmv.png Binary files differdeleted file mode 100644 index b8fe922c6d..0000000000 --- a/addons/skin.estuary/media/flags/videocodec/hdmv.png +++ /dev/null diff --git a/addons/skin.estuary/media/flags/videocodec/hev1.png b/addons/skin.estuary/media/flags/videocodec/hev1.png Binary files differdeleted file mode 100644 index 1e1d3c186b..0000000000 --- a/addons/skin.estuary/media/flags/videocodec/hev1.png +++ /dev/null diff --git a/addons/skin.estuary/media/flags/videocodec/hevc.png b/addons/skin.estuary/media/flags/videocodec/hevc.png Binary files differdeleted file mode 100644 index 1e1d3c186b..0000000000 --- a/addons/skin.estuary/media/flags/videocodec/hevc.png +++ /dev/null diff --git a/addons/skin.estuary/media/flags/videocodec/hvc1.png b/addons/skin.estuary/media/flags/videocodec/hvc1.png Binary files differdeleted file mode 100644 index 1e1d3c186b..0000000000 --- a/addons/skin.estuary/media/flags/videocodec/hvc1.png +++ /dev/null diff --git a/addons/skin.estuary/media/flags/videocodec/mp4v.png b/addons/skin.estuary/media/flags/videocodec/mp4v.png Binary files differdeleted file mode 100644 index da9d967bb8..0000000000 --- a/addons/skin.estuary/media/flags/videocodec/mp4v.png +++ /dev/null diff --git a/addons/skin.estuary/media/flags/videocodec/mpeg1.png b/addons/skin.estuary/media/flags/videocodec/mpeg1.png Binary files differdeleted file mode 100644 index 8b210cf2d1..0000000000 --- a/addons/skin.estuary/media/flags/videocodec/mpeg1.png +++ /dev/null diff --git a/addons/skin.estuary/media/flags/videocodec/mpeg1video.png b/addons/skin.estuary/media/flags/videocodec/mpeg1video.png Binary files differdeleted file mode 100644 index 8b210cf2d1..0000000000 --- a/addons/skin.estuary/media/flags/videocodec/mpeg1video.png +++ /dev/null diff --git a/addons/skin.estuary/media/flags/videocodec/mpeg2.png b/addons/skin.estuary/media/flags/videocodec/mpeg2.png Binary files differdeleted file mode 100644 index f46483b765..0000000000 --- a/addons/skin.estuary/media/flags/videocodec/mpeg2.png +++ /dev/null diff --git a/addons/skin.estuary/media/flags/videocodec/mpeg2video.png b/addons/skin.estuary/media/flags/videocodec/mpeg2video.png Binary files differdeleted file mode 100644 index f46483b765..0000000000 --- a/addons/skin.estuary/media/flags/videocodec/mpeg2video.png +++ /dev/null diff --git a/addons/skin.estuary/media/flags/videocodec/mpeg4.png b/addons/skin.estuary/media/flags/videocodec/mpeg4.png Binary files differdeleted file mode 100644 index da9d967bb8..0000000000 --- a/addons/skin.estuary/media/flags/videocodec/mpeg4.png +++ /dev/null diff --git a/addons/skin.estuary/media/flags/videocodec/theora.png b/addons/skin.estuary/media/flags/videocodec/theora.png Binary files differdeleted file mode 100644 index 8f1af4bb94..0000000000 --- a/addons/skin.estuary/media/flags/videocodec/theora.png +++ /dev/null diff --git a/addons/skin.estuary/media/flags/videocodec/tv.png b/addons/skin.estuary/media/flags/videocodec/tv.png Binary files differdeleted file mode 100644 index b7cb357442..0000000000 --- a/addons/skin.estuary/media/flags/videocodec/tv.png +++ /dev/null diff --git a/addons/skin.estuary/media/flags/videocodec/vc-1.png b/addons/skin.estuary/media/flags/videocodec/vc-1.png Binary files differdeleted file mode 100644 index 843497f6a9..0000000000 --- a/addons/skin.estuary/media/flags/videocodec/vc-1.png +++ /dev/null diff --git a/addons/skin.estuary/media/flags/videocodec/vc1.png b/addons/skin.estuary/media/flags/videocodec/vc1.png Binary files differdeleted file mode 100644 index 843497f6a9..0000000000 --- a/addons/skin.estuary/media/flags/videocodec/vc1.png +++ /dev/null diff --git a/addons/skin.estuary/media/flags/videocodec/vhs.png b/addons/skin.estuary/media/flags/videocodec/vhs.png Binary files differdeleted file mode 100644 index a1a4ff3198..0000000000 --- a/addons/skin.estuary/media/flags/videocodec/vhs.png +++ /dev/null diff --git a/addons/skin.estuary/media/flags/videocodec/vp8.png b/addons/skin.estuary/media/flags/videocodec/vp8.png Binary files differdeleted file mode 100644 index d9ad357901..0000000000 --- a/addons/skin.estuary/media/flags/videocodec/vp8.png +++ /dev/null diff --git a/addons/skin.estuary/media/flags/videocodec/vp9.png b/addons/skin.estuary/media/flags/videocodec/vp9.png Binary files differdeleted file mode 100644 index c09a28cb10..0000000000 --- a/addons/skin.estuary/media/flags/videocodec/vp9.png +++ /dev/null diff --git a/addons/skin.estuary/media/flags/videocodec/wmv.png b/addons/skin.estuary/media/flags/videocodec/wmv.png Binary files differdeleted file mode 100644 index 34a7cedf3b..0000000000 --- a/addons/skin.estuary/media/flags/videocodec/wmv.png +++ /dev/null diff --git a/addons/skin.estuary/media/flags/videocodec/wmv3.png b/addons/skin.estuary/media/flags/videocodec/wmv3.png Binary files differdeleted file mode 100644 index 34a7cedf3b..0000000000 --- a/addons/skin.estuary/media/flags/videocodec/wmv3.png +++ /dev/null diff --git a/addons/skin.estuary/media/flags/videocodec/wvc1.png b/addons/skin.estuary/media/flags/videocodec/wvc1.png Binary files differdeleted file mode 100644 index 843497f6a9..0000000000 --- a/addons/skin.estuary/media/flags/videocodec/wvc1.png +++ /dev/null diff --git a/addons/skin.estuary/media/flags/videocodec/xvid.png b/addons/skin.estuary/media/flags/videocodec/xvid.png Binary files differdeleted file mode 100644 index b835bf6873..0000000000 --- a/addons/skin.estuary/media/flags/videocodec/xvid.png +++ /dev/null diff --git a/addons/skin.estuary/media/flags/videohdr/dolbyvision.png b/addons/skin.estuary/media/flags/videohdr/dolbyvision.png Binary files differdeleted file mode 100644 index 5875eb80e7..0000000000 --- a/addons/skin.estuary/media/flags/videohdr/dolbyvision.png +++ /dev/null diff --git a/addons/skin.estuary/media/flags/videohdr/hdr10.png b/addons/skin.estuary/media/flags/videohdr/hdr10.png Binary files differdeleted file mode 100644 index e4b671fe12..0000000000 --- a/addons/skin.estuary/media/flags/videohdr/hdr10.png +++ /dev/null diff --git a/addons/skin.estuary/media/flags/videohdr/hlg.png b/addons/skin.estuary/media/flags/videohdr/hlg.png Binary files differdeleted file mode 100644 index a8bc078d70..0000000000 --- a/addons/skin.estuary/media/flags/videohdr/hlg.png +++ /dev/null diff --git a/addons/skin.estuary/media/flags/videoresolution/1080.png b/addons/skin.estuary/media/flags/videoresolution/1080.png Binary files differdeleted file mode 100644 index fa076b8d2d..0000000000 --- a/addons/skin.estuary/media/flags/videoresolution/1080.png +++ /dev/null diff --git a/addons/skin.estuary/media/flags/videoresolution/3D.png b/addons/skin.estuary/media/flags/videoresolution/3D.png Binary files differdeleted file mode 100644 index 9b6c26254f..0000000000 --- a/addons/skin.estuary/media/flags/videoresolution/3D.png +++ /dev/null diff --git a/addons/skin.estuary/media/flags/videoresolution/480.png b/addons/skin.estuary/media/flags/videoresolution/480.png Binary files differdeleted file mode 100644 index 66deb31b80..0000000000 --- a/addons/skin.estuary/media/flags/videoresolution/480.png +++ /dev/null diff --git a/addons/skin.estuary/media/flags/videoresolution/4K.png b/addons/skin.estuary/media/flags/videoresolution/4K.png Binary files differdeleted file mode 100644 index f3d13bdccf..0000000000 --- a/addons/skin.estuary/media/flags/videoresolution/4K.png +++ /dev/null diff --git a/addons/skin.estuary/media/flags/videoresolution/540.png b/addons/skin.estuary/media/flags/videoresolution/540.png Binary files differdeleted file mode 100644 index dd6c3823d1..0000000000 --- a/addons/skin.estuary/media/flags/videoresolution/540.png +++ /dev/null diff --git a/addons/skin.estuary/media/flags/videoresolution/576.png b/addons/skin.estuary/media/flags/videoresolution/576.png Binary files differdeleted file mode 100644 index 8a96be926c..0000000000 --- a/addons/skin.estuary/media/flags/videoresolution/576.png +++ /dev/null diff --git a/addons/skin.estuary/media/flags/videoresolution/720.png b/addons/skin.estuary/media/flags/videoresolution/720.png Binary files differdeleted file mode 100644 index 0b6406004a..0000000000 --- a/addons/skin.estuary/media/flags/videoresolution/720.png +++ /dev/null diff --git a/addons/skin.estuary/media/flags/videoresolution/8K.png b/addons/skin.estuary/media/flags/videoresolution/8K.png Binary files differdeleted file mode 100644 index de46f91e9b..0000000000 --- a/addons/skin.estuary/media/flags/videoresolution/8K.png +++ /dev/null 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__, [×](const AddonInstance* addon) { - return addon->toAddon->GetStreamTimes(addon, times); - }); + return DoAddonCall(__func__, [×](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); } |