diff options
44 files changed, 1398 insertions, 156 deletions
diff --git a/addons/resource.language.en_gb/resources/strings.po b/addons/resource.language.en_gb/resources/strings.po index 46d1d6174f..2ec1005854 100644 --- a/addons/resource.language.en_gb/resources/strings.po +++ b/addons/resource.language.en_gb/resources/strings.po @@ -2196,6 +2196,10 @@ msgctxt "#459" msgid "Subs" msgstr "" +#: xbmc/video/PlayerController.cpp +#: xbmc/video/dialogs/GUIDialogAudioSettings.cpp +#: addons/skin.estuary/xml/DialogPlayerProcessInfo.xml +#: xbmc/video/guilib/VideoStreamSelectHelper.cpp msgctxt "#460" msgid "Audio stream" msgstr "" @@ -2204,8 +2208,10 @@ msgctxt "#461" msgid "[active]" msgstr "" +#: xbmc/video/dialogs/GUIDialogSubtitleSettings.cpp +#: xbmc/video/guilib/VideoStreamSelectHelper.cpp msgctxt "#462" -msgid "Subtitle" +msgid "Subtitle stream" msgstr "" msgctxt "#463" @@ -22827,6 +22833,7 @@ msgstr "" #. Label for an option to select the video stream to play if current video has more than one video stream #: xbmc/video/dialogs/GUIDialogVideoSettings.cpp #: xbmc/video/PlayerController.cpp +#: xbmc/video/guilib/VideoStreamSelectHelper.cpp msgctxt "#38031" msgid "Video stream" msgstr "" diff --git a/addons/skin.estuary/language/resource.language.en_gb/strings.po b/addons/skin.estuary/language/resource.language.en_gb/strings.po index 229f0b95e5..d6f2bc6c04 100644 --- a/addons/skin.estuary/language/resource.language.en_gb/strings.po +++ b/addons/skin.estuary/language/resource.language.en_gb/strings.po @@ -502,7 +502,12 @@ msgctxt "#31107" msgid "WideList" msgstr "" -#empty strings from id 31108 to 31109 +#empty strings from id 31108 to 31108 + +#: /xml/VideoOSD.xml +msgctxt "#31109" +msgid "Video streams" +msgstr "" #: /xml/Home.xml msgctxt "#31110" @@ -516,7 +521,7 @@ msgstr "" #: /xml/VideoOSD.xml msgctxt "#31112" -msgid "Toggle audio stream" +msgid "Audio streams" msgstr "" #: /xml/Custom_1107_SearchDialog.xml @@ -531,7 +536,7 @@ msgstr "" #: /xml/VideoOSD.xml msgctxt "#31115" -msgid "Toggle subtitle" +msgid "Subtitles streams" msgstr "" #: /xml/Includes_Home.xml @@ -924,3 +929,9 @@ msgstr "" msgctxt "#31611" msgid "System" msgstr "" + +#: /xml/Includes_DialogSelect.xml +#. Audio channels +msgctxt "#31612" +msgid "channels" +msgstr "" diff --git a/addons/skin.estuary/media/icons/menumarks/star.png b/addons/skin.estuary/media/icons/menumarks/star.png Binary files differnew file mode 100644 index 0000000000..da55f2ea2a --- /dev/null +++ b/addons/skin.estuary/media/icons/menumarks/star.png diff --git a/addons/skin.estuary/media/icons/menumarks/tick.png b/addons/skin.estuary/media/icons/menumarks/tick.png Binary files differnew file mode 100644 index 0000000000..c5a89ca6f8 --- /dev/null +++ b/addons/skin.estuary/media/icons/menumarks/tick.png diff --git a/addons/skin.estuary/xml/DialogSelect.xml b/addons/skin.estuary/xml/DialogSelect.xml index 308c14b743..cafeb37998 100644 --- a/addons/skin.estuary/xml/DialogSelect.xml +++ b/addons/skin.estuary/xml/DialogSelect.xml @@ -4,7 +4,10 @@ <include>Animation_DialogPopupOpenClose</include> <depth>DepthOSD</depth> <controls> - <include condition="![Window.IsActive(selectvideoversion) | Window.IsActive(selectvideoextra) | Window.IsActive(gamesaves) | Window.IsActive(gamestretchmode) | Window.IsActive(gamevideofilter) | Window.IsActive(gamevideorotation) | Window.IsActive(ingamesaves)]">DefaultDialogSelectLayout</include> + <include condition="![Window.IsActive(videoselectdialog) | Window.IsActive(audioselectdialog) | Window.IsActive(subtitleselectdialog) | Window.IsActive(selectvideoversion) | Window.IsActive(selectvideoextra) | Window.IsActive(gamesaves) | Window.IsActive(gamestretchmode) | Window.IsActive(gamevideofilter) | Window.IsActive(gamevideorotation) | Window.IsActive(ingamesaves)]">DefaultDialogSelectLayout</include> + <include condition="Window.IsActive(videoselectdialog)">VideoDialogSelectVideoLayout</include> + <include condition="Window.IsActive(audioselectdialog)">VideoDialogSelectAudioLayout</include> + <include condition="Window.IsActive(subtitleselectdialog)">VideoDialogSelectSubtitleLayout</include> <include condition="Window.IsActive(gamesaves)">GameDialogSelectSaveLayout</include> <include condition="Window.IsActive(gamevideofilter)">GameDialogSelectFilterLayout</include> <include condition="Window.IsActive(gamestretchmode)">GameDialogSelectViewLayout</include> diff --git a/addons/skin.estuary/xml/Includes_DialogSelect.xml b/addons/skin.estuary/xml/Includes_DialogSelect.xml index 7ca4f0d459..72049e69dd 100644 --- a/addons/skin.estuary/xml/Includes_DialogSelect.xml +++ b/addons/skin.estuary/xml/Includes_DialogSelect.xml @@ -194,6 +194,550 @@ </control> </control> </include> + <include name="VideoDialogSelectVideoLayout"> + <!-- + Available ListItem video stream properties: + stream.description, stream.codec, stream.language, stream.resolution, stream.bitrate, + stream.fps, stream.is3d, stream.stereomode, stream.hdrtype, stream.isdefault, stream.isforced, + stream.ishearingimpaired, stream.isvisualimpaired + --> + <control type="group"> + <centertop>50%</centertop> + <centerleft>50%</centerleft> + <height>750</height> + <width>1220</width> + <include content="DialogBackgroundCommons"> + <param name="width" value="1220" /> + <param name="height" value="750" /> + <param name="header_label" value="" /> + <param name="header_id" value="1" /> + </include> + <control type="image"> + <left>0</left> + <top>80</top> + <width>920</width> + <bottom>2</bottom> + <texture border="40">buttons/dialogbutton-nofo.png</texture> + </control> + <control type="list" id="3"> + <left>20</left> + <top>100</top> + <width>880</width> + <bottom>20</bottom> + <onup>3</onup> + <ondown>3</ondown> + <onleft>9001</onleft> + <onright>61</onright> + <pagecontrol>61</pagecontrol> + <scrolltime>200</scrolltime> + <include content="DefaultSimpleListLayout"> + <param name="width" value="880" /> + <param name="list_id" value="3" /> + </include> + </control> + <control type="list" id="6"> + <left>20</left> + <top>100</top> + <width>880</width> + <bottom>20</bottom> + <onup>6</onup> + <ondown>6</ondown> + <onleft>9001</onleft> + <onright>61</onright> + <pagecontrol>61</pagecontrol> + <scrolltime>200</scrolltime> + <itemlayout height="100" width="880"> + <control type="image"> + <left>10</left> + <top>16</top> + <width>30</width> + <height>30</height> + <aspectratio>keep</aspectratio> + <aligny>center</aligny> + <texture>icons/menumarks/tick.png</texture> + <visible>ListItem.IsSelected</visible> + </control> + <control type="label"> + <left>50</left> + <top>0</top> + <right>80</right> + <height>60</height> + <font>font14</font> + <aligny>center</aligny> + <label>$INFO[ListItem.Property(stream.codec)]$INFO[ListItem.Property(stream.resolution),$COMMA ]$INFO[ListItem.Property(stream.bitrate),$COMMA , kbps]$INFO[ListItem.Property(stream.fps),$COMMA , fps]$VAR[VideoStreamDialogVideoItemLabelVar, - [LIGHT],[/LIGHT]]</label> + </control> + <control type="textbox"> + <left>50</left> + <top>50</top> + <right>80</right> + <height>67</height> + <font>font12</font> + <textcolor>grey</textcolor> + <label>$INFO[ListItem.Property(stream.description),, ]$INFO[ListItem.Property(stream.language),(,)]</label> + </control> + <control type="image"> + <left>830</left> + <top>16</top> + <width>30</width> + <height>30</height> + <aspectratio>keep</aspectratio> + <aligny>center</aligny> + <texture>icons/menumarks/star.png</texture> + <visible>ListItem.Property(stream.isdefault)</visible> + </control> + </itemlayout> + <focusedlayout height="100" width="880"> + <control type="image"> + <left>0</left> + <top>0</top> + <right>0</right> + <bottom>0</bottom> + <texture colordiffuse="button_focus">lists/focus.png</texture> + <animation effect="fade" start="100" end="50" time="0">UnFocus</animation> + </control> + <control type="image"> + <left>10</left> + <top>16</top> + <width>30</width> + <height>30</height> + <aspectratio>keep</aspectratio> + <aligny>center</aligny> + <texture>icons/menumarks/tick.png</texture> + <visible>ListItem.IsSelected</visible> + </control> + <control type="label"> + <left>50</left> + <top>0</top> + <right>80</right> + <height>60</height> + <aligny>center</aligny> + <scroll>true</scroll> + <font>font14</font> + <label>$INFO[ListItem.Property(stream.codec)]$INFO[ListItem.Property(stream.resolution),$COMMA ]$INFO[ListItem.Property(stream.bitrate),$COMMA , kbps]$INFO[ListItem.Property(stream.fps),$COMMA , fps]$VAR[VideoStreamDialogVideoItemLabelVar, - [LIGHT],[/LIGHT]]</label> + </control> + <control type="textbox"> + <left>50</left> + <top>50</top> + <right>80</right> + <height>67</height> + <font>font12</font> + <label>$INFO[ListItem.Property(stream.description),, ]$INFO[ListItem.Property(stream.language),(,)]</label> + </control> + <control type="image"> + <left>830</left> + <top>16</top> + <width>30</width> + <height>30</height> + <aspectratio>keep</aspectratio> + <aligny>center</aligny> + <texture>icons/menumarks/star.png</texture> + <visible>ListItem.Property(stream.isdefault)</visible> + </control> + </focusedlayout> + </control> + <control type="scrollbar" id="61"> + <left>910</left> + <top>100</top> + <width>12</width> + <bottom>20</bottom> + <onleft condition="Control.IsVisible(3)">3</onleft> + <onleft condition="Control.IsVisible(6)">6</onleft> + <onright>9001</onright> + <orientation>vertical</orientation> + </control> + <control type="label"> + <left>925</left> + <bottom>10</bottom> + <width>275</width> + <height>35</height> + <font>font12</font> + <align>right</align> + <textcolor>grey</textcolor> + <label>$VAR[SelectLabel]</label> + </control> + <control type="grouplist" id="9001"> + <left>920</left> + <top>80</top> + <onleft>61</onleft> + <itemgap>dialogbuttons_itemgap</itemgap> + <onright>3</onright> + <include content="DefaultDialogButton"> + <param name="id" value="5" /> + <param name="label" value="" /> + </include> + <include content="DefaultDialogButton"> + <param name="id" value="8" /> + <param name="label" value="" /> + </include> + <include content="DefaultDialogButton"> + <param name="id" value="7" /> + <param name="label" value="$LOCALIZE[222]" /> + </include> + </control> + </control> + </include> + <include name="VideoDialogSelectAudioLayout"> + <!-- + Available ListItem audio stream properties: + stream.description, stream.codec, stream.codecdesc, stream.channels, + stream.isdefault, stream.isforced, stream.isoriginal, stream.ishearingimpaired, stream.isvisualimpaired + --> + <control type="group"> + <centertop>50%</centertop> + <centerleft>50%</centerleft> + <height>750</height> + <width>1220</width> + <include content="DialogBackgroundCommons"> + <param name="width" value="1220" /> + <param name="height" value="750" /> + <param name="header_label" value="" /> + <param name="header_id" value="1" /> + </include> + <control type="image"> + <left>0</left> + <top>80</top> + <width>920</width> + <bottom>2</bottom> + <texture border="40">buttons/dialogbutton-nofo.png</texture> + </control> + <control type="list" id="3"> + <left>20</left> + <top>100</top> + <width>880</width> + <bottom>20</bottom> + <onup>3</onup> + <ondown>3</ondown> + <onleft>9001</onleft> + <onright>61</onright> + <pagecontrol>61</pagecontrol> + <scrolltime>200</scrolltime> + <include content="DefaultSimpleListLayout"> + <param name="width" value="880" /> + <param name="list_id" value="3" /> + </include> + </control> + <control type="list" id="6"> + <left>20</left> + <top>100</top> + <width>880</width> + <bottom>20</bottom> + <onup>6</onup> + <ondown>6</ondown> + <onleft>9001</onleft> + <onright>61</onright> + <pagecontrol>61</pagecontrol> + <scrolltime>200</scrolltime> + <itemlayout height="130" width="880"> + <control type="image"> + <left>10</left> + <top>16</top> + <width>30</width> + <height>30</height> + <aspectratio>keep</aspectratio> + <aligny>center</aligny> + <texture>icons/menumarks/tick.png</texture> + <visible>ListItem.IsSelected</visible> + </control> +- <control type="label"> + <left>50</left> + <top>0</top> + <right>80</right> + <height>60</height> + <font>font14</font> + <aligny>center</aligny> + <label>$INFO[ListItem.Label]$VAR[VideoStreamDialogAudioItemLabelVar, - [LIGHT],[/LIGHT]]</label> + </control> + <control type="textbox"> + <left>50</left> + <top>50</top> + <right>80</right> + <height>80</height> + <font>font12</font> + <textcolor>grey</textcolor> + <label>$INFO[ListItem.Property(stream.codecdesc),, - ]$INFO[ListItem.Property(stream.channels),$LOCALIZE[31612]: ][CR]$INFO[ListItem.Property(stream.description)]</label> + </control> + <control type="image"> + <left>830</left> + <top>16</top> + <width>30</width> + <height>30</height> + <aspectratio>keep</aspectratio> + <aligny>center</aligny> + <texture>icons/menumarks/star.png</texture> + <visible>ListItem.Property(stream.isdefault)</visible> + </control> + </itemlayout> + <focusedlayout height="130" width="880"> + <control type="image"> + <left>0</left> + <top>0</top> + <right>0</right> + <bottom>0</bottom> + <texture colordiffuse="button_focus">lists/focus.png</texture> + <animation effect="fade" start="100" end="50" time="0">UnFocus</animation> + </control> + <control type="image"> + <left>10</left> + <top>16</top> + <width>30</width> + <height>30</height> + <aspectratio>keep</aspectratio> + <aligny>center</aligny> + <texture>icons/menumarks/tick.png</texture> + <visible>ListItem.IsSelected</visible> + </control> + <control type="label"> + <left>50</left> + <top>0</top> + <right>80</right> + <height>60</height> + <aligny>center</aligny> + <scroll>true</scroll> + <font>font14</font> + <label>$INFO[ListItem.Label]$VAR[VideoStreamDialogAudioItemLabelVar, - [LIGHT],[/LIGHT]]</label> + </control> + <control type="textbox"> + <left>50</left> + <top>50</top> + <right>80</right> + <height>80</height> + <font>font12</font> + <label>$INFO[ListItem.Property(stream.codecdesc),, - ]$INFO[ListItem.Property(stream.channels),$LOCALIZE[31612]: ][CR]$INFO[ListItem.Property(stream.description)]</label> + </control> + <control type="image"> + <left>830</left> + <top>16</top> + <width>30</width> + <height>30</height> + <aspectratio>keep</aspectratio> + <aligny>center</aligny> + <texture>icons/menumarks/star.png</texture> + <visible>ListItem.Property(stream.isdefault)</visible> + </control> + </focusedlayout> + </control> + <control type="scrollbar" id="61"> + <left>910</left> + <top>100</top> + <width>12</width> + <bottom>20</bottom> + <onleft condition="Control.IsVisible(3)">3</onleft> + <onleft condition="Control.IsVisible(6)">6</onleft> + <onright>9001</onright> + <orientation>vertical</orientation> + </control> + <control type="label"> + <left>925</left> + <bottom>10</bottom> + <width>275</width> + <height>35</height> + <font>font12</font> + <align>right</align> + <textcolor>grey</textcolor> + <label>$VAR[SelectLabel]</label> + </control> + <control type="grouplist" id="9001"> + <left>920</left> + <top>80</top> + <onleft>61</onleft> + <itemgap>dialogbuttons_itemgap</itemgap> + <onright>3</onright> + <include content="DefaultDialogButton"> + <param name="id" value="5" /> + <param name="label" value="" /> + </include> + <include content="DefaultDialogButton"> + <param name="id" value="8" /> + <param name="label" value="" /> + </include> + <include content="DefaultDialogButton"> + <param name="id" value="7" /> + <param name="label" value="$LOCALIZE[222]" /> + </include> + </control> + </control> + </include> + <include name="VideoDialogSelectSubtitleLayout"> + <!-- + Available ListItem subtitle stream properties: + stream.description, stream.codec, stream.isdefault, stream.isforced, stream.isoriginal, + stream.ishearingimpaired, stream.isvisualimpaired, stream.isexternal + --> + <control type="group"> + <centertop>50%</centertop> + <centerleft>50%</centerleft> + <height>750</height> + <width>1220</width> + <include content="DialogBackgroundCommons"> + <param name="width" value="1220" /> + <param name="height" value="750" /> + <param name="header_label" value="" /> + <param name="header_id" value="1" /> + </include> + <control type="image"> + <left>0</left> + <top>80</top> + <width>920</width> + <bottom>2</bottom> + <texture border="40">buttons/dialogbutton-nofo.png</texture> + </control> + <control type="list" id="3"> + <left>20</left> + <top>100</top> + <width>880</width> + <bottom>20</bottom> + <onup>3</onup> + <ondown>3</ondown> + <onleft>9001</onleft> + <onright>61</onright> + <pagecontrol>61</pagecontrol> + <scrolltime>200</scrolltime> + <include content="DefaultSimpleListLayout"> + <param name="width" value="880" /> + <param name="list_id" value="3" /> + </include> + </control> + <control type="list" id="6"> + <left>20</left> + <top>100</top> + <width>880</width> + <bottom>20</bottom> + <onup>6</onup> + <ondown>6</ondown> + <onleft>9001</onleft> + <onright>61</onright> + <pagecontrol>61</pagecontrol> + <scrolltime>200</scrolltime> + <itemlayout height="100" width="880"> + <control type="image"> + <left>10</left> + <top>16</top> + <width>30</width> + <height>30</height> + <aspectratio>keep</aspectratio> + <aligny>center</aligny> + <texture>icons/menumarks/tick.png</texture> + <visible>ListItem.IsSelected</visible> + </control> + <control type="label"> + <left>50</left> + <top>0</top> + <right>80</right> + <height>60</height> + <font>font14</font> + <aligny>center</aligny> + <label>$INFO[ListItem.Label]$VAR[VideoStreamDialogSubItemLabelVar, - [LIGHT],[/LIGHT]]</label> + </control> + <control type="textbox"> + <left>50</left> + <top>50</top> + <right>80</right> + <height>67</height> + <font>font12</font> + <textcolor>grey</textcolor> + <label>$INFO[ListItem.Property(stream.description)]</label> + </control> + <control type="image"> + <left>830</left> + <top>16</top> + <width>30</width> + <height>30</height> + <aspectratio>keep</aspectratio> + <aligny>center</aligny> + <texture>icons/menumarks/star.png</texture> + <visible>ListItem.Property(stream.isdefault)</visible> + </control> + </itemlayout> + <focusedlayout height="100" width="880"> + <control type="image"> + <left>0</left> + <top>0</top> + <right>0</right> + <bottom>0</bottom> + <texture colordiffuse="button_focus">lists/focus.png</texture> + <animation effect="fade" start="100" end="50" time="0">UnFocus</animation> + </control> + <control type="image"> + <left>10</left> + <top>16</top> + <width>30</width> + <height>30</height> + <aspectratio>keep</aspectratio> + <aligny>center</aligny> + <texture>icons/menumarks/tick.png</texture> + <visible>ListItem.IsSelected</visible> + </control> + <control type="label"> + <left>50</left> + <top>0</top> + <right>80</right> + <height>60</height> + <aligny>center</aligny> + <scroll>true</scroll> + <font>font14</font> + <label>$INFO[ListItem.Label]$VAR[VideoStreamDialogSubItemLabelVar, - [LIGHT],[/LIGHT]]</label> + </control> + <control type="textbox"> + <left>50</left> + <top>50</top> + <right>80</right> + <height>67</height> + <font>font12</font> + <label>$INFO[ListItem.Property(stream.description)]</label> + </control> + <control type="image"> + <left>830</left> + <top>16</top> + <width>30</width> + <height>30</height> + <aspectratio>keep</aspectratio> + <aligny>center</aligny> + <texture>icons/menumarks/star.png</texture> + <visible>ListItem.Property(stream.isdefault)</visible> + </control> + </focusedlayout> + </control> + <control type="scrollbar" id="61"> + <left>910</left> + <top>100</top> + <width>12</width> + <bottom>20</bottom> + <onleft condition="Control.IsVisible(3)">3</onleft> + <onleft condition="Control.IsVisible(6)">6</onleft> + <onright>9001</onright> + <orientation>vertical</orientation> + </control> + <control type="label"> + <left>925</left> + <bottom>10</bottom> + <width>275</width> + <height>35</height> + <font>font12</font> + <align>right</align> + <textcolor>grey</textcolor> + <label>$VAR[SelectLabel]</label> + </control> + <control type="grouplist" id="9001"> + <left>920</left> + <top>80</top> + <onleft>61</onleft> + <itemgap>dialogbuttons_itemgap</itemgap> + <onright>3</onright> + <include content="DefaultDialogButton"> + <param name="id" value="5" /> + <param name="label" value="" /> + </include> + <include content="DefaultDialogButton"> + <param name="id" value="8" /> + <param name="label" value="" /> + </include> + <include content="DefaultDialogButton"> + <param name="id" value="7" /> + <param name="label" value="$LOCALIZE[222]" /> + </include> + </control> + </control> + </include> <include name="GameDialogSelectSaveLayout"> <control type="group"> <centertop>50%</centertop> diff --git a/addons/skin.estuary/xml/Includes_SettingsDialog.xml b/addons/skin.estuary/xml/Includes_SettingsDialog.xml index 7d8aa3cab9..1dedc8b9a8 100644 --- a/addons/skin.estuary/xml/Includes_SettingsDialog.xml +++ b/addons/skin.estuary/xml/Includes_SettingsDialog.xml @@ -25,19 +25,25 @@ <onclick>ActivateWindow(osdcmssettings)</onclick> <visible>System.HasCMS</visible> </control> + <control type="button" id="22106"> + <include>DialogSettingButton</include> + <label>$LOCALIZE[31115]</label> + <label2>$VAR[ActiveVideoPlayerSubtitleLanguage]</label2> + <onclick>DialogSelectSubtitle</onclick> + <visible>VideoPlayer.HasSubtitles</visible> + </control> <control type="button" id="22105"> <include>DialogSettingButton</include> <label>$LOCALIZE[31112]</label> <label2>[B]$INFO[VideoPlayer.AudioLanguage][/B]</label2> - <onclick>AudioNextLanguage</onclick> + <onclick>DialogSelectAudio</onclick> <visible>Integer.IsGreater(VideoPlayer.AudioStreamCount,1)</visible> </control> - <control type="button" id="22106"> + <control type="button" id="22110"> <include>DialogSettingButton</include> - <label>$LOCALIZE[31115]</label> - <label2>$VAR[ActiveVideoPlayerSubtitleLanguage]</label2> - <onclick>NextSubtitle</onclick> - <visible>VideoPlayer.HasSubtitles</visible> + <label>$LOCALIZE[31109]</label> + <onclick>DialogSelectVideo</onclick> + <visible>Integer.IsGreater(VideoPlayer.VideoStreamCount,1)</visible> </control> <control type="button" id="22107"> <include>DialogSettingButton</include> diff --git a/addons/skin.estuary/xml/Variables.xml b/addons/skin.estuary/xml/Variables.xml index fb320882e8..49382061a9 100644 --- a/addons/skin.estuary/xml/Variables.xml +++ b/addons/skin.estuary/xml/Variables.xml @@ -967,4 +967,37 @@ <variable name="PVRInstanceName"> <value condition="Integer.IsGreater(PVR.ClientCount,1)">$INFO[ListItem.PVRClientName,[COLOR grey]$LOCALIZE[31137]:[/COLOR] ,]$INFO[ListItem.PVRInstanceName, (,)]</value> </variable> + <variable name="VideoStreamDialogVideoItemLabelVar"> + <value condition="String.IsEqual(ListItem.Property(stream.isforced),true)+String.IsEqual(ListItem.Property(stream.ishearingimpaired),true)">$LOCALIZE[39106], $LOCALIZE[39107]</value> + <value condition="String.IsEqual(ListItem.Property(stream.isforced),true)+String.IsEqual(ListItem.Property(stream.isvisualimpaired),true)">$LOCALIZE[39106], $LOCALIZE[39108]</value> + <value condition="String.IsEqual(ListItem.Property(stream.ishearingimpaired),true)">$LOCALIZE[39107]</value> + <value condition="String.IsEqual(ListItem.Property(stream.isvisualimpaired),true)">$LOCALIZE[39108]</value> + <value condition="String.IsEqual(ListItem.Property(stream.isforced),true)">$LOCALIZE[39106]</value> + </variable> + <variable name="VideoStreamDialogAudioItemLabelVar"> + <value condition="String.IsEqual(ListItem.Property(stream.isoriginal),true)+String.IsEqual(ListItem.Property(stream.isforced),true)+String.IsEqual(ListItem.Property(stream.ishearingimpaired),true)">$LOCALIZE[39111], $LOCALIZE[39106], $LOCALIZE[39107]</value> + <value condition="String.IsEqual(ListItem.Property(stream.isoriginal),true)+String.IsEqual(ListItem.Property(stream.isforced),true)+String.IsEqual(ListItem.Property(stream.isvisualimpaired),true)">$LOCALIZE[39111], $LOCALIZE[39106], $LOCALIZE[39108]</value> + <value condition="String.IsEqual(ListItem.Property(stream.isoriginal),true)+String.IsEqual(ListItem.Property(stream.isforced),true)">$LOCALIZE[39111], $LOCALIZE[39106]</value> + <value condition="String.IsEqual(ListItem.Property(stream.isoriginal),true)+String.IsEqual(ListItem.Property(stream.ishearingimpaired),true)">$LOCALIZE[39111], $LOCALIZE[39107]</value> + <value condition="String.IsEqual(ListItem.Property(stream.isoriginal),true)+String.IsEqual(ListItem.Property(stream.isvisualimpaired),true)">$LOCALIZE[39111], $LOCALIZE[39108]</value> + <value condition="String.IsEqual(ListItem.Property(stream.isforced),true)+String.IsEqual(ListItem.Property(stream.ishearingimpaired),true)">$LOCALIZE[39106], $LOCALIZE[39107]</value> + <value condition="String.IsEqual(ListItem.Property(stream.isforced),true)+String.IsEqual(ListItem.Property(stream.isvisualimpaired),true)">$LOCALIZE[39106], $LOCALIZE[39108]</value> + <value condition="String.IsEqual(ListItem.Property(stream.ishearingimpaired),true)">$LOCALIZE[39107]</value> + <value condition="String.IsEqual(ListItem.Property(stream.isvisualimpaired),true)">$LOCALIZE[39108]</value> + <value condition="String.IsEqual(ListItem.Property(stream.isforced),true)">$LOCALIZE[39106]</value> + <value condition="String.IsEqual(ListItem.Property(stream.isoriginal),true)">$LOCALIZE[39111]</value> + </variable> + <variable name="VideoStreamDialogSubItemLabelVar"> + <value condition="String.IsEqual(ListItem.Property(stream.isoriginal),true)+String.IsEqual(ListItem.Property(stream.isforced),true)+String.IsEqual(ListItem.Property(stream.ishearingimpaired),true)">$LOCALIZE[39111], $LOCALIZE[39106], $LOCALIZE[39107]</value> + <value condition="String.IsEqual(ListItem.Property(stream.isoriginal),true)+String.IsEqual(ListItem.Property(stream.isforced),true)+String.IsEqual(ListItem.Property(stream.isvisualimpaired),true)">$LOCALIZE[39111], $LOCALIZE[39106], $LOCALIZE[39108]</value> + <value condition="String.IsEqual(ListItem.Property(stream.isoriginal),true)+String.IsEqual(ListItem.Property(stream.isforced),true)">$LOCALIZE[39111], $LOCALIZE[39106]</value> + <value condition="String.IsEqual(ListItem.Property(stream.isoriginal),true)+String.IsEqual(ListItem.Property(stream.ishearingimpaired),true)">$LOCALIZE[39111], $LOCALIZE[39107]</value> + <value condition="String.IsEqual(ListItem.Property(stream.isoriginal),true)+String.IsEqual(ListItem.Property(stream.isvisualimpaired),true)">$LOCALIZE[39111], $LOCALIZE[39108]</value> + <value condition="String.IsEqual(ListItem.Property(stream.isforced),true)+String.IsEqual(ListItem.Property(stream.ishearingimpaired),true)">$LOCALIZE[39106], $LOCALIZE[39107]</value> + <value condition="String.IsEqual(ListItem.Property(stream.isforced),true)+String.IsEqual(ListItem.Property(stream.isvisualimpaired),true)">$LOCALIZE[39106], $LOCALIZE[39108]</value> + <value condition="String.IsEqual(ListItem.Property(stream.ishearingimpaired),true)">$LOCALIZE[39107]</value> + <value condition="String.IsEqual(ListItem.Property(stream.isvisualimpaired),true)">$LOCALIZE[39108]</value> + <value condition="String.IsEqual(ListItem.Property(stream.isforced),true)">$LOCALIZE[39106]</value> + <value condition="String.IsEqual(ListItem.Property(stream.isoriginal),true)">$LOCALIZE[39111]</value> + </variable> </includes> diff --git a/system/keymaps/customcontroller.Harmony.xml b/system/keymaps/customcontroller.Harmony.xml index 77f288dfc2..98e3f21906 100644 --- a/system/keymaps/customcontroller.Harmony.xml +++ b/system/keymaps/customcontroller.Harmony.xml @@ -94,10 +94,10 @@ <!-- F8 --> <button id="196">ActivateWindow(FavouritesBrowser)</button> <!-- F9 --> <button id="173">ShowVideoMenu</button> <!-- F10 --> <button id="174">ShowSubtitles</button> - <!-- F11 --> <button id="175">NextSubtitle</button> + <!-- F11 --> <button id="175">DialogSelectSubtitle</button> <!-- F12 --> <button id="176">ActivateWindow(Videos)</button> <!-- F13 --> <button id="163">Playlist</button> - <!-- F14 --> <button id="164">AudioNextLanguage</button> + <!-- F14 --> <button id="164">DialogSelectAudio</button> <!-- Large Down --> <button id="182">PageDown</button> <!-- Large Up --> <button id="181">PageUp</button> <!-- pwrToggle --> <button id="166">ShutDown()</button> diff --git a/system/keymaps/keyboard.xml b/system/keymaps/keyboard.xml index ac0033d7f8..4b69e9b8c3 100644 --- a/system/keymaps/keyboard.xml +++ b/system/keymaps/keyboard.xml @@ -371,7 +371,7 @@ <zoom>AspectRatio</zoom> <t>ShowSubtitles</t> <t mod="ctrl">SubtitleAlign</t> - <l>NextSubtitle</l> + <l>DialogSelectSubtitle</l> <left>StepBack</left> <right>StepForward</right> <up>ChapterOrBigStepForward</up> @@ -381,11 +381,11 @@ <left mod="alt">PlayerControl(tempodown)</left> <right mod="alt">PlayerControl(tempoup)</right> <a>AudioDelay</a> - <a mod="ctrl">AudioNextLanguage</a> + <a mod="ctrl">DialogSelectAudio</a> <escape>Fullscreen</escape> <c>Playlist</c> <v>ActivateWindow(Teletext)</v> - <v mod="ctrl">VideoNextStream</v> + <v mod="ctrl">DialogSelectVideo</v> <text>ActivateWindow(Teletext)</text> <up mod="ctrl">SubtitleShiftUp</up> <down mod="ctrl">SubtitleShiftDown</down> @@ -583,7 +583,7 @@ <z>AspectRatio</z> <zoom>AspectRatio</zoom> <t>ShowSubtitles</t> - <l>NextSubtitle</l> + <l>DialogSelectSubtitle</l> <a>AudioDelay</a> <escape>Fullscreen</escape> <return>Select</return> diff --git a/system/keymaps/remote.xml b/system/keymaps/remote.xml index 5c0baf7e9d..5306e94cb9 100644 --- a/system/keymaps/remote.xml +++ b/system/keymaps/remote.xml @@ -198,9 +198,9 @@ <info>Info</info> <guide>ActivateWindow(TVGuide)</guide> <teletext>ActivateWindow(Teletext)</teletext> - <subtitle>NextSubtitle</subtitle> + <subtitle>DialogSelectSubtitle</subtitle> <star>NextSubtitle</star> - <language>AudioNextLanguage</language> + <language>DialogSelectAudio</language> <playlist>Playlist</playlist> <hash>AudioNextLanguage</hash> <pageplus>SkipNext</pageplus> diff --git a/xbmc/GUIInfoManager.cpp b/xbmc/GUIInfoManager.cpp index 4bfc07d3d1..ee12786f60 100644 --- a/xbmc/GUIInfoManager.cpp +++ b/xbmc/GUIInfoManager.cpp @@ -3976,6 +3976,14 @@ const infomap musicplayer[] = {{ "title", MUSICPLAYER_TITLE }, /// @skinning_v20 **[New Infolabel]** \link VideoPlayer_AudioStreamCount `VideoPlayer.AudioStreamCount`\endlink /// <p> /// } +/// \table_row3{ <b>`VideoPlayer.VideoStreamCount`</b>, +/// \anchor VideoPlayer_VideoStreamCount +/// _integer_, +/// @return The number of video streams of the currently playing video. +/// <p><hr> +/// @skinning_v22 **[New Infolabel]** \link VideoPlayer_VideoStreamCount `VideoPlayer.VideoStreamCount`\endlink +/// <p> +/// } /// \table_row3{ <b>`VideoPlayer.HdrType`</b>, /// \anchor VideoPlayer_HdrType /// _string_, @@ -4086,6 +4094,7 @@ const infomap videoplayer[] = {{ "title", VIDEOPLAYER_TITLE }, { "uniqueid", VIDEOPLAYER_UNIQUEID }, { "tvshowdbid", VIDEOPLAYER_TVSHOWDBID }, { "audiostreamcount", VIDEOPLAYER_AUDIOSTREAMCOUNT }, + { "videostreamcount", VIDEOPLAYER_VIDEOSTREAMCOUNT }, { "hdrtype", VIDEOPLAYER_HDR_TYPE }, { "art", VIDEOPLAYER_ART}, { "videoversionname", VIDEOPLAYER_VIDEOVERSION_NAME}, diff --git a/xbmc/Util.cpp b/xbmc/Util.cpp index cd761165e3..de9dd6f8b4 100644 --- a/xbmc/Util.cpp +++ b/xbmc/Util.cpp @@ -2200,6 +2200,16 @@ ExternalStreamInfo CUtil::GetExternalStreamDetailsFromFilename(const std::string info.flag |= StreamFlags::FLAG_FORCED; continue; } + else if (!flag_tmp.compare("original")) + { + info.flag |= StreamFlags::FLAG_ORIGINAL; + continue; + } + else if (!flag_tmp.compare("impaired")) + { + info.flag |= StreamFlags::FLAG_HEARING_IMPAIRED; + continue; + } if (info.language.empty()) { diff --git a/xbmc/addons/interfaces/gui/GUITranslator.cpp b/xbmc/addons/interfaces/gui/GUITranslator.cpp index 2062f38667..bcc38b4815 100644 --- a/xbmc/addons/interfaces/gui/GUITranslator.cpp +++ b/xbmc/addons/interfaces/gui/GUITranslator.cpp @@ -552,6 +552,12 @@ int CAddonGUITranslator::TranslateActionIdToKodi(ADDON_ACTION addonId) return ACTION_SHOW_SUBTITLES; case ADDON_ACTION_NEXT_SUBTITLE: return ACTION_NEXT_SUBTITLE; + case ADDON_ACTION_DIALOG_SELECT_VIDEO: + return ACTION_DIALOG_SELECT_VIDEO; + case ADDON_ACTION_DIALOG_SELECT_AUDIO: + return ACTION_DIALOG_SELECT_AUDIO; + case ADDON_ACTION_DIALOG_SELECT_SUBTITLE: + return ACTION_DIALOG_SELECT_SUBTITLE; case ADDON_ACTION_PLAYER_DEBUG: return ACTION_PLAYER_DEBUG; case ADDON_ACTION_NEXT_PICTURE: diff --git a/xbmc/addons/kodi-dev-kit/include/kodi/c-api/gui/input/action_ids.h b/xbmc/addons/kodi-dev-kit/include/kodi/c-api/gui/input/action_ids.h index b7e8f9c6d2..0f779ce464 100644 --- a/xbmc/addons/kodi-dev-kit/include/kodi/c-api/gui/input/action_ids.h +++ b/xbmc/addons/kodi-dev-kit/include/kodi/c-api/gui/input/action_ids.h @@ -677,6 +677,15 @@ enum ADDON_ACTION /// @brief <b>`267`</b>: Tempo decrease in current file played. global action, can be used anywhere ADDON_ACTION_PLAYER_DECREASE_TEMPO = 267, + /// @brief <b>`270 `</b>: Open the dialog window to select a video stream + ADDON_ACTION_DIALOG_SELECT_VIDEO = 270, + + /// @brief <b>`271 `</b>: Open the dialog window to select a audio stream + ADDON_ACTION_DIALOG_SELECT_AUDIO = 271, + + /// @brief <b>`272 `</b>: Open the dialog window to select a subtitle stream + ADDON_ACTION_DIALOG_SELECT_SUBTITLE = 272, + /// @brief <b>`300`</b>: Voice actions ADDON_ACTION_VOICE_RECOGNIZE = 300, diff --git a/xbmc/addons/kodi-dev-kit/include/kodi/versions.h b/xbmc/addons/kodi-dev-kit/include/kodi/versions.h index c4c45d6f95..34de2017ac 100644 --- a/xbmc/addons/kodi-dev-kit/include/kodi/versions.h +++ b/xbmc/addons/kodi-dev-kit/include/kodi/versions.h @@ -49,7 +49,7 @@ #define ADDON_GLOBAL_VERSION_GENERAL_XML_ID "kodi.binary.global.general" #define ADDON_GLOBAL_VERSION_GENERAL_DEPENDS "General.h" -#define ADDON_GLOBAL_VERSION_GUI "5.15.0" +#define ADDON_GLOBAL_VERSION_GUI "5.15.1" #define ADDON_GLOBAL_VERSION_GUI_MIN "5.15.0" #define ADDON_GLOBAL_VERSION_GUI_XML_ID "kodi.binary.global.gui" #define ADDON_GLOBAL_VERSION_GUI_DEPENDS "c-api/gui/input/action_ids.h" \ diff --git a/xbmc/application/ApplicationPlayer.cpp b/xbmc/application/ApplicationPlayer.cpp index 9a59242a85..a222557677 100644 --- a/xbmc/application/ApplicationPlayer.cpp +++ b/xbmc/application/ApplicationPlayer.cpp @@ -760,18 +760,18 @@ void CApplicationPlayer::LoadPage(int p, int sp, unsigned char* buffer) player->LoadPage(p, sp, buffer); } -void CApplicationPlayer::GetAudioCapabilities(std::vector<int>& audioCaps) const +void CApplicationPlayer::GetAudioCapabilities(std::vector<IPlayerAudioCaps>& caps) const { const std::shared_ptr<const IPlayer> player = GetInternal(); if (player) - player->GetAudioCapabilities(audioCaps); + player->GetAudioCapabilities(caps); } -void CApplicationPlayer::GetSubtitleCapabilities(std::vector<int>& subCaps) const +void CApplicationPlayer::GetSubtitleCapabilities(std::vector<IPlayerSubtitleCaps>& caps) const { const std::shared_ptr<const IPlayer> player = GetInternal(); if (player) - player->GetSubtitleCapabilities(subCaps); + player->GetSubtitleCapabilities(caps); } int CApplicationPlayer::SeekChapter(int iChapter) diff --git a/xbmc/application/ApplicationPlayer.h b/xbmc/application/ApplicationPlayer.h index ccee506bd6..ae901d4058 100644 --- a/xbmc/application/ApplicationPlayer.h +++ b/xbmc/application/ApplicationPlayer.h @@ -83,7 +83,7 @@ public: bool CanPause() const; bool CanSeek() const; int GetAudioDelay() const; - void GetAudioCapabilities(std::vector<int>& audioCaps) const; + void GetAudioCapabilities(std::vector<IPlayerAudioCaps>& caps) const; int GetAudioStream(); int GetAudioStreamCount() const; void GetAudioStreamInfo(int index, AudioStreamInfo& info) const; @@ -98,7 +98,7 @@ public: KODI::PLAYLIST::Id GetPreferredPlaylist() const; int GetSubtitleDelay() const; int GetSubtitle(); - void GetSubtitleCapabilities(std::vector<int>& subCaps) const; + void GetSubtitleCapabilities(std::vector<IPlayerSubtitleCaps>& caps) const; int GetSubtitleCount() const; void GetSubtitleStreamInfo(int index, SubtitleStreamInfo& info) const; bool GetSubtitleVisible() const; diff --git a/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAESink.cpp b/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAESink.cpp index f0cce1885b..9eb39fdc2c 100644 --- a/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAESink.cpp +++ b/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAESink.cpp @@ -1207,12 +1207,7 @@ void CActiveAESink::SetSilenceTimer() m_extSilenceTimeout = XbmcThreads::EndTime<decltype(m_extSilenceTimeout)>::Max(); else if (m_extAppFocused) // handles no playback/GUI and playback in pause and seek { - // only true with AudioTrack RAW + passthrough + TrueHD - const bool noSilenceOnPause = - !m_needIecPack && m_requestedFormat.m_dataFormat == AE_FMT_RAW && - m_sinkFormat.m_streamInfo.m_type == CAEStreamInfo::STREAM_TYPE_TRUEHD; - - m_extSilenceTimeout = (noSilenceOnPause) ? 0ms : m_silenceTimeOut; + m_extSilenceTimeout = m_silenceTimeOut; } else { diff --git a/xbmc/cores/AudioEngine/Sinks/AESinkAUDIOTRACK.cpp b/xbmc/cores/AudioEngine/Sinks/AESinkAUDIOTRACK.cpp index 149bc7a268..875901b158 100644 --- a/xbmc/cores/AudioEngine/Sinks/AESinkAUDIOTRACK.cpp +++ b/xbmc/cores/AudioEngine/Sinks/AESinkAUDIOTRACK.cpp @@ -20,6 +20,8 @@ #include "platform/android/activity/XBMCApp.h" +#include <numeric> + #include <androidjni/AudioFormat.h> #include <androidjni/AudioManager.h> #include <androidjni/AudioTrack.h> @@ -768,25 +770,16 @@ void CAESinkAUDIOTRACK::GetDelay(AEDelayStatus& status) // the RAW hack for simulating pause bursts should not come // into the way of hw delay + if (m_pause_ms > m_audiotrackbuffer_sec * 1000.0) + m_pause_ms = m_audiotrackbuffer_sec * 1000.0; + if (m_pause_ms > 0.0) { - double difference = (m_audiotrackbuffer_sec - delay) * 1000; - if (usesAdvancedLogging) - { - CLog::Log(LOGINFO, "Faking Pause-Bursts in Delay - returning smoothed {} ms Original {} ms", - m_audiotrackbuffer_sec * 1000, delay * 1000); - CLog::Log(LOGINFO, "Difference: {} ms m_pause_ms {}", difference, m_pause_ms); - } - // buffer not yet reached - if (difference > 0.0) + if (delay < m_audiotrackbuffer_sec) delay = m_audiotrackbuffer_sec; else - { - CLog::Log(LOGINFO, "Resetting pause bursts as buffer level was reached! (2)"); - m_pause_ms = 0.0; - } + m_audiotrackbuffer_sec = delay; } - const double d = GetMovingAverageDelay(delay); // Audiotrack is caching more than we thought it would @@ -798,6 +791,8 @@ void CAESinkAUDIOTRACK::GetDelay(AEDelayStatus& status) if (usesAdvancedLogging) { CLog::Log(LOGINFO, "Delay Current: {:f} ms", d * 1000); + if (m_pause_ms > 0.0) + CLog::Log(LOGINFO, "Delay faked due to pause delay: {:f} ms", m_pause_ms); } status.SetDelay(d); } @@ -927,49 +922,28 @@ unsigned int CAESinkAUDIOTRACK::AddPackets(uint8_t **data, unsigned int frames, } unsigned int written_frames = static_cast<unsigned int>(written / m_format.m_frameSize); double time_to_add_ms = 1000.0 * (CurrentHostCounter() - startTime) / CurrentHostFrequency(); + // Get Back in Sync with faked pause bursts if (m_passthrough && !m_info.m_wantsIECPassthrough) { - // AT does not consume in a blocking way - it runs ahead and blocks - // exactly once with the last package for some 100 ms - double extra_sleep = 0.0; - if (time_to_add_ms < m_format.m_streamInfo.GetDuration()) - extra_sleep = (m_format.m_streamInfo.GetDuration() - time_to_add_ms) / 2; - - // if there is still place, just add it without blocking - if (m_delay < (m_audiotrackbuffer_sec - (m_format.m_streamInfo.GetDuration() / 1000.0))) - extra_sleep = 0; - if (m_pause_ms > 0.0) { - extra_sleep = 0; - m_pause_ms -= m_format.m_streamInfo.GetDuration(); + // Idea here is: Slowly correct the wrong buffer so that AE should not realize + // but do not underrun while doing so + double extra_sleep_ms = m_format.m_streamInfo.GetDuration() / 2.0 - time_to_add_ms; + if (extra_sleep_ms > 0) + { + CLog::Log(LOGDEBUG, "Sleeping for {:f}", extra_sleep_ms); + m_pause_ms -= extra_sleep_ms; + usleep(extra_sleep_ms * 1000); + } if (m_pause_ms <= 0.0) { m_pause_ms = 0.0; - CLog::Log(LOGINFO, "Resetting pause bursts as buffer level was reached! (1)"); + extra_sleep_ms = 0.0; + CLog::Log(LOGDEBUG, "Resetting pause bursts as buffer level was reached! (1)"); } } - else - { - if (m_delay > 0.3) - extra_sleep *= 2; - } - - usleep(extra_sleep * 1000); - } - else - { - // waiting should only be done if sink is not run dry - double period_time = m_format.m_frames / static_cast<double>(m_sink_sampleRate); - if (m_delay >= (m_audiotrackbuffer_sec - period_time)) - { - double time_should_ms = 1000.0 * written_frames / m_format.m_sampleRate; - double time_off = time_should_ms - time_to_add_ms; - if (time_off > 0) - usleep(time_off * 500); // sleep half the error on average away - } } - return written_frames; } @@ -1248,7 +1222,6 @@ double CAESinkAUDIOTRACK::GetMovingAverageDelay(double newestdelay) return d; #endif - m_linearmovingaverage.push_back(newestdelay); // new values are in the back, old values are in the front @@ -1261,12 +1234,8 @@ double CAESinkAUDIOTRACK::GetMovingAverageDelay(double newestdelay) m_linearmovingaverage.pop_front(); size--; } - // m_{LWMA}^{(n)}(t) = \frac{2}{n (n+1)} \sum_{i=1}^n i \; x(t-n+i) - const double denom = 2.0 / (size * (size + 1)); - double sum = 0.0; - for (size_t i = 0; i < m_linearmovingaverage.size(); i++) - sum += (i + 1) * m_linearmovingaverage.at(i); + double sum = std::accumulate(m_linearmovingaverage.begin(), m_linearmovingaverage.end(), 0.0); - return sum * denom; + return sum / size; } diff --git a/xbmc/cores/IPlayer.h b/xbmc/cores/IPlayer.h index d988d77527..8f6bd763bf 100644 --- a/xbmc/cores/IPlayer.h +++ b/xbmc/cores/IPlayer.h @@ -50,22 +50,24 @@ public: class CFileItem; -enum IPlayerAudioCapabilities +// \brief Player Audio capabilities +enum class IPlayerAudioCaps { - IPC_AUD_ALL, - IPC_AUD_OFFSET, - IPC_AUD_AMP, - IPC_AUD_SELECT_STREAM, - IPC_AUD_OUTPUT_STEREO, - IPC_AUD_SELECT_OUTPUT + ALL, // All capabilities supported + SELECT_STREAM, // Support to change stream + SELECT_OUTPUT, // Support to select an output device + OUTPUT_STEREO, // Support output in stereo mode + OFFSET, // Support to change sync offset + VOLUME_AMP, // Support volume amplification }; -enum IPlayerSubtitleCapabilities +// \brief Player Subtitle capabilities +enum class IPlayerSubtitleCaps { - IPC_SUBS_ALL, - IPC_SUBS_SELECT, - IPC_SUBS_EXTERNAL, - IPC_SUBS_OFFSET + ALL, // All capabilities supported + SELECT_STREAM, // Support to change stream + EXTERNAL, // Support to load external subtitles + OFFSET, // Support to change sync offset }; enum ERENDERFEATURE @@ -211,16 +213,20 @@ public: virtual std::string GetPlayerState() { return ""; } virtual bool SetPlayerState(const std::string& state) { return false; } - virtual void GetAudioCapabilities(std::vector<int>& audioCaps) const + /*! + * \brief Define the audio capabilities of the player + */ + virtual void GetAudioCapabilities(std::vector<IPlayerAudioCaps>& caps) const { - audioCaps.assign(1, IPC_AUD_ALL); + caps.assign(1, IPlayerAudioCaps::ALL); } + /*! - \brief define the subtitle capabilities of the player + * \brief Define the subtitle capabilities of the player */ - virtual void GetSubtitleCapabilities(std::vector<int>& subCaps) const + virtual void GetSubtitleCapabilities(std::vector<IPlayerSubtitleCaps>& caps) const { - subCaps.assign(1, IPC_SUBS_ALL); + caps.assign(1, IPlayerSubtitleCaps::ALL); } /*! diff --git a/xbmc/cores/VideoPlayer/DVDInputStreams/InputStreamPVRBase.cpp b/xbmc/cores/VideoPlayer/DVDInputStreams/InputStreamPVRBase.cpp index 1f1622318d..7b8be41763 100644 --- a/xbmc/cores/VideoPlayer/DVDInputStreams/InputStreamPVRBase.cpp +++ b/xbmc/cores/VideoPlayer/DVDInputStreams/InputStreamPVRBase.cpp @@ -40,8 +40,9 @@ bool CInputStreamPVRBase::IsEOF() bool CInputStreamPVRBase::Open() { - if (CDVDInputStream::Open() && OpenPVRStream()) + if (!m_isOpen && CDVDInputStream::Open() && OpenPVRStream()) { + m_isOpen = true; m_eof = false; m_StreamProps->iStreamCount = 0; return true; @@ -54,9 +55,13 @@ bool CInputStreamPVRBase::Open() void CInputStreamPVRBase::Close() { - ClosePVRStream(); - CDVDInputStream::Close(); - m_eof = true; + if (m_isOpen) + { + ClosePVRStream(); + CDVDInputStream::Close(); + m_eof = true; + m_isOpen = false; + } } int CInputStreamPVRBase::Read(uint8_t* buf, int buf_size) diff --git a/xbmc/cores/VideoPlayer/DVDInputStreams/InputStreamPVRBase.h b/xbmc/cores/VideoPlayer/DVDInputStreams/InputStreamPVRBase.h index 67b5d32d1c..7a2d336512 100644 --- a/xbmc/cores/VideoPlayer/DVDInputStreams/InputStreamPVRBase.h +++ b/xbmc/cores/VideoPlayer/DVDInputStreams/InputStreamPVRBase.h @@ -80,4 +80,5 @@ protected: std::shared_ptr<PVR_STREAM_PROPERTIES> m_StreamProps; std::map<int, std::shared_ptr<CDemuxStream>> m_streamMap; std::shared_ptr<PVR::CPVRClient> m_client; + bool m_isOpen{false}; }; diff --git a/xbmc/cores/VideoPlayer/Interface/StreamInfo.h b/xbmc/cores/VideoPlayer/Interface/StreamInfo.h index df67662e9b..9f93e86b7f 100644 --- a/xbmc/cores/VideoPlayer/Interface/StreamInfo.h +++ b/xbmc/cores/VideoPlayer/Interface/StreamInfo.h @@ -10,6 +10,7 @@ #include "utils/Geometry.h" +#include <cstdint> #include <string> template <typename T> class CRectGen; @@ -45,6 +46,7 @@ struct StreamInfo std::string language; std::string name; std::string codecName; + std::string codecDesc; StreamFlags flags = StreamFlags::FLAG_NONE; protected: @@ -60,7 +62,9 @@ struct AudioStreamInfo : StreamInfo }; struct SubtitleStreamInfo : StreamInfo -{}; +{ + bool isExternal{false}; +}; struct VideoStreamInfo : StreamInfo { @@ -73,6 +77,8 @@ struct VideoStreamInfo : StreamInfo std::string stereoMode; int angles = 0; StreamHdrType hdrType = StreamHdrType::HDR_TYPE_NONE; + uint32_t fpsRate{0}; + uint32_t fpsScale{0}; }; struct ProgramInfo diff --git a/xbmc/cores/VideoPlayer/VideoPlayer.cpp b/xbmc/cores/VideoPlayer/VideoPlayer.cpp index b182b6ec20..5a20ca0fff 100644 --- a/xbmc/cores/VideoPlayer/VideoPlayer.cpp +++ b/xbmc/cores/VideoPlayer/VideoPlayer.cpp @@ -454,6 +454,7 @@ void CSelectionStreams::Update(const std::shared_ptr<CDVDInputStream>& input, AudioStreamInfo info = nav->GetAudioStreamInfo(i); s.name = info.name; s.codec = info.codecName; + s.codecDesc = info.codecDesc; s.language = g_LangCodeExpander.ConvertToISO6392B(info.language); s.channels = info.channels; s.flags = info.flags; @@ -472,6 +473,7 @@ void CSelectionStreams::Update(const std::shared_ptr<CDVDInputStream>& input, SubtitleStreamInfo info = nav->GetSubtitleStreamInfo(i); s.name = info.name; + s.codec = info.codecName; s.flags = info.flags; s.language = g_LangCodeExpander.ConvertToISO6392B(info.language); Update(s); @@ -535,17 +537,12 @@ void CSelectionStreams::Update(const std::shared_ptr<CDVDInputStream>& input, s.stereo_mode = vstream->stereo_mode; s.bitrate = vstream->iBitRate; s.hdrType = vstream->hdr_type; + s.fpsRate = static_cast<uint32_t>(vstream->iFpsRate); + s.fpsScale = static_cast<uint32_t>(vstream->iFpsScale); } if(stream->type == STREAM_AUDIO) { - std::string type; - type = static_cast<CDemuxStreamAudio*>(stream)->GetStreamType(); - if(type.length() > 0) - { - if(s.name.length() > 0) - s.name += " - "; - s.name += type; - } + s.codecDesc = static_cast<CDemuxStreamAudio*>(stream)->GetStreamType(); s.channels = static_cast<CDemuxStreamAudio*>(stream)->iChannels; s.bitrate = static_cast<CDemuxStreamAudio*>(stream)->iBitRate; } @@ -5298,6 +5295,8 @@ void CVideoPlayer::GetVideoStreamInfo(int streamId, VideoStreamInfo& info) const info.stereoMode = s.stereo_mode; info.flags = s.flags; info.hdrType = s.hdrType; + info.fpsRate = s.fpsRate; + info.fpsScale = s.fpsScale; } int CVideoPlayer::GetVideoStreamCount() const @@ -5343,6 +5342,7 @@ void CVideoPlayer::GetAudioStreamInfo(int index, AudioStreamInfo& info) const info.bitrate = s.bitrate; info.channels = s.channels; info.codecName = s.codec; + info.codecDesc = s.codecDesc; info.flags = s.flags; } @@ -5387,7 +5387,10 @@ void CVideoPlayer::GetSubtitleStreamInfo(int index, SubtitleStreamInfo& info) co info.name += "(Invalid)"; info.language = s.language; + info.codecName = s.codec; info.flags = s.flags; + info.isExternal = STREAM_SOURCE_MASK(s.source) == STREAM_SOURCE_DEMUX_SUB || + STREAM_SOURCE_MASK(s.source) == STREAM_SOURCE_TEXT; } void CVideoPlayer::SetSubtitle(int iStream) diff --git a/xbmc/cores/VideoPlayer/VideoPlayer.h b/xbmc/cores/VideoPlayer/VideoPlayer.h index f340b85e83..1546505830 100644 --- a/xbmc/cores/VideoPlayer/VideoPlayer.h +++ b/xbmc/cores/VideoPlayer/VideoPlayer.h @@ -187,6 +187,7 @@ struct SelectionStream int id = 0; int64_t demuxerId = -1; std::string codec; + std::string codecDesc; int channels = 0; int bitrate = 0; int width = 0; @@ -197,6 +198,8 @@ struct SelectionStream std::string stereo_mode; float aspect_ratio = 0.0f; StreamHdrType hdrType = StreamHdrType::HDR_TYPE_NONE; + uint32_t fpsScale{0}; + uint32_t fpsRate{0}; }; class CSelectionStreams diff --git a/xbmc/cores/paplayer/PAPlayer.h b/xbmc/cores/paplayer/PAPlayer.h index c2a77dd84a..ae7059bfd8 100644 --- a/xbmc/cores/paplayer/PAPlayer.h +++ b/xbmc/cores/paplayer/PAPlayer.h @@ -50,7 +50,7 @@ public: void GetAudioStreamInfo(int index, AudioStreamInfo& info) const override; void SetTime(int64_t time) override; void SeekTime(int64_t iTime = 0) override; - void GetAudioCapabilities(std::vector<int>& audioCaps) const override {} + void GetAudioCapabilities(std::vector<IPlayerAudioCaps>& caps) const override {} int GetAudioStreamCount() const override { return 1; } int GetAudioStream() override { return 0; } diff --git a/xbmc/guilib/GUIWindowManager.cpp b/xbmc/guilib/GUIWindowManager.cpp index 065a68cbb8..2999fb4960 100644 --- a/xbmc/guilib/GUIWindowManager.cpp +++ b/xbmc/guilib/GUIWindowManager.cpp @@ -299,6 +299,9 @@ void CGUIWindowManager::CreateWindows() Add(new CGUIDialogVideoManagerExtras); Add(new CGUIDialogSelect(WINDOW_DIALOG_SELECT_VIDEO_VERSION)); Add(new CGUIDialogSelect(WINDOW_DIALOG_SELECT_VIDEO_EXTRA)); + Add(new CGUIDialogSelect(WINDOW_DIALOG_SELECT_VIDEO_STREAM)); + Add(new CGUIDialogSelect(WINDOW_DIALOG_SELECT_AUDIO_STREAM)); + Add(new CGUIDialogSelect(WINDOW_DIALOG_SELECT_SUBTITLE_STREAM)); Add(new CGUIDialogTextViewer); Add(new CGUIWindowFullScreen); @@ -386,6 +389,9 @@ bool CGUIWindowManager::DestroyWindows() DestroyWindow(WINDOW_DIALOG_SLIDER); DestroyWindow(WINDOW_DIALOG_MEDIA_FILTER); DestroyWindow(WINDOW_DIALOG_SUBTITLES); + DestroyWindow(WINDOW_DIALOG_SELECT_VIDEO_STREAM); + DestroyWindow(WINDOW_DIALOG_SELECT_AUDIO_STREAM); + DestroyWindow(WINDOW_DIALOG_SELECT_SUBTITLE_STREAM); DestroyWindow(WINDOW_DIALOG_COLOR_PICKER); /* Delete PVR related windows and dialogs */ diff --git a/xbmc/guilib/WindowIDs.h b/xbmc/guilib/WindowIDs.h index 40e4724543..318569115b 100644 --- a/xbmc/guilib/WindowIDs.h +++ b/xbmc/guilib/WindowIDs.h @@ -180,6 +180,10 @@ #define WINDOW_DIALOG_SELECT_VIDEO_EXTRA 12016 #define WINDOW_DIALOG_MANAGE_VIDEO_EXTRAS 12017 +#define WINDOW_DIALOG_SELECT_VIDEO_STREAM 12300 +#define WINDOW_DIALOG_SELECT_AUDIO_STREAM 12301 +#define WINDOW_DIALOG_SELECT_SUBTITLE_STREAM 12302 + #define WINDOW_WEATHER 12600 #define WINDOW_SCREENSAVER 12900 #define WINDOW_DIALOG_VIDEO_OSD 12901 diff --git a/xbmc/guilib/guiinfo/GUIInfoLabels.h b/xbmc/guilib/guiinfo/GUIInfoLabels.h index 613aff347f..3138987c6a 100644 --- a/xbmc/guilib/guiinfo/GUIInfoLabels.h +++ b/xbmc/guilib/guiinfo/GUIInfoLabels.h @@ -281,6 +281,7 @@ #define VIDEOPLAYER_UNIQUEID 294 #define VIDEOPLAYER_AUDIOSTREAMCOUNT 295 #define VIDEOPLAYER_VIDEOVERSION_NAME 296 +#define VIDEOPLAYER_VIDEOSTREAMCOUNT 297 // Videoplayer infobools #define VIDEOPLAYER_HASSUBTITLES 300 diff --git a/xbmc/guilib/guiinfo/VideoGUIInfo.cpp b/xbmc/guilib/guiinfo/VideoGUIInfo.cpp index 0a112ea3ef..406cc06793 100644 --- a/xbmc/guilib/guiinfo/VideoGUIInfo.cpp +++ b/xbmc/guilib/guiinfo/VideoGUIInfo.cpp @@ -759,6 +759,9 @@ bool CVideoGUIInfo::GetInt(int& value, const CGUIListItem *gitem, int contextWin /////////////////////////////////////////////////////////////////////////////////////////////// // VIDEOPLAYER_* /////////////////////////////////////////////////////////////////////////////////////////////// + case VIDEOPLAYER_VIDEOSTREAMCOUNT: + value = m_appPlayer->GetVideoStreamCount(); + return true; case VIDEOPLAYER_AUDIOSTREAMCOUNT: value = m_appPlayer->GetAudioStreamCount(); return true; diff --git a/xbmc/input/WindowTranslator.cpp b/xbmc/input/WindowTranslator.cpp index 795c854f0f..9eeaceb63f 100644 --- a/xbmc/input/WindowTranslator.cpp +++ b/xbmc/input/WindowTranslator.cpp @@ -177,6 +177,9 @@ const CWindowTranslator::WindowMapByName CWindowTranslator::WindowMappingByName {"ingamesaves", WINDOW_DIALOG_IN_GAME_SAVES}, {"gamesaves", WINDOW_DIALOG_GAME_SAVES}, {"gameagents", WINDOW_DIALOG_GAME_AGENTS}, + {"videoselectdialog", WINDOW_DIALOG_SELECT_VIDEO_STREAM}, + {"audioselectdialog", WINDOW_DIALOG_SELECT_AUDIO_STREAM}, + {"subtitleselectdialog", WINDOW_DIALOG_SELECT_SUBTITLE_STREAM}, }; namespace diff --git a/xbmc/input/actions/ActionIDs.h b/xbmc/input/actions/ActionIDs.h index ddd0d44cb6..11f9ed251d 100644 --- a/xbmc/input/actions/ActionIDs.h +++ b/xbmc/input/actions/ActionIDs.h @@ -458,6 +458,15 @@ constexpr const int ACTION_KEYBOARD_COMPOSING_KEY_FINISHED = 265; constexpr const int ACTION_PLAYER_INCREASE_TEMPO = 266; constexpr const int ACTION_PLAYER_DECREASE_TEMPO = 267; +//! Open the dialog window to select a video stream +constexpr const int ACTION_DIALOG_SELECT_VIDEO = 270; + +//! Open the dialog window to select a audio stream +constexpr const int ACTION_DIALOG_SELECT_AUDIO = 271; + +//! Open the dialog window to select a subtitle stream +constexpr const int ACTION_DIALOG_SELECT_SUBTITLE = 272; + // Voice actions constexpr const int ACTION_VOICE_RECOGNIZE = 300; diff --git a/xbmc/input/actions/ActionTranslator.cpp b/xbmc/input/actions/ActionTranslator.cpp index c47ee6b305..7233418115 100644 --- a/xbmc/input/actions/ActionTranslator.cpp +++ b/xbmc/input/actions/ActionTranslator.cpp @@ -55,6 +55,9 @@ static const std::map<ActionName, ActionID> ActionMappings = { {"nextsubtitle", ACTION_NEXT_SUBTITLE}, {"browsesubtitle", ACTION_BROWSE_SUBTITLE}, {"cyclesubtitle", ACTION_CYCLE_SUBTITLE}, + {"dialogselectvideo", ACTION_DIALOG_SELECT_VIDEO}, + {"dialogselectaudio", ACTION_DIALOG_SELECT_AUDIO}, + {"dialogselectsubtitle", ACTION_DIALOG_SELECT_SUBTITLE}, {"playerdebug", ACTION_PLAYER_DEBUG}, {"playerdebugvideo", ACTION_PLAYER_DEBUG_VIDEO}, {"codecinfo", ACTION_PLAYER_PROCESS_INFO}, diff --git a/xbmc/platform/android/activity/XBMCApp.cpp b/xbmc/platform/android/activity/XBMCApp.cpp index da9c31456a..c67ed9e8cc 100644 --- a/xbmc/platform/android/activity/XBMCApp.cpp +++ b/xbmc/platform/android/activity/XBMCApp.cpp @@ -220,6 +220,11 @@ void CXBMCApp::Announce(ANNOUNCEMENT::AnnouncementFlag flag, m_mediaSessionUpdated = false; UpdateSessionState(); } + else if (message == "OnAVStart") + { + m_mediaSessionUpdated = false; + UpdateSessionState(); + } } else if (flag & Info) { diff --git a/xbmc/pvr/addons/PVRClient.cpp b/xbmc/pvr/addons/PVRClient.cpp index 8d56392567..dd868f6292 100644 --- a/xbmc/pvr/addons/PVRClient.cpp +++ b/xbmc/pvr/addons/PVRClient.cpp @@ -133,9 +133,9 @@ public: m_fanartPath(recording.ClientFanartPath()), m_firstAired(recording.FirstAired().IsValid() ? recording.FirstAired().GetAsW3CDate() : ""), m_providerName(recording.ProviderName()), - m_parentalRatingCode(""), //! @todo - m_parentalRatingIcon(""), //! @todo - m_parentalRatingSource("") //! @todo + m_parentalRatingCode(recording.GetParentalRatingCode()), + m_parentalRatingIcon(recording.GetParentalRatingIcon()), + m_parentalRatingSource(recording.GetParentalRatingSource()) { // zero-init base struct members PVR_RECORDING* base = static_cast<PVR_RECORDING*>(this); @@ -269,7 +269,8 @@ public: prop.iKey = entry.first; prop.eType = entry.second.type; prop.iValue = entry.second.value.asInteger32(); - prop.strValue = entry.second.value.asString().c_str(); + m_customPropStringValues.emplace_back(entry.second.value.asString()); + prop.strValue = m_customPropStringValues.back().c_str(); ++idx; } customProps = m_customProps.get(); @@ -283,6 +284,7 @@ private: const std::string m_directory; const std::string m_summary; const std::string m_seriesLink; + std::vector<std::string> m_customPropStringValues; std::unique_ptr<PVR_SETTING_KEY_VALUE_PAIR[]> m_customProps; }; @@ -304,8 +306,8 @@ public: m_genreDescription(tag.GenreDescription()), m_firstAired(GetFirstAired(tag)), m_parentalRatingCode(tag.ParentalRatingCode()), - m_parentalRatingIcon(""), //! @todo - m_parentalRatingSource("") //! @todo + m_parentalRatingIcon(tag.ParentalRatingIcon()), + m_parentalRatingSource(tag.ParentalRatingSource()) { // zero-init base struct members EPG_TAG* base = static_cast<EPG_TAG*>(this); diff --git a/xbmc/video/PlayerController.cpp b/xbmc/video/PlayerController.cpp index 35025c517b..ace0f0a637 100644 --- a/xbmc/video/PlayerController.cpp +++ b/xbmc/video/PlayerController.cpp @@ -31,6 +31,7 @@ #include "utils/StringUtils.h" #include "utils/Variant.h" #include "video/dialogs/GUIDialogAudioSettings.h" +#include "video/guilib/VideoStreamSelectHelper.h" using namespace KODI; using namespace UTILS; @@ -141,6 +142,12 @@ bool CPlayerController::OnAction(const CAction &action) return true; } + case ACTION_DIALOG_SELECT_SUBTITLE: + { + VIDEO::GUILIB::OpenDialogSelectSubtitleStream(); + return true; + } + case ACTION_SUBTITLE_DELAY_MIN: { float videoSubsDelayRange = CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_videoSubsDelayRange; @@ -224,19 +231,29 @@ bool CPlayerController::OnAction(const CAction &action) if (++currentAudio >= audioStreamCount) currentAudio = 0; appPlayer->SetAudioStream(currentAudio); // Set the audio stream to the one selected - std::string aud; + std::string lan; AudioStreamInfo info; appPlayer->GetAudioStreamInfo(currentAudio, info); if (!g_LangCodeExpander.Lookup(info.language, lan)) lan = g_localizeStrings.Get(13205); // Unknown - if (info.name.empty()) - aud = lan; - else - aud = StringUtils::Format("{} - {}", lan, info.name); + + std::string textInfo = lan; + if (!info.name.empty()) + textInfo += " - " + info.name; + if (!info.codecDesc.empty()) + textInfo += " (" + info.codecDesc + ")"; + std::string caption = g_localizeStrings.Get(460); caption += StringUtils::Format(" ({}/{})", currentAudio + 1, audioStreamCount); - CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Info, caption, aud, DisplTime, false, MsgTime); + CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Info, caption, textInfo, + DisplTime, false, MsgTime); + return true; + } + + case ACTION_DIALOG_SELECT_AUDIO: + { + VIDEO::GUILIB::OpenDialogSelectAudioStream(); return true; } @@ -259,6 +276,12 @@ bool CPlayerController::OnAction(const CAction &action) return true; } + case ACTION_DIALOG_SELECT_VIDEO: + { + VIDEO::GUILIB::OpenDialogSelectVideoStream(); + return true; + } + case ACTION_ZOOM_IN: { CVideoSettings vs = appPlayer->GetVideoSettings(); diff --git a/xbmc/video/dialogs/GUIDialogAudioSettings.cpp b/xbmc/video/dialogs/GUIDialogAudioSettings.cpp index 9c8b1b10ff..e8371ffa3b 100644 --- a/xbmc/video/dialogs/GUIDialogAudioSettings.cpp +++ b/xbmc/video/dialogs/GUIDialogAudioSettings.cpp @@ -263,7 +263,7 @@ void CGUIDialogAudioSettings::InitializeSettings() std::static_pointer_cast<CSettingControlSlider>(settingAudioVolume->GetControl())->SetFormatter(SettingFormatterPercentAsDecibel); // audio volume amplification setting - if (SupportsAudioFeature(IPC_AUD_AMP)) + if (SupportsAudioFeature(IPlayerAudioCaps::VOLUME_AMP)) { std::shared_ptr<CSettingNumber> settingAudioVolumeAmplification = AddSlider(groupAudio, SETTING_AUDIO_VOLUME_AMPLIFICATION, 660, SettingLevel::Basic, videoSettings.m_VolumeAmplification, 14054, VOLUME_DRC_MINIMUM * 0.01f, (VOLUME_DRC_MAXIMUM - VOLUME_DRC_MINIMUM) / 6000.0f, VOLUME_DRC_MAXIMUM * 0.01f); settingAudioVolumeAmplification->SetDependencies(depsAudioOutputPassthroughDisabled); @@ -277,7 +277,7 @@ void CGUIDialogAudioSettings::InitializeSettings() } // audio delay setting - if (SupportsAudioFeature(IPC_AUD_OFFSET)) + if (SupportsAudioFeature(IPlayerAudioCaps::OFFSET)) { std::shared_ptr<CSettingNumber> settingAudioDelay = AddSlider( groupAudio, SETTING_AUDIO_DELAY, 297, SettingLevel::Basic, videoSettings.m_AudioDelay, 0, @@ -289,11 +289,11 @@ void CGUIDialogAudioSettings::InitializeSettings() } // audio stream setting - if (SupportsAudioFeature(IPC_AUD_SELECT_STREAM)) + if (SupportsAudioFeature(IPlayerAudioCaps::SELECT_STREAM)) AddAudioStreams(groupAudio, SETTING_AUDIO_STREAM); // audio digital/analog setting - if (SupportsAudioFeature(IPC_AUD_SELECT_OUTPUT)) + if (SupportsAudioFeature(IPlayerAudioCaps::SELECT_OUTPUT)) { m_passthrough = CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_AUDIOOUTPUT_PASSTHROUGH); AddToggle(groupAudio, SETTING_AUDIO_PASSTHROUGH, 348, SettingLevel::Basic, m_passthrough); @@ -303,11 +303,11 @@ void CGUIDialogAudioSettings::InitializeSettings() AddButton(groupSaveAsDefault, SETTING_AUDIO_MAKE_DEFAULT, 12376, SettingLevel::Basic); } -bool CGUIDialogAudioSettings::SupportsAudioFeature(int feature) +bool CGUIDialogAudioSettings::SupportsAudioFeature(IPlayerAudioCaps feature) { - for (Features::iterator itr = m_audioCaps.begin(); itr != m_audioCaps.end(); ++itr) + for (IPlayerAudioCaps cap : m_audioCaps) { - if (*itr == feature || *itr == IPC_AUD_ALL) + if (cap == feature || cap == IPlayerAudioCaps::ALL) return true; } @@ -346,15 +346,14 @@ void CGUIDialogAudioSettings::AudioStreamsOptionFiller(const SettingConstPtr& se { const auto& components = CServiceBroker::GetAppComponents(); const auto appPlayer = components.GetComponent<CApplicationPlayer>(); - int audioStreamCount = appPlayer->GetAudioStreamCount(); + const int audioStreamCount = appPlayer->GetAudioStreamCount(); - std::string strFormat = "{:s} - {:s} - {:d} " + g_localizeStrings.Get(10127); + std::string channelsLabel = g_localizeStrings.Get(10127); std::string strUnknown = "[" + g_localizeStrings.Get(13205) + "]"; // cycle through each audio stream and add it to our list control for (int i = 0; i < audioStreamCount; ++i) { - std::string strItem; std::string strLanguage; AudioStreamInfo info; @@ -363,14 +362,19 @@ void CGUIDialogAudioSettings::AudioStreamsOptionFiller(const SettingConstPtr& se if (!g_LangCodeExpander.Lookup(info.language, strLanguage)) strLanguage = strUnknown; - if (info.name.length() == 0) - info.name = strUnknown; + std::string textInfo = strLanguage; + if (!info.name.empty()) + textInfo += " - " + info.name; - strItem = StringUtils::Format(strFormat, strLanguage, info.name, info.channels); + textInfo += " ("; + if (!info.codecDesc.empty()) + textInfo += info.codecDesc + ", "; - strItem += FormatFlags(info.flags); - strItem += StringUtils::Format(" ({}/{})", i + 1, audioStreamCount); - list.emplace_back(strItem, i); + textInfo += std::to_string(info.channels) + " " + channelsLabel + ")"; + + textInfo += FormatFlags(info.flags); + textInfo += StringUtils::Format(" ({}/{})", i + 1, audioStreamCount); + list.emplace_back(textInfo, i); } if (list.empty()) diff --git a/xbmc/video/dialogs/GUIDialogAudioSettings.h b/xbmc/video/dialogs/GUIDialogAudioSettings.h index de69b77ae7..d07b694e68 100644 --- a/xbmc/video/dialogs/GUIDialogAudioSettings.h +++ b/xbmc/video/dialogs/GUIDialogAudioSettings.h @@ -15,6 +15,7 @@ #include <utility> #include <vector> +enum class IPlayerAudioCaps; class CVariant; struct IntegerSettingOption; @@ -44,7 +45,7 @@ protected: // specialization of CGUIDialogSettingsManualBase void InitializeSettings() override; - bool SupportsAudioFeature(int feature); + bool SupportsAudioFeature(IPlayerAudioCaps feature); void AddAudioStreams(const std::shared_ptr<CSettingGroup>& group, const std::string& settingId); @@ -75,8 +76,8 @@ protected: int m_audioStream; bool m_passthrough = false; - typedef std::vector<int> Features; - Features m_audioCaps; + std::vector<IPlayerAudioCaps> m_audioCaps; + private: static std::string FormatFlags(StreamFlags flags); }; diff --git a/xbmc/video/dialogs/GUIDialogSubtitleSettings.cpp b/xbmc/video/dialogs/GUIDialogSubtitleSettings.cpp index 171c69a294..20dd6d8b4b 100644 --- a/xbmc/video/dialogs/GUIDialogSubtitleSettings.cpp +++ b/xbmc/video/dialogs/GUIDialogSubtitleSettings.cpp @@ -297,18 +297,18 @@ void CGUIDialogSubtitleSettings::InitializeSettings() AddToggle(groupSubtitles, SETTING_SUBTITLE_ENABLE, 13397, SettingLevel::Basic, m_subtitleVisible); // subtitle delay setting - if (SupportsSubtitleFeature(IPC_SUBS_OFFSET)) + if (SupportsSubtitleFeature(IPlayerSubtitleCaps::OFFSET)) { std::shared_ptr<CSettingNumber> settingSubtitleDelay = AddSlider(groupSubtitles, SETTING_SUBTITLE_DELAY, 22006, SettingLevel::Basic, videoSettings.m_SubtitleDelay, 0, -CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_videoSubsDelayRange, 0.1f, CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_videoSubsDelayRange, 22006, usePopup); std::static_pointer_cast<CSettingControlSlider>(settingSubtitleDelay->GetControl())->SetFormatter(SettingFormatterDelay); } // subtitle stream setting - if (SupportsSubtitleFeature(IPC_SUBS_SELECT)) + if (SupportsSubtitleFeature(IPlayerSubtitleCaps::SELECT_STREAM)) AddSubtitleStreams(groupSubtitles, SETTING_SUBTITLE_STREAM); // subtitle browser setting - if (SupportsSubtitleFeature(IPC_SUBS_EXTERNAL)) + if (SupportsSubtitleFeature(IPlayerSubtitleCaps::EXTERNAL)) AddButton(groupSubtitles, SETTING_SUBTITLE_BROWSER, 13250, SettingLevel::Basic); AddButton(groupSubtitles, SETTING_SUBTITLE_SEARCH, 24134, SettingLevel::Basic); @@ -317,11 +317,11 @@ void CGUIDialogSubtitleSettings::InitializeSettings() AddButton(groupSaveAsDefault, SETTING_MAKE_DEFAULT, 12376, SettingLevel::Basic); } -bool CGUIDialogSubtitleSettings::SupportsSubtitleFeature(int feature) +bool CGUIDialogSubtitleSettings::SupportsSubtitleFeature(IPlayerSubtitleCaps feature) { - for (auto item : m_subtitleCapabilities) + for (IPlayerSubtitleCaps cap : m_subtitleCapabilities) { - if (item == feature || item == IPC_SUBS_ALL) + if (cap == feature || cap == IPlayerSubtitleCaps::ALL) return true; } return false; @@ -417,6 +417,8 @@ std::string CGUIDialogSubtitleSettings::FormatFlags(StreamFlags flags) localizedFlags.emplace_back(g_localizeStrings.Get(39107)); if (flags & StreamFlags::FLAG_VISUAL_IMPAIRED) localizedFlags.emplace_back(g_localizeStrings.Get(39108)); + if (flags & StreamFlags::FLAG_ORIGINAL) + localizedFlags.emplace_back(g_localizeStrings.Get(39111)); std::string formated = StringUtils::Join(localizedFlags, ", "); diff --git a/xbmc/video/dialogs/GUIDialogSubtitleSettings.h b/xbmc/video/dialogs/GUIDialogSubtitleSettings.h index 65216ede38..5889734939 100644 --- a/xbmc/video/dialogs/GUIDialogSubtitleSettings.h +++ b/xbmc/video/dialogs/GUIDialogSubtitleSettings.h @@ -15,6 +15,7 @@ #include <utility> #include <vector> +enum class IPlayerSubtitleCaps; class CVariant; struct IntegerSettingOption; @@ -44,7 +45,7 @@ protected: void InitializeSettings() override; private: - bool SupportsSubtitleFeature(int feature); + bool SupportsSubtitleFeature(IPlayerSubtitleCaps feature); void AddSubtitleStreams(const std::shared_ptr<CSettingGroup>& group, const std::string& settingId); @@ -53,7 +54,7 @@ private: bool m_subtitleVisible; std::shared_ptr<CSettingInt> m_subtitleStreamSetting; - std::vector<int> m_subtitleCapabilities; + std::vector<IPlayerSubtitleCaps> m_subtitleCapabilities; static std::string FormatFlags(StreamFlags flags); static void SubtitleStreamsOptionFiller(const std::shared_ptr<const CSetting>& setting, diff --git a/xbmc/video/guilib/CMakeLists.txt b/xbmc/video/guilib/CMakeLists.txt index 64192185c6..83205a56a4 100644 --- a/xbmc/video/guilib/CMakeLists.txt +++ b/xbmc/video/guilib/CMakeLists.txt @@ -1,12 +1,14 @@ set(SOURCES VideoGUIUtils.cpp VideoPlayActionProcessor.cpp VideoSelectActionProcessor.cpp + VideoStreamSelectHelper.cpp VideoVersionHelper.cpp) set(HEADERS VideoAction.h VideoGUIUtils.h VideoPlayActionProcessor.h VideoSelectActionProcessor.h + VideoStreamSelectHelper.h VideoVersionHelper.h) core_add_library(video_guilib) diff --git a/xbmc/video/guilib/VideoStreamSelectHelper.cpp b/xbmc/video/guilib/VideoStreamSelectHelper.cpp new file mode 100644 index 0000000000..93d7a80936 --- /dev/null +++ b/xbmc/video/guilib/VideoStreamSelectHelper.cpp @@ -0,0 +1,518 @@ +/* + * 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 "VideoStreamSelectHelper.h" + +#include "FileItem.h" +#include "FileItemList.h" +#include "ServiceBroker.h" +#include "application/ApplicationComponents.h" +#include "application/ApplicationPlayer.h" +#include "dialogs/GUIDialogSelect.h" +#include "guilib/GUIComponent.h" +#include "guilib/GUIWindowManager.h" +#include "guilib/LocalizeStrings.h" +#include "utils/LangCodeExpander.h" +#include "utils/StreamDetails.h" +#include "utils/StringUtils.h" +#include "utils/log.h" + +namespace +{ +constexpr int STREAM_ID_DISABLE = -2; // Stream id referred to item that disable the stream +constexpr int STREAM_ID_NONE = -1; // Stream id referred to item for "none" stream + +// \brief Make a FileItem entry with "Disable" label, allow to disable a stream +const std::shared_ptr<CFileItem> MakeFileItemDisable(bool isSelected) +{ + const auto fileItem{std::make_shared<CFileItem>(g_localizeStrings.Get(24021))}; + fileItem->Select(isSelected); + fileItem->SetProperty("stream.id", STREAM_ID_DISABLE); + return fileItem; +} + +// \brief Make a FileItem entry with "None" label, signal an empty list +const std::shared_ptr<CFileItem> MakeFileItemNone() +{ + const auto fileItem{std::make_shared<CFileItem>(g_localizeStrings.Get(231))}; + fileItem->SetProperty("stream.id", STREAM_ID_NONE); + return fileItem; +} + +std::shared_ptr<const CFileItem> OpenSelectDialog(CGUIDialogSelect& dialog, + int headingId, + const CFileItemList& itemsToDisplay) +{ + dialog.Reset(); + dialog.SetHeading(headingId); + dialog.SetUseDetails(true); + dialog.SetMultiSelection(false); + dialog.SetItems(itemsToDisplay); + + dialog.Open(); + + if (dialog.IsConfirmed()) + return dialog.GetSelectedFileItem(); + + return {}; +} + +std::string ConvertFpsToString(float value) +{ + if (value == 0.0f) + return ""; + + std::string str{StringUtils::Format("{:.3f}", value)}; + // Keep numbers after the comma only if they are not 0 + const size_t zeroPos = str.find_last_not_of("0"); + if (zeroPos != std::string::npos) + str.erase(zeroPos + 1); + + if (str.back() == '.') + str.pop_back(); + + return str; +} + +struct VideoStreamInfoExt : VideoStreamInfo +{ + VideoStreamInfoExt(int id, const VideoStreamInfo& info) : VideoStreamInfo(info) + { + streamId = id; + isDefault = info.flags & StreamFlags::FLAG_DEFAULT; + isForced = info.flags & StreamFlags::FLAG_FORCED; + isHearingImpaired = info.flags & StreamFlags::FLAG_HEARING_IMPAIRED; + isVisualImpaired = info.flags & StreamFlags::FLAG_VISUAL_IMPAIRED; + } + + int streamId{0}; + std::string languageDesc; + bool isDefault{false}; + bool isForced{false}; + bool isHearingImpaired{false}; + bool isVisualImpaired{false}; +}; + +struct AudioStreamInfoExt : AudioStreamInfo +{ + AudioStreamInfoExt(int id, const AudioStreamInfo& info) : AudioStreamInfo(info) + { + streamId = id; + + if (!g_LangCodeExpander.Lookup(info.language, languageDesc)) + languageDesc = g_localizeStrings.Get(13205); // Unknown + + isDefault = info.flags & StreamFlags::FLAG_DEFAULT; + isForced = info.flags & StreamFlags::FLAG_FORCED; + isHearingImpaired = info.flags & StreamFlags::FLAG_HEARING_IMPAIRED; + isVisualImpaired = info.flags & StreamFlags::FLAG_VISUAL_IMPAIRED; + isOriginal = info.flags & StreamFlags::FLAG_ORIGINAL; + } + + int streamId{0}; + std::string languageDesc; + bool isDefault{false}; + bool isForced{false}; + bool isHearingImpaired{false}; + bool isVisualImpaired{false}; + bool isOriginal{false}; +}; + +struct SubtitleStreamInfoExt : SubtitleStreamInfo +{ + SubtitleStreamInfoExt(int id, const SubtitleStreamInfo& info) : SubtitleStreamInfo(info) + { + streamId = id; + + if (!g_LangCodeExpander.Lookup(info.language, languageDesc)) + languageDesc = g_localizeStrings.Get(13205); // Unknown + + isDefault = info.flags & StreamFlags::FLAG_DEFAULT; + isForced = info.flags & StreamFlags::FLAG_FORCED; + isHearingImpaired = info.flags & StreamFlags::FLAG_HEARING_IMPAIRED; + isVisualImpaired = info.flags & StreamFlags::FLAG_VISUAL_IMPAIRED; + isOriginal = info.flags & StreamFlags::FLAG_ORIGINAL; + } + + int streamId{0}; + std::string languageDesc; + bool isDefault{false}; + bool isForced{false}; + bool isHearingImpaired{false}; + bool isVisualImpaired{false}; + bool isOriginal{false}; +}; + +struct SortComparerStreamVideo +{ + bool operator()(const VideoStreamInfoExt& a, const VideoStreamInfoExt& b) + { + if (a.language != b.language) + { + return a.language < b.language; + } + if (a.codecName != b.codecName) + { + return a.codecName < b.codecName; + } + if (a.hdrType != b.hdrType) + { + return a.hdrType < b.hdrType; + } + if (a.fpsRate != b.fpsRate) + { + return a.fpsRate < b.fpsRate; + } + if (a.fpsScale != b.fpsScale) + { + return a.fpsScale < b.fpsScale; + } + if (a.height != b.height) + { + return a.height < b.height; + } + if (a.width != b.width) + { + return a.width < b.width; + } + return a.bitrate < b.bitrate; + } +}; + +struct SortComparerStreamAudio +{ + bool operator()(const AudioStreamInfoExt& a, const AudioStreamInfoExt& b) + { + if (a.languageDesc != b.languageDesc) + { + return a.languageDesc < b.languageDesc; + } + if (a.isOriginal != b.isOriginal) + { + return a.isOriginal < b.isOriginal; + } + if (a.isHearingImpaired != b.isHearingImpaired) + { + return a.isHearingImpaired < b.isHearingImpaired; + } + if (a.isVisualImpaired != b.isVisualImpaired) + { + return a.isVisualImpaired < b.isVisualImpaired; + } + if (a.isForced != b.isForced) + { + return a.isForced < b.isForced; + } + if (a.channels != b.channels) + { + return a.channels < b.channels; + } + if (a.bitrate != b.bitrate) + { + return a.bitrate < b.bitrate; + } + if (a.samplerate != b.samplerate) + { + return a.samplerate < b.samplerate; + } + return a.codecName < b.codecName; + } +}; + +struct SortComparerStreamSubtitle +{ + bool operator()(const SubtitleStreamInfoExt& a, const SubtitleStreamInfoExt& b) + { + if (a.isExternal != b.isExternal) + { + return a.isExternal > b.isExternal; + } + if (a.languageDesc != b.languageDesc) + { + return a.languageDesc < b.languageDesc; + } + if (a.isOriginal != b.isOriginal) + { + return a.isOriginal < b.isOriginal; + } + if (a.isHearingImpaired != b.isHearingImpaired) + { + return a.isHearingImpaired < b.isHearingImpaired; + } + if (a.isVisualImpaired != b.isVisualImpaired) + { + return a.isVisualImpaired < b.isVisualImpaired; + } + if (a.isForced != b.isForced) + { + return a.isForced < b.isForced; + } + return a.codecName < b.codecName; + } +}; + +bool SupportsAudioFeature(IPlayerAudioCaps feature, const std::vector<IPlayerAudioCaps>& caps) +{ + for (IPlayerAudioCaps cap : caps) + { + if (cap == feature || cap == IPlayerAudioCaps::ALL) + return true; + } + + return false; +} + +bool SupportsSubtitleFeature(IPlayerSubtitleCaps feature, + const std::vector<IPlayerSubtitleCaps>& caps) +{ + for (IPlayerSubtitleCaps cap : caps) + { + if (cap == feature || cap == IPlayerSubtitleCaps::ALL) + return true; + } + return false; +} + +} // unnamed namespace + +void KODI::VIDEO::GUILIB::OpenDialogSelectVideoStream() +{ + CGUIDialogSelect* dialog{CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogSelect>( + WINDOW_DIALOG_SELECT_VIDEO_STREAM)}; + if (!dialog) + { + CLog::LogF(LOGERROR, "Unable to get WINDOW_DIALOG_SELECT_VIDEO_STREAM dialog instance"); + return; + } + + auto& components = CServiceBroker::GetAppComponents(); + auto appPlayer = components.GetComponent<CApplicationPlayer>(); + const int streamCount = appPlayer->GetVideoStreamCount(); + const int selectedId = appPlayer->GetVideoStream(); + + std::vector<VideoStreamInfoExt> streams; + streams.reserve(streamCount); + + // Collect all streams + for (int i = 0; i < streamCount; ++i) + { + VideoStreamInfo info; + appPlayer->GetVideoStreamInfo(i, info); + streams.emplace_back(i, info); + } + + // Sort streams + std::sort(streams.begin(), streams.end(), SortComparerStreamVideo()); + + // Convert streams to FileItem's + CFileItemList itemsToDisplay; + itemsToDisplay.Reserve(streams.size()); + + for (const VideoStreamInfoExt& info : streams) + { + const auto fileItem = std::make_shared<CFileItem>(info.name); + fileItem->SetProperty("stream.id", info.streamId); + fileItem->SetProperty("stream.description", info.name); + fileItem->SetProperty("stream.codec", info.codecName); + + std::string languageDesc; + g_LangCodeExpander.Lookup(info.language, languageDesc); + fileItem->SetProperty("stream.language", languageDesc); + + fileItem->SetProperty("stream.resolution", + std::to_string(info.width) + "x" + std::to_string(info.height)); + fileItem->SetProperty("stream.bitrate", + static_cast<int>(std::lrint(static_cast<double>(info.bitrate) / 1000.0))); + + float fps = static_cast<float>(info.fpsRate); + if (fps > 0.0f && info.fpsScale > 0) + fps /= info.fpsScale; + + fileItem->SetProperty("stream.fps", ConvertFpsToString(fps)); + + fileItem->SetProperty("stream.is3d", !info.stereoMode.empty() && info.stereoMode != "mono"); + fileItem->SetProperty("stream.stereomode", info.stereoMode); + fileItem->SetProperty("stream.hdrtype", CStreamDetails::HdrTypeToString(info.hdrType)); + + fileItem->SetProperty("stream.isdefault", info.isDefault); + fileItem->SetProperty("stream.isforced", info.isForced); + fileItem->SetProperty("stream.ishearingimpaired", info.isHearingImpaired); + fileItem->SetProperty("stream.isvisualimpaired", info.isVisualImpaired); + if (selectedId == info.streamId) + fileItem->Select(true); + + itemsToDisplay.Add(fileItem); + } + + if (itemsToDisplay.IsEmpty()) + itemsToDisplay.Add(MakeFileItemNone()); + + const auto selectedItem = OpenSelectDialog(*dialog, 38031, itemsToDisplay); + if (selectedItem) + { + const int id = selectedItem->GetProperty("stream.id").asInteger32(STREAM_ID_NONE); + + if (id != STREAM_ID_NONE) + appPlayer->SetVideoStream(id); + } +} + +void KODI::VIDEO::GUILIB::OpenDialogSelectAudioStream() +{ + CGUIDialogSelect* dialog{CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogSelect>( + WINDOW_DIALOG_SELECT_AUDIO_STREAM)}; + if (!dialog) + { + CLog::LogF(LOGERROR, "Unable to get WINDOW_DIALOG_SELECT_AUDIO_STREAM dialog instance"); + return; + } + + auto& components = CServiceBroker::GetAppComponents(); + auto appPlayer = components.GetComponent<CApplicationPlayer>(); + + std::vector<IPlayerAudioCaps> caps; + appPlayer->GetAudioCapabilities(caps); + if (!SupportsAudioFeature(IPlayerAudioCaps::SELECT_STREAM, caps)) + return; + + const int streamCount = appPlayer->GetAudioStreamCount(); + const int selectedId = appPlayer->GetAudioStream(); + + std::vector<AudioStreamInfoExt> streams; + streams.reserve(streamCount); + + // Collect all streams + for (int i = 0; i < streamCount; ++i) + { + AudioStreamInfo info; + appPlayer->GetAudioStreamInfo(i, info); + streams.emplace_back(i, info); + } + + // Sort streams + std::sort(streams.begin(), streams.end(), SortComparerStreamAudio()); + + // Convert streams to FileItem's + CFileItemList itemsToDisplay; + itemsToDisplay.Reserve(streams.size()); + + for (const AudioStreamInfoExt& info : streams) + { + CFileItemPtr fileItem = std::make_shared<CFileItem>(info.languageDesc); + fileItem->SetProperty("stream.id", info.streamId); + fileItem->SetProperty("stream.description", info.name); + fileItem->SetProperty("stream.codec", info.codecName); + fileItem->SetProperty("stream.codecdesc", info.codecDesc); + fileItem->SetProperty("stream.channels", info.channels); + + fileItem->SetProperty("stream.isdefault", info.isDefault); + fileItem->SetProperty("stream.isforced", info.isForced); + fileItem->SetProperty("stream.ishearingimpaired", info.isHearingImpaired); + fileItem->SetProperty("stream.isvisualimpaired", info.isVisualImpaired); + fileItem->SetProperty("stream.isoriginal", info.isOriginal); + if (selectedId == info.streamId) + fileItem->Select(true); + + itemsToDisplay.Add(fileItem); + } + + if (itemsToDisplay.IsEmpty()) + itemsToDisplay.Add(MakeFileItemNone()); + + const auto selectedItem = OpenSelectDialog(*dialog, 460, itemsToDisplay); + if (selectedItem) + { + const int id = selectedItem->GetProperty("stream.id").asInteger32(STREAM_ID_NONE); + + if (id != STREAM_ID_NONE) + appPlayer->SetAudioStream(id); + } +} + +void KODI::VIDEO::GUILIB::OpenDialogSelectSubtitleStream() +{ + CGUIDialogSelect* dialog{CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogSelect>( + WINDOW_DIALOG_SELECT_SUBTITLE_STREAM)}; + if (!dialog) + { + CLog::LogF(LOGERROR, "Unable to get WINDOW_DIALOG_SELECT_SUBTITLE_STREAM dialog instance"); + return; + } + + auto& components = CServiceBroker::GetAppComponents(); + auto appPlayer = components.GetComponent<CApplicationPlayer>(); + + std::vector<IPlayerSubtitleCaps> caps; + appPlayer->GetSubtitleCapabilities(caps); + if (!SupportsSubtitleFeature(IPlayerSubtitleCaps::SELECT_STREAM, caps)) + return; + + const int streamCount = appPlayer->GetSubtitleCount(); + const int selectedId = appPlayer->GetSubtitle(); + const bool isSubtitleEnabled = appPlayer->GetSubtitleVisible(); + + std::vector<SubtitleStreamInfoExt> streams; + streams.reserve(streamCount); + + // Collect all streams + for (int i = 0; i < streamCount; ++i) + { + SubtitleStreamInfo info; + appPlayer->GetSubtitleStreamInfo(i, info); + streams.emplace_back(i, info); + } + + // Sort streams + std::sort(streams.begin(), streams.end(), SortComparerStreamSubtitle()); + + // Convert streams to FileItem's + CFileItemList itemsToDisplay; + itemsToDisplay.Reserve(streams.size() + 1); + + for (const SubtitleStreamInfoExt& info : streams) + { + CFileItemPtr fileItem = std::make_shared<CFileItem>(info.languageDesc); + fileItem->SetProperty("stream.id", info.streamId); + fileItem->SetProperty("stream.description", info.name); + fileItem->SetProperty("stream.codec", info.codecName); + + fileItem->SetProperty("stream.isdefault", info.isDefault); + fileItem->SetProperty("stream.isforced", info.isForced); + fileItem->SetProperty("stream.isoriginal", info.isOriginal); + fileItem->SetProperty("stream.ishearingimpaired", info.isHearingImpaired); + fileItem->SetProperty("stream.isvisualimpaired", info.isVisualImpaired); + fileItem->SetProperty("stream.isexternal", info.isExternal); + if (selectedId == info.streamId && isSubtitleEnabled) + fileItem->Select(true); + + itemsToDisplay.Add(fileItem); + } + + if (itemsToDisplay.IsEmpty()) + itemsToDisplay.Add(MakeFileItemNone()); + else + itemsToDisplay.AddFront(MakeFileItemDisable(!isSubtitleEnabled), 0); + + const auto selectedItem = OpenSelectDialog(*dialog, 462, itemsToDisplay); + if (selectedItem) + { + const int id = selectedItem->GetProperty("stream.id").asInteger32(STREAM_ID_NONE); + + if (id == STREAM_ID_DISABLE) + { + appPlayer->SetSubtitleVisible(false); + } + else if (id != STREAM_ID_NONE) + { + appPlayer->SetSubtitle(id); + + if (!appPlayer->GetSubtitleVisible()) + appPlayer->SetSubtitleVisible(true); + } + } +} diff --git a/xbmc/video/guilib/VideoStreamSelectHelper.h b/xbmc/video/guilib/VideoStreamSelectHelper.h new file mode 100644 index 0000000000..598682422b --- /dev/null +++ b/xbmc/video/guilib/VideoStreamSelectHelper.h @@ -0,0 +1,29 @@ +/* + * 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 KODI::VIDEO::GUILIB +{ + +/*! + * \brief Open dialog window to select/change video stream + */ +void OpenDialogSelectVideoStream(); + +/*! + * \brief Open dialog window to select/change audio stream + */ +void OpenDialogSelectAudioStream(); + +/*! + * \brief Open dialog window to select/change subtitle stream + */ +void OpenDialogSelectSubtitleStream(); + +} // namespace KODI::VIDEO::GUILIB |