Merge pull request #7184 from TiVo:p-subtitle-format-from-codecs
PiperOrigin-RevId: 305137114
This commit is contained in:
commit
2b44ff3c71
@ -88,6 +88,8 @@
|
|||||||
`OfflineLicenseHelper`
|
`OfflineLicenseHelper`
|
||||||
([#7078](https://github.com/google/ExoPlayer/issues/7078)).
|
([#7078](https://github.com/google/ExoPlayer/issues/7078)).
|
||||||
* Remove generics from DRM components.
|
* 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
|
* Downloads: Merge downloads in `SegmentDownloader` to improve overall
|
||||||
download speed ([#5978](https://github.com/google/ExoPlayer/issues/5978)).
|
download speed ([#5978](https://github.com/google/ExoPlayer/issues/5978)).
|
||||||
* MP3: Add `IndexSeeker` for accurate seeks in VBR streams
|
* MP3: Add `IndexSeeker` for accurate seeks in VBR streams
|
||||||
|
@ -123,22 +123,22 @@ public final class MimeTypes {
|
|||||||
customMimeTypes.add(customMimeType);
|
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) {
|
public static boolean isAudio(@Nullable String mimeType) {
|
||||||
return BASE_TYPE_AUDIO.equals(getTopLevelType(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) {
|
public static boolean isVideo(@Nullable String mimeType) {
|
||||||
return BASE_TYPE_VIDEO.equals(getTopLevelType(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) {
|
public static boolean isText(@Nullable String mimeType) {
|
||||||
return BASE_TYPE_TEXT.equals(getTopLevelType(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) {
|
public static boolean isApplication(@Nullable String mimeType) {
|
||||||
return BASE_TYPE_APPLICATION.equals(getTopLevelType(mimeType));
|
return BASE_TYPE_APPLICATION.equals(getTopLevelType(mimeType));
|
||||||
}
|
}
|
||||||
@ -174,13 +174,14 @@ public final class MimeTypes {
|
|||||||
* @param codecs The codecs attribute.
|
* @param codecs The codecs attribute.
|
||||||
* @return The derived video mimeType, or null if it could not be derived.
|
* @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) {
|
if (codecs == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
String[] codecList = Util.splitCodecs(codecs);
|
String[] codecList = Util.splitCodecs(codecs);
|
||||||
for (String codec : codecList) {
|
for (String codec : codecList) {
|
||||||
String mimeType = getMediaMimeType(codec);
|
@Nullable String mimeType = getMediaMimeType(codec);
|
||||||
if (mimeType != null && isVideo(mimeType)) {
|
if (mimeType != null && isVideo(mimeType)) {
|
||||||
return mimeType;
|
return mimeType;
|
||||||
}
|
}
|
||||||
@ -194,13 +195,14 @@ public final class MimeTypes {
|
|||||||
* @param codecs The codecs attribute.
|
* @param codecs The codecs attribute.
|
||||||
* @return The derived audio mimeType, or null if it could not be derived.
|
* @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) {
|
if (codecs == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
String[] codecList = Util.splitCodecs(codecs);
|
String[] codecList = Util.splitCodecs(codecs);
|
||||||
for (String codec : codecList) {
|
for (String codec : codecList) {
|
||||||
String mimeType = getMediaMimeType(codec);
|
@Nullable String mimeType = getMediaMimeType(codec);
|
||||||
if (mimeType != null && isAudio(mimeType)) {
|
if (mimeType != null && isAudio(mimeType)) {
|
||||||
return mimeType;
|
return mimeType;
|
||||||
}
|
}
|
||||||
@ -214,7 +216,8 @@ public final class MimeTypes {
|
|||||||
* @param codec The codec identifier to derive.
|
* @param codec The codec identifier to derive.
|
||||||
* @return The mimeType, or null if it could not be derived.
|
* @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) {
|
if (codec == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -235,7 +238,7 @@ public final class MimeTypes {
|
|||||||
} else if (codec.startsWith("vp8") || codec.startsWith("vp08")) {
|
} else if (codec.startsWith("vp8") || codec.startsWith("vp08")) {
|
||||||
return MimeTypes.VIDEO_VP8;
|
return MimeTypes.VIDEO_VP8;
|
||||||
} else if (codec.startsWith("mp4a")) {
|
} else if (codec.startsWith("mp4a")) {
|
||||||
String mimeType = null;
|
@Nullable String mimeType = null;
|
||||||
if (codec.startsWith("mp4a.")) {
|
if (codec.startsWith("mp4a.")) {
|
||||||
String objectTypeString = codec.substring(5); // remove the 'mp4a.' prefix
|
String objectTypeString = codec.substring(5); // remove the 'mp4a.' prefix
|
||||||
if (objectTypeString.length() >= 2) {
|
if (objectTypeString.length() >= 2) {
|
||||||
@ -244,7 +247,7 @@ public final class MimeTypes {
|
|||||||
int objectTypeInt = Integer.parseInt(objectTypeHexString, 16);
|
int objectTypeInt = Integer.parseInt(objectTypeHexString, 16);
|
||||||
mimeType = getMimeTypeFromMp4ObjectType(objectTypeInt);
|
mimeType = getMimeTypeFromMp4ObjectType(objectTypeInt);
|
||||||
} catch (NumberFormatException ignored) {
|
} catch (NumberFormatException ignored) {
|
||||||
// ignored
|
// Ignored.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -267,6 +270,10 @@ public final class MimeTypes {
|
|||||||
return MimeTypes.AUDIO_VORBIS;
|
return MimeTypes.AUDIO_VORBIS;
|
||||||
} else if (codec.startsWith("flac")) {
|
} else if (codec.startsWith("flac")) {
|
||||||
return MimeTypes.AUDIO_FLAC;
|
return MimeTypes.AUDIO_FLAC;
|
||||||
|
} else if (codec.startsWith("stpp")) {
|
||||||
|
return MimeTypes.APPLICATION_TTML;
|
||||||
|
} else if (codec.startsWith("wvtt")) {
|
||||||
|
return MimeTypes.TEXT_VTT;
|
||||||
} else {
|
} else {
|
||||||
return getCustomMimeTypeForCodec(codec);
|
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
|
* Returns the top-level type of {@code mimeType}, or null if {@code mimeType} is null or does not
|
||||||
* contain a forward slash character ({@code '/'}).
|
* 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) {
|
if (mimeType == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -417,7 +425,8 @@ public final class MimeTypes {
|
|||||||
return mimeType.substring(0, indexOfSlash);
|
return mimeType.substring(0, indexOfSlash);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static @Nullable String getCustomMimeTypeForCodec(String codec) {
|
@Nullable
|
||||||
|
private static String getCustomMimeTypeForCodec(String codec) {
|
||||||
int customMimeTypeCount = customMimeTypes.size();
|
int customMimeTypeCount = customMimeTypes.size();
|
||||||
for (int i = 0; i < customMimeTypeCount; i++) {
|
for (int i = 0; i < customMimeTypeCount; i++) {
|
||||||
CustomMimeType customMimeType = customMimeTypes.get(i);
|
CustomMimeType customMimeType = customMimeTypes.get(i);
|
||||||
|
@ -73,6 +73,10 @@ public final class MimeTypesTest {
|
|||||||
assertThat(MimeTypes.getMediaMimeType("mp4a.AA")).isEqualTo(MimeTypes.AUDIO_DTS_HD);
|
assertThat(MimeTypes.getMediaMimeType("mp4a.AA")).isEqualTo(MimeTypes.AUDIO_DTS_HD);
|
||||||
assertThat(MimeTypes.getMediaMimeType("mp4a.AB")).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("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
|
@Test
|
||||||
|
@ -458,7 +458,18 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case TYPE_SUBTITLES:
|
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));
|
subtitles.add(new Rendition(uri, formatBuilder.build(), groupId, name));
|
||||||
break;
|
break;
|
||||||
case TYPE_CLOSED_CAPTIONS:
|
case TYPE_CLOSED_CAPTIONS:
|
||||||
@ -527,6 +538,17 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
|
|||||||
return null;
|
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(
|
private static HlsMediaPlaylist parseMediaPlaylist(
|
||||||
HlsMasterPlaylist masterPlaylist, LineIterator iterator, String baseUri) throws IOException {
|
HlsMasterPlaylist masterPlaylist, LineIterator iterator, String baseUri) throws IOException {
|
||||||
@HlsMediaPlaylist.PlaylistType int playlistType = HlsMediaPlaylist.PLAYLIST_TYPE_UNKNOWN;
|
@HlsMediaPlaylist.PlaylistType int playlistType = HlsMediaPlaylist.PLAYLIST_TYPE_UNKNOWN;
|
||||||
|
@ -194,6 +194,19 @@ public class HlsMasterPlaylistParserTest {
|
|||||||
+ "#EXT-X-MEDIA:TYPE=SUBTITLES,"
|
+ "#EXT-X-MEDIA:TYPE=SUBTITLES,"
|
||||||
+ "GROUP-ID=\"sub1\",NAME=\"English\",URI=\"s1/en/prog_index.m3u8\"\n";
|
+ "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
|
@Test
|
||||||
public void parseMasterPlaylist_withSimple_success() throws IOException {
|
public void parseMasterPlaylist_withSimple_success() throws IOException {
|
||||||
HlsMasterPlaylist masterPlaylist = parseMasterPlaylist(PLAYLIST_URI, PLAYLIST_SIMPLE);
|
HlsMasterPlaylist masterPlaylist = parseMasterPlaylist(PLAYLIST_URI, PLAYLIST_SIMPLE);
|
||||||
@ -321,6 +334,7 @@ public class HlsMasterPlaylistParserTest {
|
|||||||
|
|
||||||
Format firstTextFormat = playlist.subtitles.get(0).format;
|
Format firstTextFormat = playlist.subtitles.get(0).format;
|
||||||
assertThat(firstTextFormat.id).isEqualTo("sub1:Eng");
|
assertThat(firstTextFormat.id).isEqualTo("sub1:Eng");
|
||||||
|
assertThat(firstTextFormat.sampleMimeType).isEqualTo(MimeTypes.TEXT_VTT);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -345,6 +359,18 @@ public class HlsMasterPlaylistParserTest {
|
|||||||
.isEqualTo(Uri.parse("http://example.com/This/{$nested}/reference/shouldnt/work"));
|
.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
|
@Test
|
||||||
public void parseMasterPlaylist_withMatchingStreamInfUrls_success() throws IOException {
|
public void parseMasterPlaylist_withMatchingStreamInfUrls_success() throws IOException {
|
||||||
HlsMasterPlaylist playlist =
|
HlsMasterPlaylist playlist =
|
||||||
|
Loading…
x
Reference in New Issue
Block a user