diff --git a/AampDefine.h b/AampDefine.h index 7f1537ef3..ae79cbd52 100644 --- a/AampDefine.h +++ b/AampDefine.h @@ -213,10 +213,10 @@ // weights used for audio/subtitle track-selection heuristic #define AAMP_LANGUAGE_SCORE 1000000000ULL /**< Top priority: matching language **/ #define AAMP_SCHEME_ID_SCORE 100000000ULL /**< 2nd priority to scheme id matching **/ -#define AAMP_LABEL_SCORE 10000000ULL /**< 3rd priority to label matching **/ -#define AAMP_ROLE_SCORE 1000000ULL /**< 4th priority to role/rendition matching **/ -#define AAMP_TYPE_SCORE 100000ULL /**< 5th priority to type matching **/ -#define AAMP_CODEC_SCORE 1000ULL /**< Lowest priority: matching codec **/ +#define AAMP_LABEL_SCORE 10000000ULL /**< 3rd priority to label matching **/ +#define AAMP_ROLE_SCORE 1000000ULL /**< 4th priority to role/rendition matching **/ +#define AAMP_TYPE_SCORE 100000ULL /**< 5th priority to type matching **/ +#define AAMP_CODEC_SCORE 1000ULL /**< Lowest priority: matching codec **/ #define THRESHOLD_TOIGNORE_TINYPERIOD 500 /**SetPreferredTextTrack(std::move(selectedTextTrack)); + } + else + { + AAMPLOG_WARN("No text track matched user preferences, will use default selection"); + } + + // Configure Subtitle track for the playback + ConfigureTextTrack(); + + // Check for text track changes and notify + NotifyTextTrackChanges(); + if(ISCONFIGSET(eAAMPConfig_useRialtoSink) && (currentTextTrackProfileIndex == -1)) { AAMPLOG_INFO("usingRialtoSink - No default text track is selected,configure default text track for rialto"); @@ -3451,8 +3478,6 @@ AAMPStatusType StreamAbstractionAAMP_HLS::Init(TuneType tuneType) } } - - currentProfileIndex = GetDesiredProfile(false); HlsStreamInfo *streamInfo = (HlsStreamInfo*)GetStreamInfo(currentProfileIndex); long bandwidthBitsPerSecond = streamInfo->bandwidthBitsPerSecond; @@ -7291,21 +7316,12 @@ void StreamAbstractionAAMP_HLS::PopulateAudioAndTextTracks() { aamp->NotifyAudioTracksChanged(); } - - tracksChanged = false; - if (-1 != aamp->mCurrentTextTrackIndex && aamp->mCurrentTextTrackIndex != currentTextTrackProfileIndex) - { - tracksChanged = true; - } - aamp->mCurrentTextTrackIndex = currentTextTrackProfileIndex; - if (tracksChanged) - { - aamp->NotifyTextTracksChanged(); - } + + // Update closed caption track info std::vector textTracksCopy; std::copy_if(begin(mTextTracks), end(mTextTracks), back_inserter(textTracksCopy), [](const TextTrackInfo& e){return e.isCC;}); std::vector updatedTextTracks; - aamp->UpdateCCTrackInfo(textTracksCopy,updatedTextTracks); + aamp->UpdateCCTrackInfo(textTracksCopy, updatedTextTracks); PlayerCCManager::GetInstance()->updateLastTextTracks(updatedTextTracks); } else @@ -7316,6 +7332,25 @@ void StreamAbstractionAAMP_HLS::PopulateAudioAndTextTracks() } +/** + * @brief Check for text track changes and send notification events + */ +void StreamAbstractionAAMP_HLS::NotifyTextTrackChanges() +{ + // Check if text track has changed from a valid previous selection + bool tracksChanged = (aamp->mCurrentTextTrackIndex != -1 && + aamp->mCurrentTextTrackIndex != currentTextTrackProfileIndex); + + // Update current track index + aamp->mCurrentTextTrackIndex = currentTextTrackProfileIndex; + + // Send notification if track changed + if (tracksChanged) + { + aamp->NotifyTextTracksChanged(); + } +} + /** * @brief Function to update seek position */ @@ -7543,41 +7578,102 @@ void StreamAbstractionAAMP_HLS::SelectSubtitleTrack() AAMPLOG_INFO("using RialtoSink TextTrack Selected %d", currentTextTrackProfileIndex); } -bool StreamAbstractionAAMP_HLS::SelectPreferredTextTrack(TextTrackInfo& selectedTextTrack) +/** + * @brief Select best text track based on user preferences + * + * Scoring algorithm: + * - Base: 1 point for any track + * - Language: (list_size - position) * AAMP_LANGUAGE_SCORE (prioritizes order) + * - Rendition: AAMP_ROLE_SCORE + * - Name: AAMP_TYPE_SCORE + * + * @param[out] selectedTextTrack The best matching track + * @return true if a track was selected, false otherwise + */ +bool StreamAbstractionAAMP_HLS::SelectPreferredTextTrack(TextTrackInfo &selectedTextTrack) { - bool bestTrackFound = false; - unsigned long long bestScore = 0; - std::vector availableTracks = GetAvailableTextTracks(); + if (availableTracks.empty()) + { + AAMPLOG_WARN("No text tracks available"); + return false; + } + + AAMPLOG_INFO("Preferences: language='%s' rendition='%s' name='%s' sub-type='%s'", + aamp->preferredTextLanguagesString.c_str(), aamp->preferredTextRenditionString.c_str(), + aamp->preferredTextNameString.c_str(), aamp->preferredTextSubTypeString.c_str()); + + unsigned long long bestScore = 0; + const auto& languageVectorToCheck = (aamp->preferredTextLanguagesList.empty()) ? aamp->preferredSubtitleLanguageVctr : aamp->preferredTextLanguagesList; - for (const auto& track : availableTracks) + for (const auto &track : availableTracks) { - unsigned long long score = 1; // Default score for each track + unsigned long long score = 1; // Base score for any track - // Check for language match - if (!aamp->preferredTextLanguagesString.empty() && track.language == aamp->preferredTextLanguagesString) + // Score language preference (higher priority = higher score) + if (!languageVectorToCheck.empty()) { - score += AAMP_LANGUAGE_SCORE; // Add score for language match + std::string normalizedTrackLanguage = + track.language.empty() ? "" : Getiso639map_NormalizeLanguageCode(track.language, aamp->GetLangCodePreference()); + AAMPLOG_TRACE("Track '%s' lang='%s' (normalized='%s')", track.name.c_str(), track.language.c_str(), normalizedTrackLanguage.c_str()); + + auto iter = std::find(languageVectorToCheck.cbegin(), + languageVectorToCheck.cend(), + normalizedTrackLanguage); + if (iter != languageVectorToCheck.cend()) + { + size_t position = std::distance(languageVectorToCheck.cbegin(), iter); + size_t priorityMultiplier = languageVectorToCheck.size() - position; + score += priorityMultiplier * AAMP_LANGUAGE_SCORE; + + AAMPLOG_TRACE("Track '%s' lang='%s' (normalized='%s') matches position %zu (bonus: %llu)", + track.name.c_str(), track.language.c_str(), normalizedTrackLanguage.c_str(), + position, priorityMultiplier * AAMP_LANGUAGE_SCORE); + } } - if( !aamp->preferredTextRenditionString.empty() && aamp->preferredTextRenditionString.compare(track.rendition) == 0) + // Score rendition preference + if (!aamp->preferredTextRenditionString.empty() && + aamp->preferredTextRenditionString == track.rendition) { - score += AAMP_ROLE_SCORE; // Add score for rendition match + score += AAMP_ROLE_SCORE; } - // Check for name match - if( !aamp->preferredTextNameString.empty() && aamp->preferredTextNameString.compare(track.name) == 0) + // Score name preference + if (!aamp->preferredTextNameString.empty() && + aamp->preferredTextNameString == track.name) { - score += AAMP_TYPE_SCORE; // Add score for name match + score += AAMP_TYPE_SCORE; } - if(score > bestScore) + + // Only if it IS specified AND matches, add score for sub-type match. + // If a preference is not set (or is not recognised) it doesn't match. + if ( (!track.isCC && aamp->preferredTextSubTypeString == "SUBTITLES") + || ( track.isCC && aamp->preferredTextSubTypeString == "CLOSED-CAPTIONS") ) + { + // This is intentional re-use of the AAMP_TYPE_SCORE weighting, as + // specifying name AND sub-type in the same preference profile is not supported. + score += AAMP_TYPE_SCORE; + } + + // Update best if this score is higher + if (score > bestScore) { - bestTrackFound = true; bestScore = score; selectedTextTrack = track; + + AAMPLOG_INFO("New best text track: lang=%s, rendition=%s, name=%s, CC=%s score=%llu", + track.language.c_str(), track.rendition.c_str(), + track.name.c_str(), track.isCC ? "True" : "False", score); } } - return bestTrackFound; + + if (bestScore == 0) + { + AAMPLOG_WARN("No suitable text track found"); + } + + return (bestScore > 0); } /* diff --git a/fragmentcollector_hls.h b/fragmentcollector_hls.h index ebf904495..387f74d96 100644 --- a/fragmentcollector_hls.h +++ b/fragmentcollector_hls.h @@ -1029,6 +1029,15 @@ class StreamAbstractionAAMP_HLS : public StreamAbstractionAAMP * @return void ***************************************************************************/ void PopulateAudioAndTextTracks(); + + /*************************************************************************** + * @fn NotifyTextTrackChanges + * @brief Check for text track changes and send notification events + * + * @return void + ***************************************************************************/ + void NotifyTextTrackChanges(); + /*************************************************************************** * @fn ConfigureAudioTrack * diff --git a/priv_aamp.cpp b/priv_aamp.cpp index a4f1f9411..dc4e5a539 100644 --- a/priv_aamp.cpp +++ b/priv_aamp.cpp @@ -12124,7 +12124,14 @@ void PrivateInstanceAAMP::SanitizeLanguageList(std::vector& languag std::transform( languages.begin(), languages.end(), languages.begin(), [this](std::string& lang) - { return Getiso639map_NormalizeLanguageCode(lang, this->GetLangCodePreference()); } ); + { + // Skip normalization for empty strings + if (lang.empty()) + { + return lang; + } + return Getiso639map_NormalizeLanguageCode(lang, this->GetLangCodePreference()); + } ); // To keep track of the languages that have already been encountered. std::unordered_set seen; @@ -12243,6 +12250,15 @@ void PrivateInstanceAAMP::SavePreferredTextLanguages(const char *param, bool &is } } + std::string inputTextSubTypeString; + if (jsObject->isString("sub-type")) + { + if (jsObject->get("sub-type", inputTextSubTypeString)) + { + AAMPLOG_INFO("Preferred sub-type string: %s", inputTextSubTypeString.c_str()); + } + } + Accessibility inputTextAccessibilityNode; /** Get accessibility Properties*/ if (jsObject->isObject("accessibility")) @@ -12272,6 +12288,7 @@ void PrivateInstanceAAMP::SavePreferredTextLanguages(const char *param, bool &is preferredTextTypeString = std::move(inputTextTypeString); preferredInstreamIdString = std::move(inputInstreamIdString); preferredTextNameString = std::move(inputTextNameString); + preferredTextSubTypeString = std::move(inputTextSubTypeString); SETCONFIGVALUE_PRIV(AAMP_APPLICATION_SETTING,eAAMPConfig_PreferredTextRendition,preferredTextRenditionString); SETCONFIGVALUE_PRIV(AAMP_APPLICATION_SETTING,eAAMPConfig_PreferredTextLabel,preferredTextLabelString); @@ -12314,6 +12331,26 @@ void PrivateInstanceAAMP::SavePreferredTextLanguages(const char *param, bool &is SETCONFIGVALUE_PRIV(AAMP_APPLICATION_SETTING,eAAMPConfig_PreferredTextLanguage,preferredTextLanguagesString); } +/** + * @brief Find the index of a text track in a vector + * @param[in] tracks Vector of text tracks to search + * @param[in] target Track to find + * @return Index of track (0-based), or -1 if not found + */ +int PrivateInstanceAAMP::FindTextTrackIndex(const std::vector& tracks, + const TextTrackInfo& target) const +{ + int index = -1; + auto iter = std::find(tracks.cbegin(), tracks.cend(), target); + + if (iter != tracks.cend()) + { + index = static_cast(std::distance(tracks.cbegin(), iter)); + } + + return index; +} + /** * @brief Find closed caption track index if any */ @@ -12504,8 +12541,17 @@ void PrivateInstanceAAMP::SetPreferredTextLanguages(const char *param) TextTrackInfo selectedTextTrack; if (mpStreamAbstractionAAMP->SelectPreferredTextTrack(selectedTextTrack)) { + // Find the index of the selected track in the available tracks list + closedCaptionTrackId = FindTextTrackIndex(trackInfo, selectedTextTrack); + + AAMPLOG_INFO("Selected text track at index %d (lang=%s)", + closedCaptionTrackId, selectedTextTrack.language.c_str()); SetPreferredTextTrack(std::move(selectedTextTrack)); } + else + { + AAMPLOG_WARN("SelectPreferredTextTrack failed to find a matching track"); + } } seek_pos_seconds = GetPositionSeconds(); diff --git a/priv_aamp.h b/priv_aamp.h index 20cb79731..963c6e1cc 100644 --- a/priv_aamp.h +++ b/priv_aamp.h @@ -600,6 +600,15 @@ class PrivateInstanceAAMP : public DrmCallbacks, public std::enable_shared_from_ */ void CheckPreferredTextLanguages(const std::vector &trackInfo,bool &isInManifest, bool &isPresent, int &closedCaptionTrackIdx); + /** + * @brief Find the index of a text track in a vector + * @param[in] tracks - Vector of text tracks to search + * @param[in] target - Track to find + * @return Index of track (0-based), or -1 if not found + */ + int FindTextTrackIndex(const std::vector& tracks, + const TextTrackInfo& target) const; + public: /* @fn RecalculatePTS * @param[in] mediaType stream type @@ -1047,7 +1056,8 @@ class PrivateInstanceAAMP : public DrmCallbacks, public std::enable_shared_from_ std::vector preferredTextLanguagesList; /**< list of preferred text languages from most-preferred to the least*/ std::string preferredTextRenditionString; /**< String value for rendition */ std::string preferredTextTypeString; /**< String value for text type */ - std::string preferredTextLabelString; /**< String value for text type */ + std::string preferredTextLabelString; /**< String value for label */ + std::string preferredTextSubTypeString; /**< String value for sub-type */ std::string preferredInstreamIdString; /**< String value for instreamId */ std::vector vDynamicDrmData; Accessibility preferredTextAccessibilityNode; /**< Preferred Accessibility Node for Text */