2 * Copyright (C) 2024 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
9 #include "VideoStreamSelectHelper.h"
12 #include "FileItemList.h"
13 #include "ServiceBroker.h"
14 #include "application/ApplicationComponents.h"
15 #include "application/ApplicationPlayer.h"
16 #include "dialogs/GUIDialogSelect.h"
17 #include "guilib/GUIComponent.h"
18 #include "guilib/GUIWindowManager.h"
19 #include "guilib/LocalizeStrings.h"
20 #include "utils/LangCodeExpander.h"
21 #include "utils/StreamDetails.h"
22 #include "utils/StringUtils.h"
23 #include "utils/log.h"
27 constexpr int STREAM_ID_DISABLE
= -2; // Stream id referred to item that disable the stream
28 constexpr int STREAM_ID_NONE
= -1; // Stream id referred to item for "none" stream
30 // \brief Make a FileItem entry with "Disable" label, allow to disable a stream
31 const std::shared_ptr
<CFileItem
> MakeFileItemDisable(bool isSelected
)
33 const auto fileItem
{std::make_shared
<CFileItem
>(g_localizeStrings
.Get(24021))};
34 fileItem
->Select(isSelected
);
35 fileItem
->SetProperty("stream.id", STREAM_ID_DISABLE
);
39 // \brief Make a FileItem entry with "None" label, signal an empty list
40 const std::shared_ptr
<CFileItem
> MakeFileItemNone()
42 const auto fileItem
{std::make_shared
<CFileItem
>(g_localizeStrings
.Get(231))};
43 fileItem
->SetProperty("stream.id", STREAM_ID_NONE
);
47 std::shared_ptr
<const CFileItem
> OpenSelectDialog(CGUIDialogSelect
& dialog
,
49 const CFileItemList
& itemsToDisplay
)
52 dialog
.SetHeading(headingId
);
53 dialog
.SetUseDetails(true);
54 dialog
.SetMultiSelection(false);
55 dialog
.SetItems(itemsToDisplay
);
59 if (dialog
.IsConfirmed())
60 return dialog
.GetSelectedFileItem();
65 std::string
ConvertFpsToString(float value
)
70 std::string str
{StringUtils::Format("{:.3f}", value
)};
71 // Keep numbers after the comma only if they are not 0
72 const size_t zeroPos
= str
.find_last_not_of("0");
73 if (zeroPos
!= std::string::npos
)
74 str
.erase(zeroPos
+ 1);
76 if (str
.back() == '.')
82 struct VideoStreamInfoExt
: VideoStreamInfo
84 VideoStreamInfoExt(int id
, const VideoStreamInfo
& info
) : VideoStreamInfo(info
)
87 isDefault
= info
.flags
& StreamFlags::FLAG_DEFAULT
;
88 isForced
= info
.flags
& StreamFlags::FLAG_FORCED
;
89 isHearingImpaired
= info
.flags
& StreamFlags::FLAG_HEARING_IMPAIRED
;
90 isVisualImpaired
= info
.flags
& StreamFlags::FLAG_VISUAL_IMPAIRED
;
94 std::string languageDesc
;
95 bool isDefault
{false};
97 bool isHearingImpaired
{false};
98 bool isVisualImpaired
{false};
101 struct AudioStreamInfoExt
: AudioStreamInfo
103 AudioStreamInfoExt(int id
, const AudioStreamInfo
& info
) : AudioStreamInfo(info
)
107 if (!g_LangCodeExpander
.Lookup(info
.language
, languageDesc
))
108 languageDesc
= g_localizeStrings
.Get(13205); // Unknown
110 isDefault
= info
.flags
& StreamFlags::FLAG_DEFAULT
;
111 isForced
= info
.flags
& StreamFlags::FLAG_FORCED
;
112 isHearingImpaired
= info
.flags
& StreamFlags::FLAG_HEARING_IMPAIRED
;
113 isVisualImpaired
= info
.flags
& StreamFlags::FLAG_VISUAL_IMPAIRED
;
114 isOriginal
= info
.flags
& StreamFlags::FLAG_ORIGINAL
;
118 std::string languageDesc
;
119 bool isDefault
{false};
120 bool isForced
{false};
121 bool isHearingImpaired
{false};
122 bool isVisualImpaired
{false};
123 bool isOriginal
{false};
126 struct SubtitleStreamInfoExt
: SubtitleStreamInfo
128 SubtitleStreamInfoExt(int id
, const SubtitleStreamInfo
& info
) : SubtitleStreamInfo(info
)
132 if (!g_LangCodeExpander
.Lookup(info
.language
, languageDesc
))
133 languageDesc
= g_localizeStrings
.Get(13205); // Unknown
135 isDefault
= info
.flags
& StreamFlags::FLAG_DEFAULT
;
136 isForced
= info
.flags
& StreamFlags::FLAG_FORCED
;
137 isHearingImpaired
= info
.flags
& StreamFlags::FLAG_HEARING_IMPAIRED
;
138 isVisualImpaired
= info
.flags
& StreamFlags::FLAG_VISUAL_IMPAIRED
;
139 isOriginal
= info
.flags
& StreamFlags::FLAG_ORIGINAL
;
143 std::string languageDesc
;
144 bool isDefault
{false};
145 bool isForced
{false};
146 bool isHearingImpaired
{false};
147 bool isVisualImpaired
{false};
148 bool isOriginal
{false};
151 struct SortComparerStreamVideo
153 bool operator()(const VideoStreamInfoExt
& a
, const VideoStreamInfoExt
& b
)
155 if (a
.language
!= b
.language
)
157 return a
.language
< b
.language
;
159 if (a
.codecName
!= b
.codecName
)
161 return a
.codecName
< b
.codecName
;
163 if (a
.hdrType
!= b
.hdrType
)
165 return a
.hdrType
< b
.hdrType
;
167 if (a
.fpsRate
!= b
.fpsRate
)
169 return a
.fpsRate
< b
.fpsRate
;
171 if (a
.fpsScale
!= b
.fpsScale
)
173 return a
.fpsScale
< b
.fpsScale
;
175 if (a
.height
!= b
.height
)
177 return a
.height
< b
.height
;
179 if (a
.width
!= b
.width
)
181 return a
.width
< b
.width
;
183 return a
.bitrate
< b
.bitrate
;
187 struct SortComparerStreamAudio
189 bool operator()(const AudioStreamInfoExt
& a
, const AudioStreamInfoExt
& b
)
191 if (a
.languageDesc
!= b
.languageDesc
)
193 return a
.languageDesc
< b
.languageDesc
;
195 if (a
.isOriginal
!= b
.isOriginal
)
197 return a
.isOriginal
< b
.isOriginal
;
199 if (a
.isHearingImpaired
!= b
.isHearingImpaired
)
201 return a
.isHearingImpaired
< b
.isHearingImpaired
;
203 if (a
.isVisualImpaired
!= b
.isVisualImpaired
)
205 return a
.isVisualImpaired
< b
.isVisualImpaired
;
207 if (a
.isForced
!= b
.isForced
)
209 return a
.isForced
< b
.isForced
;
211 if (a
.channels
!= b
.channels
)
213 return a
.channels
< b
.channels
;
215 if (a
.bitrate
!= b
.bitrate
)
217 return a
.bitrate
< b
.bitrate
;
219 if (a
.samplerate
!= b
.samplerate
)
221 return a
.samplerate
< b
.samplerate
;
223 return a
.codecName
< b
.codecName
;
227 struct SortComparerStreamSubtitle
229 bool operator()(const SubtitleStreamInfoExt
& a
, const SubtitleStreamInfoExt
& b
)
231 if (a
.isExternal
!= b
.isExternal
)
233 return a
.isExternal
> b
.isExternal
;
235 if (a
.languageDesc
!= b
.languageDesc
)
237 return a
.languageDesc
< b
.languageDesc
;
239 if (a
.isOriginal
!= b
.isOriginal
)
241 return a
.isOriginal
< b
.isOriginal
;
243 if (a
.isHearingImpaired
!= b
.isHearingImpaired
)
245 return a
.isHearingImpaired
< b
.isHearingImpaired
;
247 if (a
.isVisualImpaired
!= b
.isVisualImpaired
)
249 return a
.isVisualImpaired
< b
.isVisualImpaired
;
251 if (a
.isForced
!= b
.isForced
)
253 return a
.isForced
< b
.isForced
;
255 return a
.codecName
< b
.codecName
;
259 bool SupportsAudioFeature(IPlayerAudioCaps feature
, const std::vector
<IPlayerAudioCaps
>& caps
)
261 for (IPlayerAudioCaps cap
: caps
)
263 if (cap
== feature
|| cap
== IPlayerAudioCaps::ALL
)
270 bool SupportsSubtitleFeature(IPlayerSubtitleCaps feature
,
271 const std::vector
<IPlayerSubtitleCaps
>& caps
)
273 for (IPlayerSubtitleCaps cap
: caps
)
275 if (cap
== feature
|| cap
== IPlayerSubtitleCaps::ALL
)
281 } // unnamed namespace
283 void KODI::VIDEO::GUILIB::OpenDialogSelectVideoStream()
285 CGUIDialogSelect
* dialog
{CServiceBroker::GetGUI()->GetWindowManager().GetWindow
<CGUIDialogSelect
>(
286 WINDOW_DIALOG_SELECT_VIDEO_STREAM
)};
289 CLog::LogF(LOGERROR
, "Unable to get WINDOW_DIALOG_SELECT_VIDEO_STREAM dialog instance");
293 auto& components
= CServiceBroker::GetAppComponents();
294 auto appPlayer
= components
.GetComponent
<CApplicationPlayer
>();
295 const int streamCount
= appPlayer
->GetVideoStreamCount();
296 const int selectedId
= appPlayer
->GetVideoStream();
298 std::vector
<VideoStreamInfoExt
> streams
;
299 streams
.reserve(streamCount
);
301 // Collect all streams
302 for (int i
= 0; i
< streamCount
; ++i
)
304 VideoStreamInfo info
;
305 appPlayer
->GetVideoStreamInfo(i
, info
);
306 streams
.emplace_back(i
, info
);
310 std::sort(streams
.begin(), streams
.end(), SortComparerStreamVideo());
312 // Convert streams to FileItem's
313 CFileItemList itemsToDisplay
;
314 itemsToDisplay
.Reserve(streams
.size());
316 for (const VideoStreamInfoExt
& info
: streams
)
318 const auto fileItem
= std::make_shared
<CFileItem
>(info
.name
);
319 fileItem
->SetProperty("stream.id", info
.streamId
);
320 fileItem
->SetProperty("stream.description", info
.name
);
321 fileItem
->SetProperty("stream.codec", info
.codecName
);
323 std::string languageDesc
;
324 g_LangCodeExpander
.Lookup(info
.language
, languageDesc
);
325 fileItem
->SetProperty("stream.language", languageDesc
);
327 fileItem
->SetProperty("stream.resolution",
328 std::to_string(info
.width
) + "x" + std::to_string(info
.height
));
329 fileItem
->SetProperty("stream.bitrate",
330 static_cast<int>(std::lrint(static_cast<double>(info
.bitrate
) / 1000.0)));
332 float fps
= static_cast<float>(info
.fpsRate
);
333 if (fps
> 0.0f
&& info
.fpsScale
> 0)
334 fps
/= info
.fpsScale
;
336 fileItem
->SetProperty("stream.fps", ConvertFpsToString(fps
));
338 fileItem
->SetProperty("stream.is3d", !info
.stereoMode
.empty() && info
.stereoMode
!= "mono");
339 fileItem
->SetProperty("stream.stereomode", info
.stereoMode
);
340 fileItem
->SetProperty("stream.hdrtype", CStreamDetails::HdrTypeToString(info
.hdrType
));
342 fileItem
->SetProperty("stream.isdefault", info
.isDefault
);
343 fileItem
->SetProperty("stream.isforced", info
.isForced
);
344 fileItem
->SetProperty("stream.ishearingimpaired", info
.isHearingImpaired
);
345 fileItem
->SetProperty("stream.isvisualimpaired", info
.isVisualImpaired
);
346 if (selectedId
== info
.streamId
)
347 fileItem
->Select(true);
349 itemsToDisplay
.Add(fileItem
);
352 if (itemsToDisplay
.IsEmpty())
353 itemsToDisplay
.Add(MakeFileItemNone());
355 const auto selectedItem
= OpenSelectDialog(*dialog
, 38031, itemsToDisplay
);
358 const int id
= selectedItem
->GetProperty("stream.id").asInteger32(STREAM_ID_NONE
);
360 if (id
!= STREAM_ID_NONE
)
361 appPlayer
->SetVideoStream(id
);
365 void KODI::VIDEO::GUILIB::OpenDialogSelectAudioStream()
367 CGUIDialogSelect
* dialog
{CServiceBroker::GetGUI()->GetWindowManager().GetWindow
<CGUIDialogSelect
>(
368 WINDOW_DIALOG_SELECT_AUDIO_STREAM
)};
371 CLog::LogF(LOGERROR
, "Unable to get WINDOW_DIALOG_SELECT_AUDIO_STREAM dialog instance");
375 auto& components
= CServiceBroker::GetAppComponents();
376 auto appPlayer
= components
.GetComponent
<CApplicationPlayer
>();
378 std::vector
<IPlayerAudioCaps
> caps
;
379 appPlayer
->GetAudioCapabilities(caps
);
380 if (!SupportsAudioFeature(IPlayerAudioCaps::SELECT_STREAM
, caps
))
383 const int streamCount
= appPlayer
->GetAudioStreamCount();
384 const int selectedId
= appPlayer
->GetAudioStream();
386 std::vector
<AudioStreamInfoExt
> streams
;
387 streams
.reserve(streamCount
);
389 // Collect all streams
390 for (int i
= 0; i
< streamCount
; ++i
)
392 AudioStreamInfo info
;
393 appPlayer
->GetAudioStreamInfo(i
, info
);
394 streams
.emplace_back(i
, info
);
398 std::sort(streams
.begin(), streams
.end(), SortComparerStreamAudio());
400 // Convert streams to FileItem's
401 CFileItemList itemsToDisplay
;
402 itemsToDisplay
.Reserve(streams
.size());
404 for (const AudioStreamInfoExt
& info
: streams
)
406 CFileItemPtr fileItem
= std::make_shared
<CFileItem
>(info
.languageDesc
);
407 fileItem
->SetProperty("stream.id", info
.streamId
);
408 fileItem
->SetProperty("stream.description", info
.name
);
409 fileItem
->SetProperty("stream.codec", info
.codecName
);
410 fileItem
->SetProperty("stream.codecdesc", info
.codecDesc
);
411 fileItem
->SetProperty("stream.channels", info
.channels
);
413 fileItem
->SetProperty("stream.isdefault", info
.isDefault
);
414 fileItem
->SetProperty("stream.isforced", info
.isForced
);
415 fileItem
->SetProperty("stream.ishearingimpaired", info
.isHearingImpaired
);
416 fileItem
->SetProperty("stream.isvisualimpaired", info
.isVisualImpaired
);
417 fileItem
->SetProperty("stream.isoriginal", info
.isOriginal
);
418 if (selectedId
== info
.streamId
)
419 fileItem
->Select(true);
421 itemsToDisplay
.Add(fileItem
);
424 if (itemsToDisplay
.IsEmpty())
425 itemsToDisplay
.Add(MakeFileItemNone());
427 const auto selectedItem
= OpenSelectDialog(*dialog
, 460, itemsToDisplay
);
430 const int id
= selectedItem
->GetProperty("stream.id").asInteger32(STREAM_ID_NONE
);
432 if (id
!= STREAM_ID_NONE
)
433 appPlayer
->SetAudioStream(id
);
437 void KODI::VIDEO::GUILIB::OpenDialogSelectSubtitleStream()
439 CGUIDialogSelect
* dialog
{CServiceBroker::GetGUI()->GetWindowManager().GetWindow
<CGUIDialogSelect
>(
440 WINDOW_DIALOG_SELECT_SUBTITLE_STREAM
)};
443 CLog::LogF(LOGERROR
, "Unable to get WINDOW_DIALOG_SELECT_SUBTITLE_STREAM dialog instance");
447 auto& components
= CServiceBroker::GetAppComponents();
448 auto appPlayer
= components
.GetComponent
<CApplicationPlayer
>();
450 std::vector
<IPlayerSubtitleCaps
> caps
;
451 appPlayer
->GetSubtitleCapabilities(caps
);
452 if (!SupportsSubtitleFeature(IPlayerSubtitleCaps::SELECT_STREAM
, caps
))
455 const int streamCount
= appPlayer
->GetSubtitleCount();
456 const int selectedId
= appPlayer
->GetSubtitle();
457 const bool isSubtitleEnabled
= appPlayer
->GetSubtitleVisible();
459 std::vector
<SubtitleStreamInfoExt
> streams
;
460 streams
.reserve(streamCount
);
462 // Collect all streams
463 for (int i
= 0; i
< streamCount
; ++i
)
465 SubtitleStreamInfo info
;
466 appPlayer
->GetSubtitleStreamInfo(i
, info
);
467 streams
.emplace_back(i
, info
);
471 std::sort(streams
.begin(), streams
.end(), SortComparerStreamSubtitle());
473 // Convert streams to FileItem's
474 CFileItemList itemsToDisplay
;
475 itemsToDisplay
.Reserve(streams
.size() + 1);
477 for (const SubtitleStreamInfoExt
& info
: streams
)
479 CFileItemPtr fileItem
= std::make_shared
<CFileItem
>(info
.languageDesc
);
480 fileItem
->SetProperty("stream.id", info
.streamId
);
481 fileItem
->SetProperty("stream.description", info
.name
);
482 fileItem
->SetProperty("stream.codec", info
.codecName
);
484 fileItem
->SetProperty("stream.isdefault", info
.isDefault
);
485 fileItem
->SetProperty("stream.isforced", info
.isForced
);
486 fileItem
->SetProperty("stream.isoriginal", info
.isOriginal
);
487 fileItem
->SetProperty("stream.ishearingimpaired", info
.isHearingImpaired
);
488 fileItem
->SetProperty("stream.isvisualimpaired", info
.isVisualImpaired
);
489 fileItem
->SetProperty("stream.isexternal", info
.isExternal
);
490 if (selectedId
== info
.streamId
&& isSubtitleEnabled
)
491 fileItem
->Select(true);
493 itemsToDisplay
.Add(fileItem
);
496 if (itemsToDisplay
.IsEmpty())
497 itemsToDisplay
.Add(MakeFileItemNone());
499 itemsToDisplay
.AddFront(MakeFileItemDisable(!isSubtitleEnabled
), 0);
501 const auto selectedItem
= OpenSelectDialog(*dialog
, 462, itemsToDisplay
);
504 const int id
= selectedItem
->GetProperty("stream.id").asInteger32(STREAM_ID_NONE
);
506 if (id
== STREAM_ID_DISABLE
)
508 appPlayer
->SetSubtitleVisible(false);
510 else if (id
!= STREAM_ID_NONE
)
512 appPlayer
->SetSubtitle(id
);
514 if (!appPlayer
->GetSubtitleVisible())
515 appPlayer
->SetSubtitleVisible(true);