Merge pull request #7184 from TiVo:p-subtitle-format-from-codecs

PiperOrigin-RevId: 305137114
This commit is contained in:
Oliver Woodman 2020-04-07 13:38:53 +01:00
commit 2b44ff3c71
5 changed files with 77 additions and 14 deletions

View File

@ -88,6 +88,8 @@
`OfflineLicenseHelper`
([#7078](https://github.com/google/ExoPlayer/issues/7078)).
* Remove generics from DRM components.
* HLS: Recognize IMSC subtitles
([#7185](https://github.com/google/ExoPlayer/issues/7185)).
* Downloads: Merge downloads in `SegmentDownloader` to improve overall
download speed ([#5978](https://github.com/google/ExoPlayer/issues/5978)).
* MP3: Add `IndexSeeker` for accurate seeks in VBR streams

View File

@ -123,22 +123,22 @@ public final class MimeTypes {
customMimeTypes.add(customMimeType);
}
/** Returns whether the given string is an audio mime type. */
/** Returns whether the given string is an audio MIME type. */
public static boolean isAudio(@Nullable String mimeType) {
return BASE_TYPE_AUDIO.equals(getTopLevelType(mimeType));
}
/** Returns whether the given string is a video mime type. */
/** Returns whether the given string is a video MIME type. */
public static boolean isVideo(@Nullable String mimeType) {
return BASE_TYPE_VIDEO.equals(getTopLevelType(mimeType));
}
/** Returns whether the given string is a text mime type. */
/** Returns whether the given string is a text MIME type. */
public static boolean isText(@Nullable String mimeType) {
return BASE_TYPE_TEXT.equals(getTopLevelType(mimeType));
}
/** Returns whether the given string is an application mime type. */
/** Returns whether the given string is an application MIME type. */
public static boolean isApplication(@Nullable String mimeType) {
return BASE_TYPE_APPLICATION.equals(getTopLevelType(mimeType));
}
@ -174,13 +174,14 @@ public final class MimeTypes {
* @param codecs The codecs attribute.
* @return The derived video mimeType, or null if it could not be derived.
*/
public static @Nullable String getVideoMediaMimeType(@Nullable String codecs) {
@Nullable
public static String getVideoMediaMimeType(@Nullable String codecs) {
if (codecs == null) {
return null;
}
String[] codecList = Util.splitCodecs(codecs);
for (String codec : codecList) {
String mimeType = getMediaMimeType(codec);
@Nullable String mimeType = getMediaMimeType(codec);
if (mimeType != null && isVideo(mimeType)) {
return mimeType;
}
@ -194,13 +195,14 @@ public final class MimeTypes {
* @param codecs The codecs attribute.
* @return The derived audio mimeType, or null if it could not be derived.
*/
public static @Nullable String getAudioMediaMimeType(@Nullable String codecs) {
@Nullable
public static String getAudioMediaMimeType(@Nullable String codecs) {
if (codecs == null) {
return null;
}
String[] codecList = Util.splitCodecs(codecs);
for (String codec : codecList) {
String mimeType = getMediaMimeType(codec);
@Nullable String mimeType = getMediaMimeType(codec);
if (mimeType != null && isAudio(mimeType)) {
return mimeType;
}
@ -214,7 +216,8 @@ public final class MimeTypes {
* @param codec The codec identifier to derive.
* @return The mimeType, or null if it could not be derived.
*/
public static @Nullable String getMediaMimeType(@Nullable String codec) {
@Nullable
public static String getMediaMimeType(@Nullable String codec) {
if (codec == null) {
return null;
}
@ -235,7 +238,7 @@ public final class MimeTypes {
} else if (codec.startsWith("vp8") || codec.startsWith("vp08")) {
return MimeTypes.VIDEO_VP8;
} else if (codec.startsWith("mp4a")) {
String mimeType = null;
@Nullable String mimeType = null;
if (codec.startsWith("mp4a.")) {
String objectTypeString = codec.substring(5); // remove the 'mp4a.' prefix
if (objectTypeString.length() >= 2) {
@ -244,7 +247,7 @@ public final class MimeTypes {
int objectTypeInt = Integer.parseInt(objectTypeHexString, 16);
mimeType = getMimeTypeFromMp4ObjectType(objectTypeInt);
} catch (NumberFormatException ignored) {
// ignored
// Ignored.
}
}
}
@ -267,6 +270,10 @@ public final class MimeTypes {
return MimeTypes.AUDIO_VORBIS;
} else if (codec.startsWith("flac")) {
return MimeTypes.AUDIO_FLAC;
} else if (codec.startsWith("stpp")) {
return MimeTypes.APPLICATION_TTML;
} else if (codec.startsWith("wvtt")) {
return MimeTypes.TEXT_VTT;
} else {
return getCustomMimeTypeForCodec(codec);
}
@ -406,7 +413,8 @@ public final class MimeTypes {
* Returns the top-level type of {@code mimeType}, or null if {@code mimeType} is null or does not
* contain a forward slash character ({@code '/'}).
*/
private static @Nullable String getTopLevelType(@Nullable String mimeType) {
@Nullable
private static String getTopLevelType(@Nullable String mimeType) {
if (mimeType == null) {
return null;
}
@ -417,7 +425,8 @@ public final class MimeTypes {
return mimeType.substring(0, indexOfSlash);
}
private static @Nullable String getCustomMimeTypeForCodec(String codec) {
@Nullable
private static String getCustomMimeTypeForCodec(String codec) {
int customMimeTypeCount = customMimeTypes.size();
for (int i = 0; i < customMimeTypeCount; i++) {
CustomMimeType customMimeType = customMimeTypes.get(i);

View File

@ -73,6 +73,10 @@ public final class MimeTypesTest {
assertThat(MimeTypes.getMediaMimeType("mp4a.AA")).isEqualTo(MimeTypes.AUDIO_DTS_HD);
assertThat(MimeTypes.getMediaMimeType("mp4a.AB")).isEqualTo(MimeTypes.AUDIO_DTS_HD);
assertThat(MimeTypes.getMediaMimeType("mp4a.AD")).isEqualTo(MimeTypes.AUDIO_OPUS);
assertThat(MimeTypes.getMediaMimeType("wvtt")).isEqualTo(MimeTypes.TEXT_VTT);
assertThat(MimeTypes.getMediaMimeType("stpp.")).isEqualTo(MimeTypes.APPLICATION_TTML);
assertThat(MimeTypes.getMediaMimeType("stpp.ttml.im1t")).isEqualTo(MimeTypes.APPLICATION_TTML);
}
@Test

View File

@ -458,7 +458,18 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
}
break;
case TYPE_SUBTITLES:
formatBuilder.setSampleMimeType(MimeTypes.TEXT_VTT).setMetadata(metadata);
sampleMimeType = null;
variant = getVariantWithSubtitleGroup(variants, groupId);
if (variant != null) {
@Nullable
String codecs = Util.getCodecsOfType(variant.format.codecs, C.TRACK_TYPE_TEXT);
formatBuilder.setCodecs(codecs);
sampleMimeType = MimeTypes.getMediaMimeType(codecs);
}
if (sampleMimeType == null) {
sampleMimeType = MimeTypes.TEXT_VTT;
}
formatBuilder.setSampleMimeType(sampleMimeType).setMetadata(metadata);
subtitles.add(new Rendition(uri, formatBuilder.build(), groupId, name));
break;
case TYPE_CLOSED_CAPTIONS:
@ -527,6 +538,17 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
return null;
}
@Nullable
private static Variant getVariantWithSubtitleGroup(ArrayList<Variant> variants, String groupId) {
for (int i = 0; i < variants.size(); i++) {
Variant variant = variants.get(i);
if (groupId.equals(variant.subtitleGroupId)) {
return variant;
}
}
return null;
}
private static HlsMediaPlaylist parseMediaPlaylist(
HlsMasterPlaylist masterPlaylist, LineIterator iterator, String baseUri) throws IOException {
@HlsMediaPlaylist.PlaylistType int playlistType = HlsMediaPlaylist.PLAYLIST_TYPE_UNKNOWN;

View File

@ -194,6 +194,19 @@ public class HlsMasterPlaylistParserTest {
+ "#EXT-X-MEDIA:TYPE=SUBTITLES,"
+ "GROUP-ID=\"sub1\",NAME=\"English\",URI=\"s1/en/prog_index.m3u8\"\n";
private static final String PLAYLIST_WITH_TTML_SUBTITLE =
" #EXTM3U\n"
+ "\n"
+ "#EXT-X-VERSION:6\n"
+ "\n"
+ "#EXT-X-INDEPENDENT-SEGMENTS\n"
+ "\n"
+ "#EXT-X-STREAM-INF:BANDWIDTH=1280000,CODECS=\"stpp.ttml.im1t,mp4a.40.2,avc1.66.30\",RESOLUTION=304x128,AUDIO=\"aud1\",SUBTITLES=\"sub1\"\n"
+ "http://example.com/low.m3u8\n"
+ "\n"
+ "#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID=\"aud1\",NAME=\"English\",URI=\"a1/index.m3u8\"\n"
+ "#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID=\"sub1\",NAME=\"English\",AUTOSELECT=YES,DEFAULT=YES,URI=\"s1/en/prog_index.m3u8\"\n";
@Test
public void parseMasterPlaylist_withSimple_success() throws IOException {
HlsMasterPlaylist masterPlaylist = parseMasterPlaylist(PLAYLIST_URI, PLAYLIST_SIMPLE);
@ -321,6 +334,7 @@ public class HlsMasterPlaylistParserTest {
Format firstTextFormat = playlist.subtitles.get(0).format;
assertThat(firstTextFormat.id).isEqualTo("sub1:Eng");
assertThat(firstTextFormat.sampleMimeType).isEqualTo(MimeTypes.TEXT_VTT);
}
@Test
@ -345,6 +359,18 @@ public class HlsMasterPlaylistParserTest {
.isEqualTo(Uri.parse("http://example.com/This/{$nested}/reference/shouldnt/work"));
}
@Test
public void parseMasterPlaylist_withTtmlSubtitle() throws IOException {
HlsMasterPlaylist playlistWithTtmlSubtitle =
parseMasterPlaylist(PLAYLIST_URI, PLAYLIST_WITH_TTML_SUBTITLE);
HlsMasterPlaylist.Variant variant = playlistWithTtmlSubtitle.variants.get(0);
Format firstTextFormat = playlistWithTtmlSubtitle.subtitles.get(0).format;
assertThat(firstTextFormat.id).isEqualTo("sub1:English");
assertThat(firstTextFormat.containerMimeType).isEqualTo(MimeTypes.APPLICATION_M3U8);
assertThat(firstTextFormat.sampleMimeType).isEqualTo(MimeTypes.APPLICATION_TTML);
assertThat(variant.format.codecs).isEqualTo("stpp.ttml.im1t,mp4a.40.2,avc1.66.30");
}
@Test
public void parseMasterPlaylist_withMatchingStreamInfUrls_success() throws IOException {
HlsMasterPlaylist playlist =