Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[PropertiesUtils] Add streams_config property #1250

Draft
wants to merge 2 commits into
base: Omega
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion inputstream.adaptive/addon.xml.in
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
name="adaptive"
extension=""
tags="true"
listitemprops="license_type|license_key|license_data|license_flags|manifest_type|server_certificate|manifest_update_parameter|manifest_params|manifest_headers|stream_params|stream_headers|original_audio_language|play_timeshift_buffer|pre_init_data|stream_selection_type|chooser_bandwidth_max|chooser_resolution_max|chooser_resolution_secure_max|live_delay"
listitemprops="license_type|license_key|license_data|license_flags|manifest_type|server_certificate|manifest_update_parameter|manifest_params|manifest_headers|stream_params|stream_headers|original_audio_language|play_timeshift_buffer|pre_init_data|stream_selection_type|chooser_bandwidth_max|chooser_resolution_max|chooser_resolution_secure_max|live_delay|streams_config"
library_@PLATFORM@="@LIBRARY_FILENAME@"/>
<extension point="xbmc.addon.metadata">
<platform>@PLATFORM@</platform>
Expand Down
7 changes: 2 additions & 5 deletions src/Session.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -798,13 +798,10 @@ void CSession::AddStream(PLAYLIST::CAdaptationSet* adp,
stream.m_info.SetStreamType(INPUTSTREAM_TYPE_AUDIO);
if (adp->IsImpaired())
flags |= INPUTSTREAM_FLAG_VISUAL_IMPAIRED;
if (adp->IsOriginal())
flags |= INPUTSTREAM_FLAG_ORIGINAL;
if (adp->IsDefault())
flags |= INPUTSTREAM_FLAG_DEFAULT;
if (adp->IsOriginal() || (!m_kodiProps.m_audioLanguageOrig.empty() &&
adp->GetLanguage() == m_kodiProps.m_audioLanguageOrig))
{
flags |= INPUTSTREAM_FLAG_ORIGINAL;
}
break;
}
case StreamType::SUBTITLE:
Expand Down
21 changes: 21 additions & 0 deletions src/common/AdaptationSet.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -157,3 +157,24 @@ bool PLAYLIST::CAdaptationSet::Compare(const std::unique_ptr<CAdaptationSet>& le

return false;
}

std::vector<std::unique_ptr<CAdaptationSet>>::const_iterator PLAYLIST::CAdaptationSet::
FindAudioAdpSet(const std::vector<std::unique_ptr<CAdaptationSet>>& adpSets,
const std::string langCode,
bool isPreferStereo,
bool filterImpaired)
{
for (auto& itAdpSet = adpSets.cbegin(); itAdpSet != adpSets.cend(); itAdpSet++)
{
auto adpSet = itAdpSet->get();
if (adpSet->GetStreamType() == StreamType::AUDIO &&
STRING::CompareNoCase(adpSet->GetLanguage(), langCode) &&
(isPreferStereo ? adpSet->GetRepresentations()[0]->GetAudioChannels() <= 2
: adpSet->GetRepresentations()[0]->GetAudioChannels() > 2) &&
adpSet->IsImpaired() == filterImpaired)
{
return itAdpSet;
}
}
return adpSets.end();
}
6 changes: 6 additions & 0 deletions src/common/AdaptationSet.h
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,12 @@ class ATTR_DLL_LOCAL CAdaptationSet : public CCommonSegAttribs, public CCommonAt
static bool Compare(const std::unique_ptr<CAdaptationSet>& left,
const std::unique_ptr<CAdaptationSet>& right);

static std::vector<std::unique_ptr<CAdaptationSet>>::const_iterator FindAudioAdpSet(
const std::vector<std::unique_ptr<CAdaptationSet>>& adpSets,
const std::string langCode,
bool isPreferStereo,
bool filterImpaired = false);

protected:
std::vector<std::unique_ptr<CRepresentation>> m_representations;

Expand Down
146 changes: 146 additions & 0 deletions src/common/AdaptiveTree.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,8 @@ namespace adaptive

void AdaptiveTree::PostOpen(const UTILS::PROPERTIES::KodiProperties& kodiProps)
{
FixStreamsFlags(kodiProps);

// A manifest can provide live delay value, if not so we use our default
// value of 16 secs, this is needed to ensure an appropriate playback,
// an add-on can override the delay to try fix edge use cases
Expand Down Expand Up @@ -374,4 +376,148 @@ namespace adaptive
m_cvWait.notify_all();
}

