1 /* 2 * Copyright (C) 2015 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.android.tv.util; 17 18 import android.content.Context; 19 import android.media.tv.TvTrackInfo; 20 import android.os.Build; 21 import android.os.LocaleList; 22 import android.text.TextUtils; 23 import android.util.Log; 24 25 import com.android.tv.R; 26 27 import com.google.common.collect.Iterables; 28 29 import java.util.ArrayList; 30 import java.util.Comparator; 31 import java.util.HashSet; 32 import java.util.List; 33 import java.util.Locale; 34 import java.util.Objects; 35 import java.util.Set; 36 37 /** Static utilities for {@link TvTrackInfo}. */ 38 public class TvTrackInfoUtils { 39 40 private static final String TAG = "TvTrackInfoUtils"; 41 private static final int AUDIO_CHANNEL_NONE = 0; 42 private static final int AUDIO_CHANNEL_MONO = 1; 43 private static final int AUDIO_CHANNEL_STEREO = 2; 44 private static final int AUDIO_CHANNEL_SURROUND_6 = 6; 45 private static final int AUDIO_CHANNEL_SURROUND_8 = 8; 46 47 /** 48 * Compares how closely two {@link android.media.tv.TvTrackInfo}s match {@code languages}, 49 * {@code channelCount} and {@code id} in that precedence. A listed sorted with this comparator 50 * has the worst matches first. 51 * 52 * @param id The track id to match. 53 * @param languages The prioritized list of languages. Languages earlier in the list are a 54 * better match. 55 * @param channelCount The channel count to match. 56 * @return -1 if lhs is a worse match, 0 if lhs and rhs match equally and 1 if lhs is a better 57 * match. 58 */ createComparator( final String id, final List<String> languages, final int channelCount)59 public static Comparator<TvTrackInfo> createComparator( 60 final String id, final List<String> languages, final int channelCount) { 61 return (TvTrackInfo lhs, TvTrackInfo rhs) -> { 62 if (Objects.equals(lhs, rhs)) { 63 return 0; 64 } 65 if (lhs == null) { 66 return -1; 67 } 68 if (rhs == null) { 69 return 1; 70 } 71 // Find the first language that matches the lhs and rhs tracks. The earlier match is 72 // better. If there is no match set the index to the size of the language list since 73 // its the worst match. 74 int lhsLangIndex = 75 Iterables.indexOf(languages, s -> Utils.isEqualLanguage(lhs.getLanguage(), s)); 76 if (lhsLangIndex == -1) { 77 lhsLangIndex = languages.size(); 78 } 79 int rhsLangIndex = 80 Iterables.indexOf(languages, s -> Utils.isEqualLanguage(rhs.getLanguage(), s)); 81 if (rhsLangIndex == -1) { 82 rhsLangIndex = languages.size(); 83 } 84 if (lhsLangIndex != rhsLangIndex) { 85 // Return the track with lower index as best 86 return Integer.compare(rhsLangIndex, lhsLangIndex); 87 } 88 boolean lhsCountMatch = 89 lhs.getType() != TvTrackInfo.TYPE_AUDIO 90 || lhs.getAudioChannelCount() == channelCount; 91 boolean rhsCountMatch = 92 rhs.getType() != TvTrackInfo.TYPE_AUDIO 93 || rhs.getAudioChannelCount() == channelCount; 94 if (lhsCountMatch && rhsCountMatch) { 95 return Boolean.compare(lhs.getId().equals(id), rhs.getId().equals(id)); 96 } else if (lhsCountMatch || rhsCountMatch) { 97 return Boolean.compare(lhsCountMatch, rhsCountMatch); 98 } else { 99 return Integer.compare(lhs.getAudioChannelCount(), rhs.getAudioChannelCount()); 100 } 101 }; 102 } 103 104 /** 105 * Selects the best TvTrackInfo available or the first if none matches. 106 * 107 * @param tracks The tracks to choose from 108 * @param id The track id to match. 109 * @param language The language to match. 110 * @param channelCount The channel count to match. 111 * @return the best matching track or the first one if none matches. 112 */ 113 public static TvTrackInfo getBestTrackInfo( 114 List<TvTrackInfo> tracks, String id, String language, int channelCount) { 115 if (tracks == null) { 116 return null; 117 } 118 List<String> languages = new ArrayList<>(); 119 if (language == null) { 120 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { 121 LocaleList locales = LocaleList.getDefault(); 122 for (int i = 0; i < locales.size(); i++) { 123 languages.add(locales.get(i).getLanguage()); 124 } 125 } else { 126 languages.add(Locale.getDefault().getLanguage()); 127 } 128 } else { 129 languages.add(language); 130 } 131 Comparator<TvTrackInfo> comparator = createComparator(id, languages, channelCount); 132 TvTrackInfo best = null; 133 for (TvTrackInfo track : tracks) { 134 if (comparator.compare(track, best) > 0) { 135 best = track; 136 } 137 } 138 return best; 139 } 140 141 public static boolean needToShowSampleRate(Context context, List<TvTrackInfo> tracks) { 142 Set<String> multiAudioStrings = new HashSet<>(); 143 for (TvTrackInfo track : tracks) { 144 String multiAudioString = getMultiAudioString(context, track, false); 145 if (multiAudioStrings.contains(multiAudioString)) { 146 return true; 147 } 148 multiAudioStrings.add(multiAudioString); 149 } 150 return false; 151 } 152 153 public static String getMultiAudioString( 154 Context context, TvTrackInfo track, boolean showSampleRate) { 155 if (track.getType() != TvTrackInfo.TYPE_AUDIO) { 156 throw new IllegalArgumentException("Not an audio track: " + toString(track)); 157 } 158 String language = context.getString(R.string.multi_audio_unknown_language); 159 if (!TextUtils.isEmpty(track.getLanguage())) { 160 language = new Locale(track.getLanguage()).getDisplayName(); 161 } else { 162 Log.d(TAG, "No language information found for the audio track: " + toString(track)); 163 } 164 165 StringBuilder metadata = new StringBuilder(); 166 switch (track.getAudioChannelCount()) { 167 case AUDIO_CHANNEL_NONE: 168 break; 169 case AUDIO_CHANNEL_MONO: 170 metadata.append(context.getString(R.string.multi_audio_channel_mono)); 171 break; 172 case AUDIO_CHANNEL_STEREO: 173 metadata.append(context.getString(R.string.multi_audio_channel_stereo)); 174 break; 175 case AUDIO_CHANNEL_SURROUND_6: 176 metadata.append(context.getString(R.string.multi_audio_channel_surround_6)); 177 break; 178 case AUDIO_CHANNEL_SURROUND_8: 179 metadata.append(context.getString(R.string.multi_audio_channel_surround_8)); 180 break; 181 default: 182 if (track.getAudioChannelCount() > 0) { 183 metadata.append( 184 context.getString( 185 R.string.multi_audio_channel_suffix, 186 track.getAudioChannelCount())); 187 } else { 188 Log.d( 189 TAG, 190 "Invalid audio channel count (" 191 + track.getAudioChannelCount() 192 + ") found for the audio track: " 193 + toString(track)); 194 } 195 break; 196 } 197 if (showSampleRate) { 198 int sampleRate = track.getAudioSampleRate(); 199 if (sampleRate > 0) { 200 if (metadata.length() > 0) { 201 metadata.append(", "); 202 } 203 int integerPart = sampleRate / 1000; 204 int tenths = (sampleRate % 1000) / 100; 205 metadata.append(integerPart); 206 if (tenths != 0) { 207 metadata.append("."); 208 metadata.append(tenths); 209 } 210 metadata.append("kHz"); 211 } 212 } 213 214 if (metadata.length() == 0) { 215 return language; 216 } 217 return context.getString( 218 R.string.multi_audio_display_string_with_channel, language, metadata.toString()); 219 } 220 221 private static String trackTypeToString(int trackType) { 222 switch (trackType) { 223 case TvTrackInfo.TYPE_AUDIO: 224 return "Audio"; 225 case TvTrackInfo.TYPE_VIDEO: 226 return "Video"; 227 case TvTrackInfo.TYPE_SUBTITLE: 228 return "Subtitle"; 229 default: 230 return "Invalid Type"; 231 } 232 } 233 234 public static String toString(TvTrackInfo info) { 235 int trackType = info.getType(); 236 return "TvTrackInfo{" 237 + "type=" 238 + trackTypeToString(trackType) 239 + ", id=" 240 + info.getId() 241 + ", language=" 242 + info.getLanguage() 243 + ", description=" 244 + info.getDescription() 245 + (trackType == TvTrackInfo.TYPE_AUDIO 246 ? (", audioChannelCount=" 247 + info.getAudioChannelCount() 248 + ", audioSampleRate=" 249 + info.getAudioSampleRate()) 250 : "") 251 + (trackType == TvTrackInfo.TYPE_VIDEO 252 ? (", videoWidth=" 253 + info.getVideoWidth() 254 + ", videoHeight=" 255 + info.getVideoHeight() 256 + ", videoFrameRate=" 257 + info.getVideoFrameRate() 258 + ", videoPixelAspectRatio=" 259 + info.getVideoPixelAspectRatio()) 260 : "") 261 + "}"; 262 } 263 264 private TvTrackInfoUtils() {} 265 } 266