Merge pull request #8150 from karyogamy/caption-fix

Fix caption auto-selection not reflected in player GUI
This commit is contained in:
Robin 2022-04-14 10:10:53 +02:00 committed by GitHub
commit 3fb5073feb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 74 additions and 53 deletions

View File

@ -62,7 +62,6 @@ import static org.schabi.newpipe.player.helper.PlayerHelper.savePlaybackParamete
import static org.schabi.newpipe.util.ListHelper.getPopupResolutionIndex; import static org.schabi.newpipe.util.ListHelper.getPopupResolutionIndex;
import static org.schabi.newpipe.util.ListHelper.getResolutionIndex; import static org.schabi.newpipe.util.ListHelper.getResolutionIndex;
import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage; import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage;
import static org.schabi.newpipe.util.Localization.containsCaseInsensitive;
import static java.util.concurrent.TimeUnit.MILLISECONDS; import static java.util.concurrent.TimeUnit.MILLISECONDS;
import android.animation.Animator; import android.animation.Animator;
@ -130,6 +129,7 @@ import androidx.recyclerview.widget.RecyclerView;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.DefaultRenderersFactory; import com.google.android.exoplayer2.DefaultRenderersFactory;
import com.google.android.exoplayer2.ExoPlayer; import com.google.android.exoplayer2.ExoPlayer;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.PlaybackException; import com.google.android.exoplayer2.PlaybackException;
import com.google.android.exoplayer2.PlaybackParameters; import com.google.android.exoplayer2.PlaybackParameters;
import com.google.android.exoplayer2.Player.PositionInfo; import com.google.android.exoplayer2.Player.PositionInfo;
@ -137,8 +137,6 @@ import com.google.android.exoplayer2.RenderersFactory;
import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.TracksInfo; import com.google.android.exoplayer2.TracksInfo;
import com.google.android.exoplayer2.source.MediaSource; import com.google.android.exoplayer2.source.MediaSource;
import com.google.android.exoplayer2.source.TrackGroup;
import com.google.android.exoplayer2.source.TrackGroupArray;
import com.google.android.exoplayer2.text.Cue; import com.google.android.exoplayer2.text.Cue;
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector; import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
import com.google.android.exoplayer2.trackselection.MappingTrackSelector; import com.google.android.exoplayer2.trackselection.MappingTrackSelector;
@ -212,7 +210,6 @@ import org.schabi.newpipe.util.external_communication.ShareUtils;
import org.schabi.newpipe.views.ExpandableSurfaceView; import org.schabi.newpipe.views.ExpandableSurfaceView;
import org.schabi.newpipe.views.player.PlayerFastSeekOverlay; import org.schabi.newpipe.views.player.PlayerFastSeekOverlay;
import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
@ -2530,7 +2527,7 @@ public final class Player implements
Log.d(TAG, "ExoPlayer - onTracksChanged(), " Log.d(TAG, "ExoPlayer - onTracksChanged(), "
+ "track group size = " + tracksInfo.getTrackGroupInfos().size()); + "track group size = " + tracksInfo.getTrackGroupInfos().size());
} }
onTextTracksChanged(); onTextTracksChanged(tracksInfo);
} }
@Override @Override
@ -3516,17 +3513,7 @@ public final class Player implements
return; return;
} }
captionPopupMenu.getMenu().removeGroup(POPUP_MENU_ID_CAPTION); captionPopupMenu.getMenu().removeGroup(POPUP_MENU_ID_CAPTION);
captionPopupMenu.setOnDismissListener(this);
final String userPreferredLanguage =
prefs.getString(context.getString(R.string.caption_user_set_key), null);
/*
* only search for autogenerated cc as fallback
* if "(auto-generated)" was not already selected
* we are only looking for "(" instead of "(auto-generated)" to hopefully get all
* internationalized variants such as "(automatisch-erzeugt)" and so on
*/
boolean searchForAutogenerated = userPreferredLanguage != null
&& !userPreferredLanguage.contains("(");
// Add option for turning off caption // Add option for turning off caption
final MenuItem captionOffItem = captionPopupMenu.getMenu().add(POPUP_MENU_ID_CAPTION, final MenuItem captionOffItem = captionPopupMenu.getMenu().add(POPUP_MENU_ID_CAPTION,
@ -3549,30 +3536,42 @@ public final class Player implements
captionItem.setOnMenuItemClickListener(menuItem -> { captionItem.setOnMenuItemClickListener(menuItem -> {
final int textRendererIndex = getCaptionRendererIndex(); final int textRendererIndex = getCaptionRendererIndex();
if (textRendererIndex != RENDERER_UNAVAILABLE) { if (textRendererIndex != RENDERER_UNAVAILABLE) {
// DefaultTrackSelector will select for text tracks in the following order.
// When multiple tracks share the same rank, a random track will be chosen.
// 1. ANY track exactly matching preferred language name
// 2. ANY track exactly matching preferred language stem
// 3. ROLE_FLAG_CAPTION track matching preferred language stem
// 4. ROLE_FLAG_DESCRIBES_MUSIC_AND_SOUND track matching preferred language stem
// This means if a caption track of preferred language is not available,
// then an auto-generated track of that language will be chosen automatically.
trackSelector.setParameters(trackSelector.buildUponParameters() trackSelector.setParameters(trackSelector.buildUponParameters()
.setPreferredTextLanguage(captionLanguage) .setPreferredTextLanguages(captionLanguage,
PlayerHelper.captionLanguageStemOf(captionLanguage))
.setPreferredTextRoleFlags(C.ROLE_FLAG_CAPTION)
.setRendererDisabled(textRendererIndex, false)); .setRendererDisabled(textRendererIndex, false));
prefs.edit().putString(context.getString(R.string.caption_user_set_key), prefs.edit().putString(context.getString(R.string.caption_user_set_key),
captionLanguage).apply(); captionLanguage).apply();
} }
return true; return true;
}); });
// apply caption language from previous user preference
if (userPreferredLanguage != null
&& (captionLanguage.equals(userPreferredLanguage)
|| (searchForAutogenerated && captionLanguage.startsWith(userPreferredLanguage))
|| (userPreferredLanguage.contains("(") && captionLanguage.startsWith(
userPreferredLanguage.substring(0, userPreferredLanguage.indexOf('(')))))) {
final int textRendererIndex = getCaptionRendererIndex();
if (textRendererIndex != RENDERER_UNAVAILABLE) {
trackSelector.setParameters(trackSelector.buildUponParameters()
.setPreferredTextLanguage(captionLanguage)
.setRendererDisabled(textRendererIndex, false));
}
searchForAutogenerated = false;
}
} }
captionPopupMenu.setOnDismissListener(this);
// apply caption language from previous user preference
final List<String> selectedPreferredLanguages =
trackSelector.getParameters().preferredTextLanguages;
final String userPreferredLanguage =
prefs.getString(context.getString(R.string.caption_user_set_key), null);
final int textRendererIndex = getCaptionRendererIndex();
if (userPreferredLanguage != null
&& !selectedPreferredLanguages.contains(userPreferredLanguage)
&& textRendererIndex != RENDERER_UNAVAILABLE) {
trackSelector.setParameters(trackSelector.buildUponParameters()
.setPreferredTextLanguages(userPreferredLanguage,
PlayerHelper.captionLanguageStemOf(userPreferredLanguage))
.setPreferredTextRoleFlags(C.ROLE_FLAG_CAPTION)
.setRendererDisabled(textRendererIndex, false));
}
} }
/** /**
@ -3668,41 +3667,43 @@ public final class Player implements
binding.subtitleView.setStyle(captionStyle); binding.subtitleView.setStyle(captionStyle);
} }
private void onTextTracksChanged() { private void onTextTracksChanged(@NonNull final TracksInfo currentTrackInfo) {
final int textRenderer = getCaptionRendererIndex();
if (binding == null) { if (binding == null) {
return; return;
} }
if (trackSelector.getCurrentMappedTrackInfo() == null if (trackSelector.getCurrentMappedTrackInfo() == null
|| textRenderer == RENDERER_UNAVAILABLE) { || !currentTrackInfo.isTypeSupportedOrEmpty(C.TRACK_TYPE_TEXT)) {
binding.captionTextView.setVisibility(View.GONE); binding.captionTextView.setVisibility(View.GONE);
return; return;
} }
final TrackGroupArray textTracks = trackSelector.getCurrentMappedTrackInfo()
.getTrackGroups(textRenderer);
// Extract all loaded languages // Extract all loaded languages
final List<String> availableLanguages = new ArrayList<>(textTracks.length); final List<TracksInfo.TrackGroupInfo> textTracks = currentTrackInfo
for (int i = 0; i < textTracks.length; i++) { .getTrackGroupInfos()
final TrackGroup textTrack = textTracks.get(i); .stream()
if (textTrack.length > 0) { .filter(trackGroupInfo -> C.TRACK_TYPE_TEXT == trackGroupInfo.getTrackType())
availableLanguages.add(textTrack.getFormat(0).language); .collect(Collectors.toList());
} final List<String> availableLanguages = textTracks.stream()
} .map(TracksInfo.TrackGroupInfo::getTrackGroup)
.filter(textTrack -> textTrack.length > 0)
.map(textTrack -> textTrack.getFormat(0).language)
.collect(Collectors.toList());
// Find selected text track
final Optional<Format> selectedTracks = textTracks.stream()
.filter(TracksInfo.TrackGroupInfo::isSelected)
.filter(info -> info.getTrackGroup().length >= 1)
.map(info -> info.getTrackGroup().getFormat(0))
.findFirst();
// Normalize mismatching language strings
final String preferredLanguage = trackSelector.getParameters()
.preferredTextLanguages.stream().findFirst().orElse(null);
// Build UI // Build UI
buildCaptionMenu(availableLanguages); buildCaptionMenu(availableLanguages);
if (trackSelector.getParameters().getRendererDisabled(textRenderer) if (trackSelector.getParameters().getRendererDisabled(getCaptionRendererIndex())
|| preferredLanguage == null || (!availableLanguages.contains(preferredLanguage) || !selectedTracks.isPresent()) {
&& !containsCaseInsensitive(availableLanguages, preferredLanguage))) {
binding.captionTextView.setText(R.string.caption_none); binding.captionTextView.setText(R.string.caption_none);
} else { } else {
binding.captionTextView.setText(preferredLanguage); binding.captionTextView.setText(selectedTracks.get().language);
} }
binding.captionTextView.setVisibility( binding.captionTextView.setVisibility(
availableLanguages.isEmpty() ? View.GONE : View.VISIBLE); availableLanguages.isEmpty() ? View.GONE : View.VISIBLE);

View File

@ -144,6 +144,21 @@ public final class PlayerHelper {
? " (" + context.getString(R.string.caption_auto_generated) + ")" : ""); ? " (" + context.getString(R.string.caption_auto_generated) + ")" : "");
} }
@NonNull
public static String captionLanguageStemOf(@NonNull final String language) {
if (!language.contains("(") || !language.contains(")")) {
return language;
}
if (language.startsWith("(")) {
// language text is right-to-left
final String[] parts = language.split("\\)");
return parts[parts.length - 1].trim();
}
return language.split("\\(")[0].trim();
}
@NonNull @NonNull
public static String resizeTypeOf(@NonNull final Context context, public static String resizeTypeOf(@NonNull final Context context,
@ResizeMode final int resizeMode) { @ResizeMode final int resizeMode) {

View File

@ -6,6 +6,7 @@ import android.net.Uri;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.MediaItem; import com.google.android.exoplayer2.MediaItem;
import com.google.android.exoplayer2.source.MediaSource; import com.google.android.exoplayer2.source.MediaSource;
import com.google.android.exoplayer2.source.MergingMediaSource; import com.google.android.exoplayer2.source.MergingMediaSource;
@ -116,9 +117,13 @@ public class VideoPlaybackResolver implements PlaybackResolver {
if (mimeType == null) { if (mimeType == null) {
continue; continue;
} }
final @C.RoleFlags int textRoleFlag = subtitle.isAutoGenerated()
? C.ROLE_FLAG_DESCRIBES_MUSIC_AND_SOUND
: C.ROLE_FLAG_CAPTION;
final MediaItem.SubtitleConfiguration textMediaItem = final MediaItem.SubtitleConfiguration textMediaItem =
new MediaItem.SubtitleConfiguration.Builder(Uri.parse(subtitle.getUrl())) new MediaItem.SubtitleConfiguration.Builder(Uri.parse(subtitle.getUrl()))
.setMimeType(mimeType) .setMimeType(mimeType)
.setRoleFlags(textRoleFlag)
.setLanguage(PlayerHelper.captionLanguageOf(context, subtitle)) .setLanguage(PlayerHelper.captionLanguageOf(context, subtitle))
.build(); .build();
final MediaSource textSource = dataSource final MediaSource textSource = dataSource