void AdaptiveTree::FixStreamsFlags(const UTILS::PROPERTIES::KodiProperties& kodiProps)
{
// Add-ons can override subtitles "default" flag to streams
if (!kodiProps.m_subtitleLangDefault.empty())
{
for (auto& period : m_periods)
{
for (auto& adpSet : period->GetAdaptationSets())
{
if (adpSet->GetStreamType() == StreamType::SUBTITLE)
{
adpSet->SetIsDefault(
STRING::CompareNoCase(adpSet->GetLanguage(), kodiProps.m_subtitleLangDefault));
}
}
}
}

// Add-ons can override audio "original" flag to streams
if (!kodiProps.m_audioLangOriginal.empty())
{
for (auto& period : m_periods)
{
for (auto& adpSet : period->GetAdaptationSets())
{
if (adpSet->GetStreamType() == StreamType::AUDIO)
{
adpSet->SetIsOriginal(
STRING::CompareNoCase(adpSet->GetLanguage(), kodiProps.m_audioLangOriginal));
}
}
}
}

// Audio streams "default" flag customization / workaround

// Manifest of video services dont always set appropriately the default stream flag and also
// the manifest "default" stream flag dont have always the same meaning of Kodi track "default" flag,
// this can lead to wrong audio track selected when playback start.
// A good example is when "Media default" Kodi audio setting is set, where kodi expects just
// a single track with the default flag.
// Another problem is when video services provide multiple audio streams with same language code
// but differents channels, most of the times we can have 1 stereo and 1 multichannels
// stream with same language code, rarely there are multi-codecs with same channels,
// but we simplify by ignoring codec types.

// To allow Kodi VP to do a better track auto-selection we need:
// - Set default flag to a single track only
// - Set default flag to stereo or multichannels track, not both
// to do this its needed that an addon specify what to do because C++ interface dont provide
// access to kodi language settings where python can do it, then we cant automatize it.
const std::string langCodeDef = kodiProps.m_audioLangDefault;
const std::string langCodeOrig = kodiProps.m_audioLangOriginal;

if (!langCodeDef.empty() || !langCodeOrig.empty())
{
bool isDefaultStereo = kodiProps.m_audioPrefStereo; // add-on based setting

for (auto& period : m_periods)
{
auto& adpSets = period->GetAdaptationSets();
auto itAudioStream = adpSets.cend();

// Try give priority to "impaired" streams
if (kodiProps.m_audioPrefType == "impaired")
{
if (isDefaultStereo)
{
itAudioStream = CAdaptationSet::FindAudioAdpSet(adpSets, langCodeDef, true, true);
if (itAudioStream == adpSets.cend()) // No stereo stream, find multichannels
itAudioStream = CAdaptationSet::FindAudioAdpSet(adpSets, langCodeDef, false, true);
}
else
{
itAudioStream = CAdaptationSet::FindAudioAdpSet(adpSets, langCodeDef, false, true);
if (itAudioStream == adpSets.cend()) // No multichannels stream, find stereo
itAudioStream = CAdaptationSet::FindAudioAdpSet(adpSets, langCodeDef, true, true);
}

// No stream found, try find a "impaired" stream with the "original" language code
if (itAudioStream == adpSets.cend() && !langCodeOrig.empty())
{
if (isDefaultStereo)
{
itAudioStream = CAdaptationSet::FindAudioAdpSet(adpSets, langCodeOrig, true, true);
if (itAudioStream == adpSets.cend()) // No stereo stream, find multichannels
itAudioStream = CAdaptationSet::FindAudioAdpSet(adpSets, langCodeOrig, false, true);
}
else
{
itAudioStream = CAdaptationSet::FindAudioAdpSet(adpSets, langCodeOrig, false, true);
if (itAudioStream == adpSets.cend()) // No multichannels stream, find stereo
itAudioStream = CAdaptationSet::FindAudioAdpSet(adpSets, langCodeOrig, true, true);
}
}
}

// Try find a stream with specified lang code
if (kodiProps.m_audioPrefType != "original" && itAudioStream == adpSets.cend() &&
!langCodeDef.empty())
{
if (isDefaultStereo)
{
itAudioStream = CAdaptationSet::FindAudioAdpSet(adpSets, langCodeDef, true);
if (itAudioStream == adpSets.cend()) // No stereo stream, find multichannels
itAudioStream = CAdaptationSet::FindAudioAdpSet(adpSets, langCodeDef, false);
}
else
{
itAudioStream = CAdaptationSet::FindAudioAdpSet(adpSets, langCodeDef, false);
if (itAudioStream == adpSets.cend()) // No multichannels stream, find stereo
itAudioStream = CAdaptationSet::FindAudioAdpSet(adpSets, langCodeDef, true);
}
}

// No stream found, try find a stream with the "original" language code
if (itAudioStream == adpSets.cend() && !langCodeOrig.empty())
{
if (isDefaultStereo)
{
itAudioStream = CAdaptationSet::FindAudioAdpSet(adpSets, langCodeOrig, true);
if (itAudioStream == adpSets.cend()) // No stereo stream, find multichannels
itAudioStream = CAdaptationSet::FindAudioAdpSet(adpSets, langCodeOrig, false);
}
else
{
itAudioStream = CAdaptationSet::FindAudioAdpSet(adpSets, langCodeOrig, false);
if (itAudioStream == adpSets.cend()) // No multichannels stream, find stereo
itAudioStream = CAdaptationSet::FindAudioAdpSet(adpSets, langCodeOrig, true);
}
}

// Update "default" flags
if (itAudioStream != adpSets.cend())
{
for (auto& adpSet : adpSets)
{
adpSet->SetIsDefault(adpSet.get() == itAudioStream->get());
}
}
}
}
}

} // namespace adaptive
6 changes: 6 additions & 0 deletions src/common/AdaptiveTree.h
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,12 @@ class ATTR_DLL_LOCAL AdaptiveTree
const std::string& data,
std::string_view info);

