/* * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.tv.util; import android.content.Context; import android.media.tv.TvTrackInfo; import android.os.Build; import android.os.LocaleList; import android.text.TextUtils; import android.util.Log; import com.android.tv.R; import com.google.common.collect.Iterables; import java.util.ArrayList; import java.util.Comparator; import java.util.HashSet; import java.util.List; import java.util.Locale; import java.util.Objects; import java.util.Set; /** Static utilities for {@link TvTrackInfo}. */ public class TvTrackInfoUtils { private static final String TAG = "TvTrackInfoUtils"; private static final int AUDIO_CHANNEL_NONE = 0; private static final int AUDIO_CHANNEL_MONO = 1; private static final int AUDIO_CHANNEL_STEREO = 2; private static final int AUDIO_CHANNEL_SURROUND_6 = 6; private static final int AUDIO_CHANNEL_SURROUND_8 = 8; /** * Compares how closely two {@link android.media.tv.TvTrackInfo}s match {@code languages}, * {@code channelCount} and {@code id} in that precedence. A listed sorted with this comparator * has the worst matches first. * * @param id The track id to match. * @param languages The prioritized list of languages. Languages earlier in the list are a * better match. * @param channelCount The channel count to match. * @return -1 if lhs is a worse match, 0 if lhs and rhs match equally and 1 if lhs is a better * match. */ public static Comparator createComparator( final String id, final List languages, final int channelCount) { return (TvTrackInfo lhs, TvTrackInfo rhs) -> { if (Objects.equals(lhs, rhs)) { return 0; } if (lhs == null) { return -1; } if (rhs == null) { return 1; } // Find the first language that matches the lhs and rhs tracks. The earlier match is // better. If there is no match set the index to the size of the language list since // its the worst match. int lhsLangIndex = Iterables.indexOf(languages, s -> Utils.isEqualLanguage(lhs.getLanguage(), s)); if (lhsLangIndex == -1) { lhsLangIndex = languages.size(); } int rhsLangIndex = Iterables.indexOf(languages, s -> Utils.isEqualLanguage(rhs.getLanguage(), s)); if (rhsLangIndex == -1) { rhsLangIndex = languages.size(); } if (lhsLangIndex != rhsLangIndex) { // Return the track with lower index as best return Integer.compare(rhsLangIndex, lhsLangIndex); } boolean lhsCountMatch = lhs.getType() != TvTrackInfo.TYPE_AUDIO || lhs.getAudioChannelCount() == channelCount; boolean rhsCountMatch = rhs.getType() != TvTrackInfo.TYPE_AUDIO || rhs.getAudioChannelCount() == channelCount; if (lhsCountMatch && rhsCountMatch) { return Boolean.compare(lhs.getId().equals(id), rhs.getId().equals(id)); } else if (lhsCountMatch || rhsCountMatch) { return Boolean.compare(lhsCountMatch, rhsCountMatch); } else { return Integer.compare(lhs.getAudioChannelCount(), rhs.getAudioChannelCount()); } }; } /** * Selects the best TvTrackInfo available or the first if none matches. * * @param tracks The tracks to choose from * @param id The track id to match. * @param language The language to match. * @param channelCount The channel count to match. * @return the best matching track or the first one if none matches. */ public static TvTrackInfo getBestTrackInfo( List tracks, String id, String language, int channelCount) { if (tracks == null) { return null; } List languages = new ArrayList<>(); if (language == null) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { LocaleList locales = LocaleList.getDefault(); for (int i = 0; i < locales.size(); i++) { languages.add(locales.get(i).getLanguage()); } } else { languages.add(Locale.getDefault().getLanguage()); } } else { languages.add(language); } Comparator comparator = createComparator(id, languages, channelCount); TvTrackInfo best = null; for (TvTrackInfo track : tracks) { if (comparator.compare(track, best) > 0) { best = track; } } return best; } public static boolean needToShowSampleRate(Context context, List tracks) { Set multiAudioStrings = new HashSet<>(); for (TvTrackInfo track : tracks) { String multiAudioString = getMultiAudioString(context, track, false); if (multiAudioStrings.contains(multiAudioString)) { return true; } multiAudioStrings.add(multiAudioString); } return false; } public static String getMultiAudioString( Context context, TvTrackInfo track, boolean showSampleRate) { if (track.getType() != TvTrackInfo.TYPE_AUDIO) { throw new IllegalArgumentException("Not an audio track: " + toString(track)); } String language = context.getString(R.string.multi_audio_unknown_language); if (!TextUtils.isEmpty(track.getLanguage())) { language = new Locale(track.getLanguage()).getDisplayName(); } else { Log.d(TAG, "No language information found for the audio track: " + toString(track)); } StringBuilder metadata = new StringBuilder(); switch (track.getAudioChannelCount()) { case AUDIO_CHANNEL_NONE: break; case AUDIO_CHANNEL_MONO: metadata.append(context.getString(R.string.multi_audio_channel_mono)); break; case AUDIO_CHANNEL_STEREO: metadata.append(context.getString(R.string.multi_audio_channel_stereo)); break; case AUDIO_CHANNEL_SURROUND_6: metadata.append(context.getString(R.string.multi_audio_channel_surround_6)); break; case AUDIO_CHANNEL_SURROUND_8: metadata.append(context.getString(R.string.multi_audio_channel_surround_8)); break; default: if (track.getAudioChannelCount() > 0) { metadata.append( context.getString( R.string.multi_audio_channel_suffix, track.getAudioChannelCount())); } else { Log.d( TAG, "Invalid audio channel count (" + track.getAudioChannelCount() + ") found for the audio track: " + toString(track)); } break; } if (showSampleRate) { int sampleRate = track.getAudioSampleRate(); if (sampleRate > 0) { if (metadata.length() > 0) { metadata.append(", "); } int integerPart = sampleRate / 1000; int tenths = (sampleRate % 1000) / 100; metadata.append(integerPart); if (tenths != 0) { metadata.append("."); metadata.append(tenths); } metadata.append("kHz"); } } if (metadata.length() == 0) { return language; } return context.getString( R.string.multi_audio_display_string_with_channel, language, metadata.toString()); } private static String trackTypeToString(int trackType) { switch (trackType) { case TvTrackInfo.TYPE_AUDIO: return "Audio"; case TvTrackInfo.TYPE_VIDEO: return "Video"; case TvTrackInfo.TYPE_SUBTITLE: return "Subtitle"; default: return "Invalid Type"; } } public static String toString(TvTrackInfo info) { int trackType = info.getType(); return "TvTrackInfo{" + "type=" + trackTypeToString(trackType) + ", id=" + info.getId() + ", language=" + info.getLanguage() + ", description=" + info.getDescription() + (trackType == TvTrackInfo.TYPE_AUDIO ? (", audioChannelCount=" + info.getAudioChannelCount() + ", audioSampleRate=" + info.getAudioSampleRate()) : "") + (trackType == TvTrackInfo.TYPE_VIDEO ? (", videoWidth=" + info.getVideoWidth() + ", videoHeight=" + info.getVideoHeight() + ", videoFrameRate=" + info.getVideoFrameRate() + ", videoPixelAspectRatio=" + info.getVideoPixelAspectRatio()) : "") + "}"; } private TvTrackInfoUtils() {} }