blob: ae30df11a9db08c6bdae5b51743b536e7ce4025e [file] [log] [blame]
/*
* 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<TvTrackInfo> createComparator(
final String id, final List<String> 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<TvTrackInfo> tracks, String id, String language, int channelCount) {
if (tracks == null) {
return null;
}
List<String> 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<TvTrackInfo> 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<TvTrackInfo> tracks) {
Set<String> 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() {}
}