/*!
* \brief Apply fixes and overrides to audio/subtitles stream flags.
* \param kodiProps The Kodi properties
*/
void FixStreamsFlags(const UTILS::PROPERTIES::KodiProperties& kodiProps);

void SortTree();

// Live segment update section
Expand Down
4 changes: 3 additions & 1 deletion src/parser/DASHTree.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -376,6 +376,8 @@ void adaptive::CDashTree::ParseTagAdaptationSet(pugi::xml_node nodeAdp, PLAYLIST
adpSet->SetIsForced(true);
else if (value == "main")
adpSet->SetIsDefault(true);
else if (value == "caption" || value == "alternate" || value == "commentary")
adpSet->SetIsImpaired(true);
}
}

Expand All @@ -388,7 +390,7 @@ void adaptive::CDashTree::ParseTagAdaptationSet(pugi::xml_node nodeAdp, PLAYLIST

if (schemeIdUri == "urn:mpeg:dash:role:2011")
{
if (value == "caption")
if (STRING::StartsWith(value, "caption")) // caption or captions
adpSet->SetIsImpaired(true);
}
}
Expand Down
28 changes: 25 additions & 3 deletions src/utils/PropertiesUtils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,9 @@ constexpr std::string_view PROP_MANIFEST_HEADERS = "inputstream.adaptive.manifes
constexpr std::string_view PROP_STREAM_PARAMS = "inputstream.adaptive.stream_params";
constexpr std::string_view PROP_STREAM_HEADERS = "inputstream.adaptive.stream_headers";

constexpr std::string_view PROP_AUDIO_LANG_ORIG = "inputstream.adaptive.original_audio_language";
constexpr std::string_view PROP_AUDIO_LANG_ORIG = "inputstream.adaptive.original_audio_language"; //! @todo: deprecated, to be removed on next Kodi release
constexpr std::string_view PROP_STREAMS_CONFIG = "inputstream.adaptive.streams_config";

