diff --git a/library/src/androidTest/java/com/google/android/exoplayer/MediaFormatTest.java b/library/src/androidTest/java/com/google/android/exoplayer/MediaFormatTest.java index 62e4ce044f..4c82d2fe6d 100644 --- a/library/src/androidTest/java/com/google/android/exoplayer/MediaFormatTest.java +++ b/library/src/androidTest/java/com/google/android/exoplayer/MediaFormatTest.java @@ -17,17 +17,20 @@ package com.google.android.exoplayer; import com.google.android.exoplayer.util.Util; +import android.annotation.SuppressLint; import android.annotation.TargetApi; import junit.framework.TestCase; +import java.nio.ByteBuffer; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; /** * Unit test for {@link MediaFormat}. */ -public class MediaFormatTest extends TestCase { +public final class MediaFormatTest extends TestCase { public void testConversionToFrameworkFormat() { if (Util.SDK_INT < 16) { @@ -41,20 +44,64 @@ public class MediaFormatTest extends TestCase { initData.add(initData1); initData.add(initData2); + testConversionToFrameworkFormatV16(MediaFormat.createVideoFormat( + "video/xyz", 102400, 1000L, 1280, 720, 1, initData)); + testConversionToFrameworkFormatV16(MediaFormat.createVideoFormat( + "video/xyz", MediaFormat.NO_VALUE, C.UNKNOWN_TIME_US, 1280, 720, 1, null)); + testConversionToFrameworkFormatV16(MediaFormat.createAudioFormat( + "audio/xyz", 128, 1000L, 5, 44100, initData)); + testConversionToFrameworkFormatV16(MediaFormat.createAudioFormat( + "audio/xyz", MediaFormat.NO_VALUE, C.UNKNOWN_TIME_US, 5, 44100, null)); testConversionToFrameworkFormatV16( - MediaFormat.createVideoFormat("video/xyz", 102400, 1000L, 1280, 720, 1.5f, initData)); + MediaFormat.createTextFormat("text/xyz", "eng", 1000L)); testConversionToFrameworkFormatV16( - MediaFormat.createAudioFormat("audio/xyz", 102400, 1000L, 5, 44100, initData)); + MediaFormat.createTextFormat("text/xyz", null, C.UNKNOWN_TIME_US)); + } + + @SuppressLint("InlinedApi") + @TargetApi(16) + private static void testConversionToFrameworkFormatV16(MediaFormat in) { + android.media.MediaFormat out = in.getFrameworkMediaFormatV16(); + assertEquals(in.mimeType, out.getString(android.media.MediaFormat.KEY_MIME)); + assertOptionalV16(out, android.media.MediaFormat.KEY_LANGUAGE, in.language); + assertOptionalV16(out, android.media.MediaFormat.KEY_MAX_INPUT_SIZE, in.maxInputSize); + assertOptionalV16(out, android.media.MediaFormat.KEY_WIDTH, in.width); + assertOptionalV16(out, android.media.MediaFormat.KEY_HEIGHT, in.height); + assertOptionalV16(out, android.media.MediaFormat.KEY_CHANNEL_COUNT, in.channelCount); + assertOptionalV16(out, android.media.MediaFormat.KEY_SAMPLE_RATE, in.sampleRate); + assertOptionalV16(out, android.media.MediaFormat.KEY_MAX_WIDTH, in.getMaxVideoWidth()); + assertOptionalV16(out, android.media.MediaFormat.KEY_MAX_HEIGHT, in.getMaxVideoHeight()); + for (int i = 0; i < in.initializationData.size(); i++) { + byte[] originalData = in.initializationData.get(i); + ByteBuffer frameworkBuffer = out.getByteBuffer("csd-" + i); + byte[] frameworkData = Arrays.copyOf(frameworkBuffer.array(), frameworkBuffer.limit()); + assertTrue(Arrays.equals(originalData, frameworkData)); + } + if (in.durationUs == C.UNKNOWN_TIME_US) { + assertFalse(out.containsKey(android.media.MediaFormat.KEY_DURATION)); + } else { + assertEquals(in.durationUs, out.getLong(android.media.MediaFormat.KEY_DURATION)); + } } @TargetApi(16) - private void testConversionToFrameworkFormatV16(MediaFormat format) { - // Convert to a framework MediaFormat and back again. - MediaFormat convertedFormat = MediaFormat.createFromFrameworkMediaFormatV16( - format.getFrameworkMediaFormatV16()); - // Assert that we end up with an equivalent object to the one we started with. - assertEquals(format.hashCode(), convertedFormat.hashCode()); - assertEquals(format, convertedFormat); + private static void assertOptionalV16(android.media.MediaFormat format, String key, + String value) { + if (value == null) { + assertFalse(format.containsKey(key)); + } else { + assertEquals(value, format.getString(key)); + } + } + + @TargetApi(16) + private static void assertOptionalV16(android.media.MediaFormat format, String key, + int value) { + if (value == MediaFormat.NO_VALUE) { + assertFalse(format.containsKey(key)); + } else { + assertEquals(value, format.getInteger(key)); + } } } diff --git a/library/src/main/java/com/google/android/exoplayer/FrameworkSampleSource.java b/library/src/main/java/com/google/android/exoplayer/FrameworkSampleSource.java index a319636d52..7116006bde 100644 --- a/library/src/main/java/com/google/android/exoplayer/FrameworkSampleSource.java +++ b/library/src/main/java/com/google/android/exoplayer/FrameworkSampleSource.java @@ -23,6 +23,7 @@ import com.google.android.exoplayer.util.Assertions; import com.google.android.exoplayer.util.MimeTypes; import com.google.android.exoplayer.util.Util; +import android.annotation.SuppressLint; import android.annotation.TargetApi; import android.content.Context; import android.media.MediaExtractor; @@ -30,6 +31,8 @@ import android.net.Uri; import java.io.FileDescriptor; import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.ArrayList; import java.util.Map; import java.util.UUID; @@ -197,8 +200,7 @@ public final class FrameworkSampleSource implements SampleSource, SampleSourceRe return NOTHING_READ; } if (trackStates[track] != TRACK_STATE_FORMAT_SENT) { - formatHolder.format = MediaFormat.createFromFrameworkMediaFormatV16( - extractor.getTrackFormat(track)); + formatHolder.format = createMediaFormat(extractor.getTrackFormat(track)); formatHolder.drmInitData = Util.SDK_INT >= 18 ? getDrmInitDataV18() : null; trackStates[track] = TRACK_STATE_FORMAT_SENT; return FORMAT_READ; @@ -297,4 +299,37 @@ public final class FrameworkSampleSource implements SampleSource, SampleSourceRe } } + @SuppressLint("InlinedApi") + private static MediaFormat createMediaFormat(android.media.MediaFormat format) { + String mimeType = format.getString(android.media.MediaFormat.KEY_MIME); + String language = getOptionalStringV16(format, android.media.MediaFormat.KEY_LANGUAGE); + int maxInputSize = getOptionalIntegerV16(format, android.media.MediaFormat.KEY_MAX_INPUT_SIZE); + int width = getOptionalIntegerV16(format, android.media.MediaFormat.KEY_WIDTH); + int height = getOptionalIntegerV16(format, android.media.MediaFormat.KEY_HEIGHT); + int channelCount = getOptionalIntegerV16(format, android.media.MediaFormat.KEY_CHANNEL_COUNT); + int sampleRate = getOptionalIntegerV16(format, android.media.MediaFormat.KEY_SAMPLE_RATE); + ArrayList initializationData = new ArrayList<>(); + for (int i = 0; format.containsKey("csd-" + i); i++) { + ByteBuffer buffer = format.getByteBuffer("csd-" + i); + byte[] data = new byte[buffer.limit()]; + buffer.get(data); + initializationData.add(data); + buffer.flip(); + } + long durationUs = format.containsKey(android.media.MediaFormat.KEY_DURATION) + ? format.getLong(android.media.MediaFormat.KEY_DURATION) : C.UNKNOWN_TIME_US; + return new MediaFormat(mimeType, maxInputSize, durationUs, width, height, MediaFormat.NO_VALUE, + channelCount, sampleRate, language, initializationData); + } + + @TargetApi(16) + private static final String getOptionalStringV16(android.media.MediaFormat format, String key) { + return format.containsKey(key) ? format.getString(key) : null; + } + + @TargetApi(16) + private static final int getOptionalIntegerV16(android.media.MediaFormat format, String key) { + return format.containsKey(key) ? format.getInteger(key) : MediaFormat.NO_VALUE; + } + } diff --git a/library/src/main/java/com/google/android/exoplayer/MediaCodecTrackRenderer.java b/library/src/main/java/com/google/android/exoplayer/MediaCodecTrackRenderer.java index 02d8115bf3..84a24db6e3 100644 --- a/library/src/main/java/com/google/android/exoplayer/MediaCodecTrackRenderer.java +++ b/library/src/main/java/com/google/android/exoplayer/MediaCodecTrackRenderer.java @@ -890,8 +890,11 @@ public abstract class MediaCodecTrackRenderer extends SampleSourceTrackRenderer * incorrectly on the host device. False otherwise. */ private static boolean codecNeedsEndOfStreamWorkaround(String name) { - return Util.SDK_INT <= 17 && "ht7s3".equals(Util.DEVICE) // Tesco HUDL - && "OMX.rk.video_decoder.avc".equals(name); + return Util.SDK_INT <= 17 + && "OMX.rk.video_decoder.avc".equals(name) + && ("ht7s3".equals(Util.DEVICE) // Tesco HUDL + || "rk30sdk".equals(Util.DEVICE) // Rockchip rk30 + || "rk31sdk".equals(Util.DEVICE)); // Rockchip rk31 } } diff --git a/library/src/main/java/com/google/android/exoplayer/MediaFormat.java b/library/src/main/java/com/google/android/exoplayer/MediaFormat.java index c1de1160c9..3d1befbbb5 100644 --- a/library/src/main/java/com/google/android/exoplayer/MediaFormat.java +++ b/library/src/main/java/com/google/android/exoplayer/MediaFormat.java @@ -21,7 +21,6 @@ import android.annotation.SuppressLint; import android.annotation.TargetApi; import java.nio.ByteBuffer; -import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -31,9 +30,6 @@ import java.util.List; */ public final class MediaFormat { - private static final String KEY_PIXEL_WIDTH_HEIGHT_RATIO = - "com.google.android.videos.pixelWidthHeightRatio"; - public static final int NO_VALUE = -1; public final String mimeType; @@ -48,6 +44,8 @@ public final class MediaFormat { public final int channelCount; public final int sampleRate; + public final String language; + public final List initializationData; private int maxWidth; @@ -58,11 +56,6 @@ public final class MediaFormat { // Possibly-lazy-initialized framework media format. private android.media.MediaFormat frameworkMediaFormat; - @TargetApi(16) - public static MediaFormat createFromFrameworkMediaFormatV16(android.media.MediaFormat format) { - return new MediaFormat(format); - } - public static MediaFormat createVideoFormat(String mimeType, int maxInputSize, int width, int height, List initializationData) { return createVideoFormat( @@ -78,7 +71,7 @@ public final class MediaFormat { public static MediaFormat createVideoFormat(String mimeType, int maxInputSize, long durationUs, int width, int height, float pixelWidthHeightRatio, List initializationData) { return new MediaFormat(mimeType, maxInputSize, durationUs, width, height, pixelWidthHeightRatio, - NO_VALUE, NO_VALUE, initializationData); + NO_VALUE, NO_VALUE, null, initializationData); } public static MediaFormat createAudioFormat(String mimeType, int maxInputSize, int channelCount, @@ -90,15 +83,16 @@ public final class MediaFormat { public static MediaFormat createAudioFormat(String mimeType, int maxInputSize, long durationUs, int channelCount, int sampleRate, List initializationData) { return new MediaFormat(mimeType, maxInputSize, durationUs, NO_VALUE, NO_VALUE, NO_VALUE, - channelCount, sampleRate, initializationData); + channelCount, sampleRate, null, initializationData); } - public static MediaFormat createTextFormat(String mimeType) { - return createTextFormat(mimeType, C.UNKNOWN_TIME_US); + public static MediaFormat createTextFormat(String mimeType, String language) { + return createTextFormat(mimeType, language, C.UNKNOWN_TIME_US); } - public static MediaFormat createTextFormat(String mimeType, long durationUs) { - return createFormatForMimeType(mimeType, durationUs); + public static MediaFormat createTextFormat(String mimeType, String language, long durationUs) { + return new MediaFormat(mimeType, NO_VALUE, durationUs, NO_VALUE, NO_VALUE, NO_VALUE, + NO_VALUE, NO_VALUE, language, null); } public static MediaFormat createFormatForMimeType(String mimeType) { @@ -107,35 +101,11 @@ public final class MediaFormat { public static MediaFormat createFormatForMimeType(String mimeType, long durationUs) { return new MediaFormat(mimeType, NO_VALUE, durationUs, NO_VALUE, NO_VALUE, NO_VALUE, - NO_VALUE, NO_VALUE, null); + NO_VALUE, NO_VALUE, null, null); } - @TargetApi(16) - private MediaFormat(android.media.MediaFormat format) { - this.frameworkMediaFormat = format; - mimeType = format.getString(android.media.MediaFormat.KEY_MIME); - maxInputSize = getOptionalIntegerV16(format, android.media.MediaFormat.KEY_MAX_INPUT_SIZE); - width = getOptionalIntegerV16(format, android.media.MediaFormat.KEY_WIDTH); - height = getOptionalIntegerV16(format, android.media.MediaFormat.KEY_HEIGHT); - channelCount = getOptionalIntegerV16(format, android.media.MediaFormat.KEY_CHANNEL_COUNT); - sampleRate = getOptionalIntegerV16(format, android.media.MediaFormat.KEY_SAMPLE_RATE); - pixelWidthHeightRatio = getOptionalFloatV16(format, KEY_PIXEL_WIDTH_HEIGHT_RATIO); - initializationData = new ArrayList<>(); - for (int i = 0; format.containsKey("csd-" + i); i++) { - ByteBuffer buffer = format.getByteBuffer("csd-" + i); - byte[] data = new byte[buffer.limit()]; - buffer.get(data); - initializationData.add(data); - buffer.flip(); - } - durationUs = format.containsKey(android.media.MediaFormat.KEY_DURATION) - ? format.getLong(android.media.MediaFormat.KEY_DURATION) : C.UNKNOWN_TIME_US; - maxWidth = NO_VALUE; - maxHeight = NO_VALUE; - } - - private MediaFormat(String mimeType, int maxInputSize, long durationUs, int width, int height, - float pixelWidthHeightRatio, int channelCount, int sampleRate, + /* package */ MediaFormat(String mimeType, int maxInputSize, long durationUs, int width, + int height, float pixelWidthHeightRatio, int channelCount, int sampleRate, String language, List initializationData) { this.mimeType = mimeType; this.maxInputSize = maxInputSize; @@ -145,17 +115,20 @@ public final class MediaFormat { this.pixelWidthHeightRatio = pixelWidthHeightRatio; this.channelCount = channelCount; this.sampleRate = sampleRate; + this.language = language; this.initializationData = initializationData == null ? Collections.emptyList() : initializationData; maxWidth = NO_VALUE; maxHeight = NO_VALUE; } + @SuppressLint("InlinedApi") public void setMaxVideoDimensions(int maxWidth, int maxHeight) { this.maxWidth = maxWidth; this.maxHeight = maxHeight; if (frameworkMediaFormat != null) { - maybeSetMaxDimensionsV16(frameworkMediaFormat); + maybeSetIntegerV16(frameworkMediaFormat, android.media.MediaFormat.KEY_MAX_WIDTH, maxWidth); + maybeSetIntegerV16(frameworkMediaFormat, android.media.MediaFormat.KEY_MAX_HEIGHT, maxHeight); } } @@ -167,6 +140,41 @@ public final class MediaFormat { return maxHeight; } + /** + * @return A {@link MediaFormat} representation of this format. + */ + @SuppressLint("InlinedApi") + @TargetApi(16) + public final android.media.MediaFormat getFrameworkMediaFormatV16() { + if (frameworkMediaFormat == null) { + android.media.MediaFormat format = new android.media.MediaFormat(); + format.setString(android.media.MediaFormat.KEY_MIME, mimeType); + maybeSetStringV16(format, android.media.MediaFormat.KEY_LANGUAGE, language); + maybeSetIntegerV16(format, android.media.MediaFormat.KEY_MAX_INPUT_SIZE, maxInputSize); + maybeSetIntegerV16(format, android.media.MediaFormat.KEY_WIDTH, width); + maybeSetIntegerV16(format, android.media.MediaFormat.KEY_HEIGHT, height); + maybeSetIntegerV16(format, android.media.MediaFormat.KEY_MAX_WIDTH, maxWidth); + maybeSetIntegerV16(format, android.media.MediaFormat.KEY_MAX_HEIGHT, maxHeight); + maybeSetIntegerV16(format, android.media.MediaFormat.KEY_CHANNEL_COUNT, channelCount); + maybeSetIntegerV16(format, android.media.MediaFormat.KEY_SAMPLE_RATE, sampleRate); + for (int i = 0; i < initializationData.size(); i++) { + format.setByteBuffer("csd-" + i, ByteBuffer.wrap(initializationData.get(i))); + } + if (durationUs != C.UNKNOWN_TIME_US) { + format.setLong(android.media.MediaFormat.KEY_DURATION, durationUs); + } + frameworkMediaFormat = format; + } + return frameworkMediaFormat; + } + + @Override + public String toString() { + return "MediaFormat(" + mimeType + ", " + maxInputSize + ", " + width + ", " + height + ", " + + pixelWidthHeightRatio + ", " + channelCount + ", " + sampleRate + ", " + durationUs + ", " + + maxWidth + ", " + maxHeight + ")"; + } + @Override public int hashCode() { if (hashCode == 0) { @@ -227,44 +235,12 @@ public final class MediaFormat { return true; } - @Override - public String toString() { - return "MediaFormat(" + mimeType + ", " + maxInputSize + ", " + width + ", " + height + ", " - + pixelWidthHeightRatio + ", " + channelCount + ", " + sampleRate + ", " + durationUs + ", " - + maxWidth + ", " + maxHeight + ")"; - } - - /** - * @return A {@link MediaFormat} representation of this format. - */ @TargetApi(16) - public final android.media.MediaFormat getFrameworkMediaFormatV16() { - if (frameworkMediaFormat == null) { - android.media.MediaFormat format = new android.media.MediaFormat(); - format.setString(android.media.MediaFormat.KEY_MIME, mimeType); - maybeSetIntegerV16(format, android.media.MediaFormat.KEY_MAX_INPUT_SIZE, maxInputSize); - maybeSetIntegerV16(format, android.media.MediaFormat.KEY_WIDTH, width); - maybeSetIntegerV16(format, android.media.MediaFormat.KEY_HEIGHT, height); - maybeSetIntegerV16(format, android.media.MediaFormat.KEY_CHANNEL_COUNT, channelCount); - maybeSetIntegerV16(format, android.media.MediaFormat.KEY_SAMPLE_RATE, sampleRate); - maybeSetFloatV16(format, KEY_PIXEL_WIDTH_HEIGHT_RATIO, pixelWidthHeightRatio); - for (int i = 0; i < initializationData.size(); i++) { - format.setByteBuffer("csd-" + i, ByteBuffer.wrap(initializationData.get(i))); - } - if (durationUs != C.UNKNOWN_TIME_US) { - format.setLong(android.media.MediaFormat.KEY_DURATION, durationUs); - } - maybeSetMaxDimensionsV16(format); - frameworkMediaFormat = format; + private static final void maybeSetStringV16(android.media.MediaFormat format, String key, + String value) { + if (value != null) { + format.setString(key, value); } - return frameworkMediaFormat; - } - - @SuppressLint("InlinedApi") - @TargetApi(16) - private final void maybeSetMaxDimensionsV16(android.media.MediaFormat format) { - maybeSetIntegerV16(format, android.media.MediaFormat.KEY_MAX_WIDTH, maxWidth); - maybeSetIntegerV16(format, android.media.MediaFormat.KEY_MAX_HEIGHT, maxHeight); } @TargetApi(16) @@ -275,22 +251,4 @@ public final class MediaFormat { } } - @TargetApi(16) - private static final void maybeSetFloatV16(android.media.MediaFormat format, String key, - float value) { - if (value != NO_VALUE) { - format.setFloat(key, value); - } - } - - @TargetApi(16) - private static final int getOptionalIntegerV16(android.media.MediaFormat format, String key) { - return format.containsKey(key) ? format.getInteger(key) : NO_VALUE; - } - - @TargetApi(16) - private static final float getOptionalFloatV16(android.media.MediaFormat format, String key) { - return format.containsKey(key) ? format.getFloat(key) : NO_VALUE; - } - } diff --git a/library/src/main/java/com/google/android/exoplayer/SampleSourceTrackRenderer.java b/library/src/main/java/com/google/android/exoplayer/SampleSourceTrackRenderer.java index 4ba94c07a5..f2372df618 100644 --- a/library/src/main/java/com/google/android/exoplayer/SampleSourceTrackRenderer.java +++ b/library/src/main/java/com/google/android/exoplayer/SampleSourceTrackRenderer.java @@ -70,15 +70,6 @@ public abstract class SampleSourceTrackRenderer extends TrackRenderer { */ protected abstract boolean handlesTrack(TrackInfo trackInfo); - /** - * Invoked when a track is selected. - * - * @param trackInfo The selected track. - */ - protected void onTrackSelected(TrackInfo trackInfo) { - // Do nothing. - } - @Override protected void onEnabled(int track, long positionUs, boolean joining) throws ExoPlaybackException { diff --git a/library/src/main/java/com/google/android/exoplayer/dash/DashChunkSource.java b/library/src/main/java/com/google/android/exoplayer/dash/DashChunkSource.java index b4b0279052..815f4c02dd 100644 --- a/library/src/main/java/com/google/android/exoplayer/dash/DashChunkSource.java +++ b/library/src/main/java/com/google/android/exoplayer/dash/DashChunkSource.java @@ -656,7 +656,8 @@ public class DashChunkSource implements ChunkSource { } return new SingleSampleMediaChunk(dataSource, dataSpec, Chunk.TRIGGER_INITIAL, representation.format, startTimeUs, endTimeUs, absoluteSegmentNum, isLastSegment, - MediaFormat.createTextFormat(MimeTypes.TEXT_VTT), null, representationHolder.vttHeader); + MediaFormat.createTextFormat(MimeTypes.TEXT_VTT, representation.format.language), null, + representationHolder.vttHeader); } else { return new ContainerMediaChunk(dataSource, dataSpec, trigger, representation.format, startTimeUs, endTimeUs, absoluteSegmentNum, isLastSegment, sampleOffsetUs, diff --git a/library/src/main/java/com/google/android/exoplayer/extractor/mp4/AtomParsers.java b/library/src/main/java/com/google/android/exoplayer/extractor/mp4/AtomParsers.java index 06395188ac..1eabba2566 100644 --- a/library/src/main/java/com/google/android/exoplayer/extractor/mp4/AtomParsers.java +++ b/library/src/main/java/com/google/android/exoplayer/extractor/mp4/AtomParsers.java @@ -62,10 +62,11 @@ import java.util.List; Atom.ContainerAtom stbl = mdia.getContainerAtomOfType(Atom.TYPE_minf) .getContainerAtomOfType(Atom.TYPE_stbl); - long mediaTimescale = parseMdhd(mdia.getLeafAtomOfType(Atom.TYPE_mdhd).data); - StsdDataHolder stsdData = parseStsd(stbl.getLeafAtomOfType(Atom.TYPE_stsd).data, durationUs); + Pair mdhdData = parseMdhd(mdia.getLeafAtomOfType(Atom.TYPE_mdhd).data); + StsdDataHolder stsdData = parseStsd(stbl.getLeafAtomOfType(Atom.TYPE_stsd).data, durationUs, + mdhdData.second); return stsdData.mediaFormat == null ? null - : new Track(id, trackType, mediaTimescale, durationUs, stsdData.mediaFormat, + : new Track(id, trackType, mdhdData.first, durationUs, stsdData.mediaFormat, stsdData.trackEncryptionBoxes, stsdData.nalUnitLengthFieldLength); } @@ -314,18 +315,25 @@ import java.util.List; * Parses an mdhd atom (defined in 14496-12). * * @param mdhd The mdhd atom to parse. - * @return The media timescale, defined as the number of time units that pass in one second. + * @return A pair consisting of the media timescale defined as the number of time units that pass + * in one second, and the language code. */ - private static long parseMdhd(ParsableByteArray mdhd) { + private static Pair parseMdhd(ParsableByteArray mdhd) { mdhd.setPosition(Atom.HEADER_SIZE); int fullAtom = mdhd.readInt(); int version = Atom.parseFullAtomVersion(fullAtom); - mdhd.skipBytes(version == 0 ? 8 : 16); - return mdhd.readUnsignedInt(); + long timescale = mdhd.readUnsignedInt(); + mdhd.skipBytes(version == 0 ? 4 : 8); + int languageCode = mdhd.readUnsignedShort(); + String language = "" + (char) (((languageCode >> 10) & 0x1F) + 0x60) + + (char) (((languageCode >> 5) & 0x1F) + 0x60) + + (char) (((languageCode) & 0x1F) + 0x60); + return Pair.create(timescale, language); } - private static StsdDataHolder parseStsd(ParsableByteArray stsd, long durationUs) { + private static StsdDataHolder parseStsd(ParsableByteArray stsd, long durationUs, + String language) { stsd.setPosition(Atom.FULL_HEADER_SIZE); int numberOfEntries = stsd.readInt(); StsdDataHolder holder = new StsdDataHolder(numberOfEntries); @@ -344,9 +352,11 @@ import java.util.List; parseAudioSampleEntry(stsd, childAtomType, childStartPosition, childAtomSize, durationUs, holder, i); } else if (childAtomType == Atom.TYPE_TTML) { - holder.mediaFormat = MediaFormat.createTextFormat(MimeTypes.APPLICATION_TTML, durationUs); + holder.mediaFormat = MediaFormat.createTextFormat(MimeTypes.APPLICATION_TTML, language, + durationUs); } else if (childAtomType == Atom.TYPE_tx3g) { - holder.mediaFormat = MediaFormat.createTextFormat(MimeTypes.APPLICATION_TX3G, durationUs); + holder.mediaFormat = MediaFormat.createTextFormat(MimeTypes.APPLICATION_TX3G, language, + durationUs); } stsd.setPosition(childStartPosition + childAtomSize); } diff --git a/library/src/main/java/com/google/android/exoplayer/extractor/ts/Id3Reader.java b/library/src/main/java/com/google/android/exoplayer/extractor/ts/Id3Reader.java index 4e778820a5..c649b33bf0 100644 --- a/library/src/main/java/com/google/android/exoplayer/extractor/ts/Id3Reader.java +++ b/library/src/main/java/com/google/android/exoplayer/extractor/ts/Id3Reader.java @@ -35,7 +35,7 @@ import com.google.android.exoplayer.util.ParsableByteArray; public Id3Reader(TrackOutput output) { super(output); - output.format(MediaFormat.createTextFormat(MimeTypes.APPLICATION_ID3)); + output.format(MediaFormat.createFormatForMimeType(MimeTypes.APPLICATION_ID3)); } @Override diff --git a/library/src/main/java/com/google/android/exoplayer/extractor/ts/SeiReader.java b/library/src/main/java/com/google/android/exoplayer/extractor/ts/SeiReader.java index ac1aaef4a2..36a3c82ba6 100644 --- a/library/src/main/java/com/google/android/exoplayer/extractor/ts/SeiReader.java +++ b/library/src/main/java/com/google/android/exoplayer/extractor/ts/SeiReader.java @@ -32,7 +32,7 @@ import com.google.android.exoplayer.util.ParsableByteArray; public SeiReader(TrackOutput output) { super(output); - output.format(MediaFormat.createTextFormat(MimeTypes.APPLICATION_EIA608)); + output.format(MediaFormat.createTextFormat(MimeTypes.APPLICATION_EIA608, null)); } @Override diff --git a/library/src/main/java/com/google/android/exoplayer/smoothstreaming/SmoothStreamingChunkSource.java b/library/src/main/java/com/google/android/exoplayer/smoothstreaming/SmoothStreamingChunkSource.java index 9849b3d323..9d5af3c482 100644 --- a/library/src/main/java/com/google/android/exoplayer/smoothstreaming/SmoothStreamingChunkSource.java +++ b/library/src/main/java/com/google/android/exoplayer/smoothstreaming/SmoothStreamingChunkSource.java @@ -398,7 +398,7 @@ public class SmoothStreamingChunkSource implements ChunkSource { trackFormat.numChannels, trackFormat.audioSamplingRate, csd); return format; } else if (streamElement.type == StreamElement.TYPE_TEXT) { - return MediaFormat.createTextFormat(trackFormat.mimeType); + return MediaFormat.createTextFormat(trackFormat.mimeType, trackFormat.language); } return null; } diff --git a/library/src/main/java/com/google/android/exoplayer/text/TextTrackRenderer.java b/library/src/main/java/com/google/android/exoplayer/text/TextTrackRenderer.java index 4c96587c39..640d2eaf5d 100644 --- a/library/src/main/java/com/google/android/exoplayer/text/TextTrackRenderer.java +++ b/library/src/main/java/com/google/android/exoplayer/text/TextTrackRenderer.java @@ -151,29 +151,14 @@ public final class TextTrackRenderer extends SampleSourceTrackRenderer implement @Override protected boolean handlesTrack(TrackInfo trackInfo) { - for (int i = 0; i < subtitleParsers.length; i++) { - if (subtitleParsers[i].canParse(trackInfo.mimeType)) { - return true; - } - } - return false; - } - - @Override - protected void onTrackSelected(TrackInfo trackInfo) { - for (int i = 0; i < subtitleParsers.length; i++) { - if (subtitleParsers[i].canParse(trackInfo.mimeType)) { - parserIndex = i; - return; - } - } - throw new IllegalStateException("Invalid track selected"); + return getParserIndex(trackInfo) != -1; } @Override protected void onEnabled(int track, long positionUs, boolean joining) throws ExoPlaybackException { super.onEnabled(track, positionUs, joining); + parserIndex = getParserIndex(getTrackInfo(track)); parserThread = new HandlerThread("textParser"); parserThread.start(); parserHelper = new SubtitleParserHelper(parserThread.getLooper(), subtitleParsers[parserIndex]); @@ -311,4 +296,13 @@ public final class TextTrackRenderer extends SampleSourceTrackRenderer implement textRenderer.onCues(cues); } + private int getParserIndex(TrackInfo trackInfo) { + for (int i = 0; i < subtitleParsers.length; i++) { + if (subtitleParsers[i].canParse(trackInfo.mimeType)) { + return i; + } + } + return -1; + } + }