blob: a52ac2003a20f317d3bc9a68b6f51352687820e7 [file] [log] [blame]
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "third_party/blink/renderer/core/html/track/automatic_track_selection.h"
#include "third_party/blink/renderer/core/html/track/text_track.h"
#include "third_party/blink/renderer/core/html/track/text_track_list.h"
#include "third_party/blink/renderer/platform/language.h"
namespace blink {
class TrackGroup {
STACK_ALLOCATED();
public:
enum GroupKind { kCaptionsAndSubtitles, kDescription, kChapter, kMetadata };
explicit TrackGroup(GroupKind kind)
: visible_track(nullptr),
default_track(nullptr),
kind(kind),
has_src_lang(false) {}
HeapVector<Member<TextTrack>> tracks;
Member<TextTrack> visible_track;
Member<TextTrack> default_track;
GroupKind kind;
bool has_src_lang;
};
static int TextTrackLanguageSelectionScore(const TextTrack& track) {
if (track.language().IsEmpty())
return 0;
Vector<AtomicString> languages = UserPreferredLanguages();
wtf_size_t language_match_index =
IndexOfBestMatchingLanguageInList(track.language(), languages);
if (language_match_index >= languages.size())
return 0;
return languages.size() - language_match_index;
}
static int TextTrackSelectionScore(const TextTrack& track) {
if (!track.IsVisualKind())
return 0;
return TextTrackLanguageSelectionScore(track);
}
AutomaticTrackSelection::AutomaticTrackSelection(
const Configuration& configuration)
: configuration_(configuration) {}
const AtomicString& AutomaticTrackSelection::PreferredTrackKind() const {
if (configuration_.text_track_kind_user_preference ==
TextTrackKindUserPreference::kSubtitles)
return TextTrack::SubtitlesKeyword();
if (configuration_.text_track_kind_user_preference ==
TextTrackKindUserPreference::kCaptions)
return TextTrack::CaptionsKeyword();
return g_null_atom;
}
void AutomaticTrackSelection::PerformAutomaticTextTrackSelection(
const TrackGroup& group) {
DCHECK(group.tracks.size());
// First, find the track in the group that should be enabled (if any).
HeapVector<Member<TextTrack>> currently_enabled_tracks;
TextTrack* track_to_enable = nullptr;
TextTrack* default_track = nullptr;
TextTrack* preferred_track = nullptr;
TextTrack* fallback_track = nullptr;
int highest_track_score = 0;
for (const auto& text_track : group.tracks) {
if (configuration_.disable_currently_enabled_tracks &&
text_track->mode() == TextTrack::ShowingKeyword())
currently_enabled_tracks.push_back(text_track);
int track_score = TextTrackSelectionScore(*text_track);
if (text_track->kind() == PreferredTrackKind())
track_score += 1;
if (track_score) {
// * If the text track kind is subtitles or captions and the user has
// indicated an interest in having a track with this text track kind, text
// track language, and text track label enabled, and there is no other
// text track in the media element's list of text tracks with a text track
// kind of either subtitles or captions whose text track mode is showing
// Let the text track mode be showing.
if (track_score > highest_track_score) {
preferred_track = text_track;
highest_track_score = track_score;
}
if (!default_track && text_track->IsDefault())
default_track = text_track;
if (!fallback_track)
fallback_track = text_track;
} else if (!group.visible_track && !default_track &&
text_track->IsDefault()) {
// * If the track element has a default attribute specified, and there is
// no other text track in the media element's list of text tracks whose
// text track mode is showing or showing by default
// Let the text track mode be showing by default.
default_track = text_track;
}
}
if (configuration_.text_track_kind_user_preference !=
TextTrackKindUserPreference::kDefault)
track_to_enable = preferred_track;
if (!track_to_enable && default_track)
track_to_enable = default_track;
if (!track_to_enable &&
configuration_.force_enable_subtitle_or_caption_track &&
group.kind == TrackGroup::kCaptionsAndSubtitles) {
if (fallback_track)
track_to_enable = fallback_track;
else
track_to_enable = group.tracks[0];
}
if (currently_enabled_tracks.size()) {
for (const auto& text_track : currently_enabled_tracks) {
if (text_track != track_to_enable)
text_track->setMode(TextTrack::DisabledKeyword());
}
}
if (track_to_enable)
track_to_enable->setMode(TextTrack::ShowingKeyword());
}
void AutomaticTrackSelection::EnableDefaultMetadataTextTracks(
const TrackGroup& group) {
DCHECK(group.tracks.size());
// https://html.spec.whatwg.org/multipage/embedded-content.html#honor-user-preferences-for-automatic-text-track-selection
// 4. If there are any text tracks in the media element's list of text
// tracks whose text track kind is metadata that correspond to track
// elements with a default attribute set whose text track mode is set to
// disabled, then set the text track mode of all such tracks to hidden
for (auto& text_track : group.tracks) {
if (text_track->mode() != TextTrack::DisabledKeyword())
continue;
if (!text_track->IsDefault())
continue;
text_track->setMode(TextTrack::HiddenKeyword());
}
}
void AutomaticTrackSelection::Perform(TextTrackList& text_tracks) {
TrackGroup caption_and_subtitle_tracks(TrackGroup::kCaptionsAndSubtitles);
TrackGroup description_tracks(TrackGroup::kDescription);
TrackGroup chapter_tracks(TrackGroup::kChapter);
TrackGroup metadata_tracks(TrackGroup::kMetadata);
for (wtf_size_t i = 0; i < text_tracks.length(); ++i) {
TextTrack* text_track = text_tracks.AnonymousIndexedGetter(i);
if (!text_track)
continue;
String kind = text_track->kind();
TrackGroup* current_group;
if (kind == TextTrack::SubtitlesKeyword() ||
kind == TextTrack::CaptionsKeyword()) {
current_group = &caption_and_subtitle_tracks;
} else if (kind == TextTrack::DescriptionsKeyword()) {
current_group = &description_tracks;
} else if (kind == TextTrack::ChaptersKeyword()) {
current_group = &chapter_tracks;
} else {
DCHECK_EQ(kind, TextTrack::MetadataKeyword());
current_group = &metadata_tracks;
}
if (!current_group->visible_track &&
text_track->mode() == TextTrack::ShowingKeyword())
current_group->visible_track = text_track;
if (!current_group->default_track && text_track->IsDefault())
current_group->default_track = text_track;
// Do not add this track to the group if it has already been automatically
// configured as we only want to perform selection once per track so that
// adding another track after the initial configuration doesn't reconfigure
// every track - only those that should be changed by the new addition. For
// example all metadata tracks are disabled by default, and we don't want a
// track that has been enabled by script to be disabled automatically when a
// new metadata track is added later.
if (text_track->HasBeenConfigured())
continue;
if (text_track->language().length())
current_group->has_src_lang = true;
current_group->tracks.push_back(text_track);
}
if (caption_and_subtitle_tracks.tracks.size())
PerformAutomaticTextTrackSelection(caption_and_subtitle_tracks);
if (description_tracks.tracks.size())
PerformAutomaticTextTrackSelection(description_tracks);
if (chapter_tracks.tracks.size())
PerformAutomaticTextTrackSelection(chapter_tracks);
if (metadata_tracks.tracks.size())
EnableDefaultMetadataTextTracks(metadata_tracks);
}
} // namespace blink