constexpr std::string_view PROP_PLAY_TIMESHIFT_BUFFER = "inputstream.adaptive.play_timeshift_buffer";
constexpr std::string_view PROP_LIVE_DELAY = "inputstream.adaptive.live_delay";
constexpr std::string_view PROP_PRE_INIT_DATA = "inputstream.adaptive.pre_init_data";
Expand Down Expand Up @@ -121,9 +123,29 @@ KodiProperties UTILS::PROPERTIES::ParseKodiProperties(
{
ParseHeaderString(props.m_streamHeaders, prop.second);
}
else if (prop.first == PROP_AUDIO_LANG_ORIG)
else if (prop.first == PROP_AUDIO_LANG_ORIG) //! @todo: deprecated, to be removed on next Kodi release
{
LOG::Log(LOGWARNING,
"Warning \"inputstream.adaptive.original_audio_language\" property is deprecated "
"has been replaced by \"inputstream.adaptive.stream_audio_cfg\". "
"Please read Wiki \"Integration\" page to learn more about the new properties.");
props.m_audioLangOriginal = prop.second;
}
else if (prop.first == PROP_STREAMS_CONFIG)
{
props.m_audioLanguageOrig = prop.second;
auto values = STRING::ToMap(prop.second, '=', ';');

if (STRING::KeyExists(values, "audio_langcode_default"))
props.m_audioLangDefault = STRING::Trim(values["audio_langcode_default"]);
if (STRING::KeyExists(values, "audio_langcode_original"))
props.m_audioLangOriginal = STRING::Trim(values["audio_langcode_original"]);
if (STRING::KeyExists(values, "audio_prefer_stereo"))
props.m_audioPrefStereo = values["audio_prefer_stereo"] == "true";
if (STRING::KeyExists(values, "audio_prefer_type"))
props.m_audioPrefType = STRING::Trim(values["audio_prefer_type"]);

if (STRING::KeyExists(values, "subtitles_langcode_default"))
props.m_subtitleLangDefault = STRING::Trim(values["subtitles_langcode_default"]);
}
else if (prop.first == PROP_PLAY_TIMESHIFT_BUFFER)
{
Expand Down
16 changes: 15 additions & 1 deletion src/utils/PropertiesUtils.h
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,21 @@ struct KodiProperties
std::string m_streamParams;
// HTTP headers used to download streams
std::map<std::string, std::string> m_streamHeaders;
std::string m_audioLanguageOrig;

// Defines what type of audio tracks should be preferred for the "default" flag,
// accepted values are: original, impaired, or empty string.
// When empty: it try to set the flag to a regular language track or fallback to original language
std::string m_audioPrefType;
// Defines if stereo audio tracks are preferred over multichannels one,
// it depends from m_audioLangDefault
bool m_audioPrefStereo{false};
// Force audio streams with the specified language code to have the "default" flag
std::string m_audioLangDefault;
// Force audio streams with the specified language code to have the "original" flag
std::string m_audioLangOriginal;
// Force subtitle streams with the specified language code to have the "default" flag
std::string m_subtitleLangDefault;

bool m_playTimeshiftBuffer{false};
// Set a custom delay from live edge in seconds
uint64_t m_liveDelay{0};
Expand Down
54 changes: 54 additions & 0 deletions src/utils/StringUtils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
#include "kodi/tools/StringUtils.h"

#include <algorithm>
#include <cctype> // isspace
#include <charconv> // from_chars
#include <cstdio>
#include <cstring> // strstr
Expand Down Expand Up @@ -255,3 +256,56 @@ std::string UTILS::STRING::ToLower(std::string str)
StringUtils::ToLower(str);
return str;
}

std::map<std::string_view, std::string_view> UTILS::STRING::ToMap(std::string_view str,
const char delimiter,
const char separator)
{
std::map<std::string_view, std::string_view> mapped;

size_t keyPos = 0;
size_t keyEnd;
size_t valPos;
size_t valEnd;

while ((keyEnd = str.find(delimiter, keyPos)) != std::string::npos)
{
valPos = str.find_first_not_of(delimiter, keyEnd);
if (valPos == std::string::npos)
break;

valEnd = str.find(separator, valPos);
mapped.emplace(str.substr(keyPos, keyEnd - keyPos), str.substr(valPos, valEnd - valPos));

keyPos = valEnd;
if (keyPos != std::string::npos)
++keyPos;
}

return mapped;
}

std::string_view UTILS::STRING::Trim(std::string_view str)
{
auto left = str.begin();
while (left != str.end())
{
if (!std::isspace(*left))
break;

left++;
}

if (left == str.end())
return {};

auto right = str.end() - 1;
while (right > left && std::isspace(*right))
{
right--;
}

//! @todo: when we will switch to C++20 replace return code with:
//! return {left, std::distance(left, right) + 1};
return str.substr(left - str.begin(), std::distance(left, right) + 1);
}
Loading