diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 6b3fbc4f27..1699f2c09b 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -1,5 +1,24 @@ # Release notes # +### 2.9.3 ### + +* Captions: Support PNG subtitles in SMPTE-TT + ([#1583](https://github.com/google/ExoPlayer/issues/1583)). +* MPEG-TS: Use random access indicators to minimize the need for + `FLAG_ALLOW_NON_IDR_KEYFRAMES`. +* Downloading: Reduce time taken to remove downloads + ([#5136](https://github.com/google/ExoPlayer/issues/5136)). +* MP3: + * Use the true bitrate for constant-bitrate MP3 seeking. + * Fix issue where streams would play twice on some Samsung devices + ([#4519](https://github.com/google/ExoPlayer/issues/4519)). +* Fix regression where some audio formats were incorrectly marked as being + unplayable due to under-reporting of platform decoder capabilities + ([#5145](https://github.com/google/ExoPlayer/issues/5145)). +* Fix decode-only frame skipping on Nvidia Shield TV devices. +* Workaround for MiTV (dangal) issue when swapping output surface + ([#5169](https://github.com/google/ExoPlayer/issues/5169)). + ### 2.9.2 ### * HLS: @@ -47,10 +66,10 @@ * DASH: Parse ProgramInformation element if present in the manifest. * HLS: * Add constructor to `DefaultHlsExtractorFactory` for adding TS payload - reader factory flags. + reader factory flags + ([#4861](https://github.com/google/ExoPlayer/issues/4861)). * Fix bug in segment sniffing ([#5039](https://github.com/google/ExoPlayer/issues/5039)). - ([#4861](https://github.com/google/ExoPlayer/issues/4861)). * SubRip: Add support for alignment tags, and remove tags from the displayed captions ([#4306](https://github.com/google/ExoPlayer/issues/4306)). * Fix issue with blind seeking to windows with non-zero offset in a diff --git a/constants.gradle b/constants.gradle index cac4f6d78b..ac801d2d3b 100644 --- a/constants.gradle +++ b/constants.gradle @@ -13,8 +13,8 @@ // limitations under the License. project.ext { // ExoPlayer version and version code. - releaseVersion = '2.9.2' - releaseVersionCode = 2009002 + releaseVersion = '2.9.3' + releaseVersionCode = 2009003 // Important: ExoPlayer specifies a minSdkVersion of 14 because various // components provided by the library may be of use on older devices. // However, please note that the core media playback functionality provided diff --git a/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/CastPlayer.java b/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/CastPlayer.java index 6cf6309796..71322de87e 100644 --- a/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/CastPlayer.java +++ b/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/CastPlayer.java @@ -283,20 +283,29 @@ public final class CastPlayer extends BasePlayer { // Player implementation. @Override + @Nullable public AudioComponent getAudioComponent() { return null; } @Override + @Nullable public VideoComponent getVideoComponent() { return null; } @Override + @Nullable public TextComponent getTextComponent() { return null; } + @Override + @Nullable + public MetadataComponent getMetadataComponent() { + return null; + } + @Override public Looper getApplicationLooper() { return Looper.getMainLooper(); diff --git a/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsMediaSource.java b/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsMediaSource.java index 400061d019..85042c4354 100644 --- a/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsMediaSource.java +++ b/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsMediaSource.java @@ -76,6 +76,12 @@ public final class ImaAdsMediaSource extends BaseMediaSource implements SourceIn adUiViewGroup, eventHandler, eventListener); } + @Override + @Nullable + public Object getTag() { + return adsMediaSource.getTag(); + } + @Override public void prepareSourceInternal( final ExoPlayer player, diff --git a/extensions/mediasession/src/main/java/com/google/android/exoplayer2/ext/mediasession/TimelineQueueEditor.java b/extensions/mediasession/src/main/java/com/google/android/exoplayer2/ext/mediasession/TimelineQueueEditor.java index 4f9c553a15..7c00fcdf17 100644 --- a/extensions/mediasession/src/main/java/com/google/android/exoplayer2/ext/mediasession/TimelineQueueEditor.java +++ b/extensions/mediasession/src/main/java/com/google/android/exoplayer2/ext/mediasession/TimelineQueueEditor.java @@ -64,13 +64,6 @@ public final class TimelineQueueEditor * {@link MediaSessionConnector}. */ public interface QueueDataAdapter { - /** - * Gets the {@link MediaDescriptionCompat} for a {@code position}. - * - * @param position The position in the queue for which to provide a description. - * @return A {@link MediaDescriptionCompat}. - */ - MediaDescriptionCompat getMediaDescription(int position); /** * Adds a {@link MediaDescriptionCompat} at the given {@code position}. * diff --git a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java index ffdadb78f7..35fa85e467 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java @@ -144,20 +144,29 @@ import java.util.concurrent.CopyOnWriteArraySet; } @Override + @Nullable public AudioComponent getAudioComponent() { return null; } @Override + @Nullable public VideoComponent getVideoComponent() { return null; } @Override + @Nullable public TextComponent getTextComponent() { return null; } + @Override + @Nullable + public MetadataComponent getMetadataComponent() { + return null; + } + @Override public Looper getPlaybackLooper() { return internalPlayer.getPlaybackLooper(); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerLibraryInfo.java b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerLibraryInfo.java index c30fe160c9..792f6cf651 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerLibraryInfo.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerLibraryInfo.java @@ -29,11 +29,11 @@ public final class ExoPlayerLibraryInfo { /** The version of the library expressed as a string, for example "1.2.3". */ // Intentionally hardcoded. Do not derive from other constants (e.g. VERSION_INT) or vice versa. - public static final String VERSION = "2.9.2"; + public static final String VERSION = "2.9.3"; /** The version of the library expressed as {@code "ExoPlayerLib/" + VERSION}. */ // Intentionally hardcoded. Do not derive from other constants (e.g. VERSION) or vice versa. - public static final String VERSION_SLASHY = "ExoPlayerLib/2.9.2"; + public static final String VERSION_SLASHY = "ExoPlayerLib/2.9.3"; /** * The version of the library expressed as an integer, for example 1002003. @@ -43,7 +43,7 @@ public final class ExoPlayerLibraryInfo { * integer version 123045006 (123-045-006). */ // Intentionally hardcoded. Do not derive from other constants (e.g. VERSION) or vice versa. - public static final int VERSION_INT = 2009002; + public static final int VERSION_INT = 2009003; /** * Whether the library was compiled with {@link com.google.android.exoplayer2.util.Assertions} diff --git a/library/core/src/main/java/com/google/android/exoplayer2/Player.java b/library/core/src/main/java/com/google/android/exoplayer2/Player.java index 16f8aa2878..e3441fb2a7 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/Player.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/Player.java @@ -26,6 +26,7 @@ import com.google.android.exoplayer2.C.VideoScalingMode; import com.google.android.exoplayer2.audio.AudioAttributes; import com.google.android.exoplayer2.audio.AudioListener; import com.google.android.exoplayer2.audio.AuxEffectInfo; +import com.google.android.exoplayer2.metadata.MetadataOutput; import com.google.android.exoplayer2.source.TrackGroupArray; import com.google.android.exoplayer2.text.TextOutput; import com.google.android.exoplayer2.trackselection.TrackSelectionArray; @@ -299,6 +300,24 @@ public interface Player { void removeTextOutput(TextOutput listener); } + /** The metadata component of a {@link Player}. */ + interface MetadataComponent { + + /** + * Adds a {@link MetadataOutput} to receive metadata. + * + * @param output The output to register. + */ + void addMetadataOutput(MetadataOutput output); + + /** + * Removes a {@link MetadataOutput}. + * + * @param output The output to remove. + */ + void removeMetadataOutput(MetadataOutput output); + } + /** * Listener of changes in player state. All methods have no-op default implementations to allow * selective overrides. @@ -533,6 +552,12 @@ public interface Player { @Nullable TextComponent getTextComponent(); + /** + * Returns the component of this player for metadata output, or null if metadata is not supported. + */ + @Nullable + MetadataComponent getMetadataComponent(); + /** * Returns the {@link Looper} associated with the application thread that's used to access the * player and on which player events are received. diff --git a/library/core/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java b/library/core/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java index 8517556887..fe52cc7e8c 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java @@ -65,7 +65,11 @@ import java.util.concurrent.CopyOnWriteArraySet; */ @TargetApi(16) public class SimpleExoPlayer extends BasePlayer - implements ExoPlayer, Player.AudioComponent, Player.VideoComponent, Player.TextComponent { + implements ExoPlayer, + Player.AudioComponent, + Player.VideoComponent, + Player.TextComponent, + Player.MetadataComponent { /** @deprecated Use {@link com.google.android.exoplayer2.video.VideoListener}. */ @Deprecated @@ -243,20 +247,29 @@ public class SimpleExoPlayer extends BasePlayer } @Override + @Nullable public AudioComponent getAudioComponent() { return this; } @Override + @Nullable public VideoComponent getVideoComponent() { return this; } @Override + @Nullable public TextComponent getTextComponent() { return this; } + @Override + @Nullable + public MetadataComponent getMetadataComponent() { + return this; + } + /** * Sets the video scaling mode. * @@ -713,20 +726,12 @@ public class SimpleExoPlayer extends BasePlayer removeTextOutput(output); } - /** - * Adds a {@link MetadataOutput} to receive metadata. - * - * @param listener The output to register. - */ + @Override public void addMetadataOutput(MetadataOutput listener) { metadataOutputs.add(listener); } - /** - * Removes a {@link MetadataOutput}. - * - * @param listener The output to remove. - */ + @Override public void removeMetadataOutput(MetadataOutput listener) { metadataOutputs.remove(listener); } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioRendererEventListener.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioRendererEventListener.java index 7c3c1481fc..eff7bc8de2 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioRendererEventListener.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioRendererEventListener.java @@ -25,7 +25,8 @@ import com.google.android.exoplayer2.decoder.DecoderCounters; import com.google.android.exoplayer2.util.Assertions; /** - * Listener of audio {@link Renderer} events. + * Listener of audio {@link Renderer} events. All methods have no-op default implementations to + * allow selective overrides. */ public interface AudioRendererEventListener { @@ -35,14 +36,14 @@ public interface AudioRendererEventListener { * @param counters {@link DecoderCounters} that will be updated by the renderer for as long as it * remains enabled. */ - void onAudioEnabled(DecoderCounters counters); + default void onAudioEnabled(DecoderCounters counters) {} /** * Called when the audio session is set. * * @param audioSessionId The audio session id. */ - void onAudioSessionId(int audioSessionId); + default void onAudioSessionId(int audioSessionId) {} /** * Called when a decoder is created. @@ -52,15 +53,15 @@ public interface AudioRendererEventListener { * finished. * @param initializationDurationMs The time taken to initialize the decoder in milliseconds. */ - void onAudioDecoderInitialized(String decoderName, long initializedTimestampMs, - long initializationDurationMs); + default void onAudioDecoderInitialized( + String decoderName, long initializedTimestampMs, long initializationDurationMs) {} /** * Called when the format of the media being consumed by the renderer changes. * * @param format The new format. */ - void onAudioInputFormatChanged(Format format); + default void onAudioInputFormatChanged(Format format) {} /** * Called when an {@link AudioSink} underrun occurs. @@ -71,14 +72,15 @@ public interface AudioRendererEventListener { * as the buffered media can have a variable bitrate so the duration may be unknown. * @param elapsedSinceLastFeedMs The time since the {@link AudioSink} was last fed data. */ - void onAudioSinkUnderrun(int bufferSize, long bufferSizeMs, long elapsedSinceLastFeedMs); + default void onAudioSinkUnderrun( + int bufferSize, long bufferSizeMs, long elapsedSinceLastFeedMs) {} /** * Called when the renderer is disabled. * * @param counters {@link DecoderCounters} that were updated by the renderer. */ - void onAudioDisabled(DecoderCounters counters); + default void onAudioDisabled(DecoderCounters counters) {} /** * Dispatches events to a {@link AudioRendererEventListener}. diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/MpegAudioHeader.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/MpegAudioHeader.java index ab49ca5454..87bb992082 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/MpegAudioHeader.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/MpegAudioHeader.java @@ -34,16 +34,26 @@ public final class MpegAudioHeader { private static final String[] MIME_TYPE_BY_LAYER = new String[] {MimeTypes.AUDIO_MPEG_L1, MimeTypes.AUDIO_MPEG_L2, MimeTypes.AUDIO_MPEG}; private static final int[] SAMPLING_RATE_V1 = {44100, 48000, 32000}; - private static final int[] BITRATE_V1_L1 = - {32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448}; - private static final int[] BITRATE_V2_L1 = - {32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256}; - private static final int[] BITRATE_V1_L2 = - {32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384}; - private static final int[] BITRATE_V1_L3 = - {32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320}; - private static final int[] BITRATE_V2 = - {8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160}; + private static final int[] BITRATE_V1_L1 = { + 32000, 64000, 96000, 128000, 160000, 192000, 224000, 256000, 288000, 320000, 352000, 384000, + 416000, 448000 + }; + private static final int[] BITRATE_V2_L1 = { + 32000, 48000, 56000, 64000, 80000, 96000, 112000, 128000, 144000, 160000, 176000, 192000, + 224000, 256000 + }; + private static final int[] BITRATE_V1_L2 = { + 32000, 48000, 56000, 64000, 80000, 96000, 112000, 128000, 160000, 192000, 224000, 256000, + 320000, 384000 + }; + private static final int[] BITRATE_V1_L3 = { + 32000, 40000, 48000, 56000, 64000, 80000, 96000, 112000, 128000, 160000, 192000, 224000, 256000, + 320000 + }; + private static final int[] BITRATE_V2 = { + 8000, 16000, 24000, 32000, 40000, 48000, 56000, 64000, 80000, 96000, 112000, 128000, 144000, + 160000 + }; /** * Returns the size of the frame associated with {@code header}, or {@link C#LENGTH_UNSET} if it @@ -89,7 +99,7 @@ public final class MpegAudioHeader { if (layer == 3) { // Layer I (layer == 3) bitrate = version == 3 ? BITRATE_V1_L1[bitrateIndex - 1] : BITRATE_V2_L1[bitrateIndex - 1]; - return (12000 * bitrate / samplingRate + padding) * 4; + return (12 * bitrate / samplingRate + padding) * 4; } else { // Layer II (layer == 2) or III (layer == 1) if (version == 3) { @@ -102,10 +112,10 @@ public final class MpegAudioHeader { if (version == 3) { // Version 1 - return 144000 * bitrate / samplingRate + padding; + return 144 * bitrate / samplingRate + padding; } else { // Version 2 or 2.5 - return (layer == 1 ? 72000 : 144000) * bitrate / samplingRate + padding; + return (layer == 1 ? 72 : 144) * bitrate / samplingRate + padding; } } @@ -159,7 +169,7 @@ public final class MpegAudioHeader { if (layer == 3) { // Layer I (layer == 3) bitrate = version == 3 ? BITRATE_V1_L1[bitrateIndex - 1] : BITRATE_V2_L1[bitrateIndex - 1]; - frameSize = (12000 * bitrate / sampleRate + padding) * 4; + frameSize = (12 * bitrate / sampleRate + padding) * 4; samplesPerFrame = 384; } else { // Layer II (layer == 2) or III (layer == 1) @@ -167,19 +177,22 @@ public final class MpegAudioHeader { // Version 1 bitrate = layer == 2 ? BITRATE_V1_L2[bitrateIndex - 1] : BITRATE_V1_L3[bitrateIndex - 1]; samplesPerFrame = 1152; - frameSize = 144000 * bitrate / sampleRate + padding; + frameSize = 144 * bitrate / sampleRate + padding; } else { // Version 2 or 2.5. bitrate = BITRATE_V2[bitrateIndex - 1]; samplesPerFrame = layer == 1 ? 576 : 1152; - frameSize = (layer == 1 ? 72000 : 144000) * bitrate / sampleRate + padding; + frameSize = (layer == 1 ? 72 : 144) * bitrate / sampleRate + padding; } } + // Calculate the bitrate in the same way Mp3Extractor calculates sample timestamps so that + // seeking to a given timestamp and playing from the start up to that timestamp give the same + // results for CBR streams. See also [internal: b/120390268]. + bitrate = 8 * frameSize * sampleRate / samplesPerFrame; String mimeType = MIME_TYPE_BY_LAYER[3 - layer]; int channels = ((headerData >> 6) & 3) == 3 ? 1 : 2; - header.setValues(version, mimeType, frameSize, sampleRate, channels, bitrate * 1000, - samplesPerFrame); + header.setValues(version, mimeType, frameSize, sampleRate, channels, bitrate, samplesPerFrame); return true; } @@ -198,8 +211,14 @@ public final class MpegAudioHeader { /** Number of samples stored in the frame. */ public int samplesPerFrame; - private void setValues(int version, String mimeType, int frameSize, int sampleRate, int channels, - int bitrate, int samplesPerFrame) { + private void setValues( + int version, + String mimeType, + int frameSize, + int sampleRate, + int channels, + int bitrate, + int samplesPerFrame) { this.version = version; this.mimeType = mimeType; this.frameSize = frameSize; diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/Ac3Extractor.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/Ac3Extractor.java index 93ce15a7ab..3741d52294 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/Ac3Extractor.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/Ac3Extractor.java @@ -15,6 +15,8 @@ */ package com.google.android.exoplayer2.extractor.ts; +import static com.google.android.exoplayer2.extractor.ts.TsPayloadReader.FLAG_DATA_ALIGNMENT_INDICATOR; + import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.audio.Ac3Util; import com.google.android.exoplayer2.extractor.Extractor; @@ -140,7 +142,7 @@ public final class Ac3Extractor implements Extractor { if (!startedPacket) { // Pass data to the reader as though it's contained within a single infinitely long packet. - reader.packetStarted(firstSampleTimestampUs, true); + reader.packetStarted(firstSampleTimestampUs, FLAG_DATA_ALIGNMENT_INDICATOR); startedPacket = true; } // TODO: Make it possible for the reader to consume the dataSource directly, so that it becomes diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/Ac3Reader.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/Ac3Reader.java index 2ef9704a7a..93724be92d 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/Ac3Reader.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/Ac3Reader.java @@ -100,7 +100,7 @@ public final class Ac3Reader implements ElementaryStreamReader { } @Override - public void packetStarted(long pesTimeUs, boolean dataAlignmentIndicator) { + public void packetStarted(long pesTimeUs, @TsPayloadReader.Flags int flags) { timeUs = pesTimeUs; } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/AdtsExtractor.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/AdtsExtractor.java index 04a6b571bd..77b79fa19f 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/AdtsExtractor.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/AdtsExtractor.java @@ -15,6 +15,8 @@ */ package com.google.android.exoplayer2.extractor.ts; +import static com.google.android.exoplayer2.extractor.ts.TsPayloadReader.FLAG_DATA_ALIGNMENT_INDICATOR; + import android.support.annotation.IntDef; import android.support.annotation.Nullable; import com.google.android.exoplayer2.C; @@ -202,7 +204,7 @@ public final class AdtsExtractor implements Extractor { if (!startedPacket) { // Pass data to the reader as though it's contained within a single infinitely long packet. - reader.packetStarted(firstSampleTimestampUs, true); + reader.packetStarted(firstSampleTimestampUs, FLAG_DATA_ALIGNMENT_INDICATOR); startedPacket = true; } // TODO: Make it possible for reader to consume the dataSource directly, so that it becomes diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/AdtsReader.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/AdtsReader.java index e31f67c77c..589b543170 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/AdtsReader.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/AdtsReader.java @@ -141,7 +141,7 @@ public final class AdtsReader implements ElementaryStreamReader { } @Override - public void packetStarted(long pesTimeUs, boolean dataAlignmentIndicator) { + public void packetStarted(long pesTimeUs, @TsPayloadReader.Flags int flags) { timeUs = pesTimeUs; } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/DtsReader.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/DtsReader.java index 2e45853951..1f9b0e79d4 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/DtsReader.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/DtsReader.java @@ -80,7 +80,7 @@ public final class DtsReader implements ElementaryStreamReader { } @Override - public void packetStarted(long pesTimeUs, boolean dataAlignmentIndicator) { + public void packetStarted(long pesTimeUs, @TsPayloadReader.Flags int flags) { timeUs = pesTimeUs; } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/DvbSubtitleReader.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/DvbSubtitleReader.java index 0944d1810e..3f0a772b1c 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/DvbSubtitleReader.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/DvbSubtitleReader.java @@ -15,6 +15,8 @@ */ package com.google.android.exoplayer2.extractor.ts; +import static com.google.android.exoplayer2.extractor.ts.TsPayloadReader.FLAG_DATA_ALIGNMENT_INDICATOR; + import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.extractor.ExtractorOutput; @@ -73,8 +75,8 @@ public final class DvbSubtitleReader implements ElementaryStreamReader { } @Override - public void packetStarted(long pesTimeUs, boolean dataAlignmentIndicator) { - if (!dataAlignmentIndicator) { + public void packetStarted(long pesTimeUs, @TsPayloadReader.Flags int flags) { + if ((flags & FLAG_DATA_ALIGNMENT_INDICATOR) == 0) { return; } writingSample = true; diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/ElementaryStreamReader.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/ElementaryStreamReader.java index fa7f78c8c0..e022fc237b 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/ElementaryStreamReader.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/ElementaryStreamReader.java @@ -43,9 +43,9 @@ public interface ElementaryStreamReader { * Called when a packet starts. * * @param pesTimeUs The timestamp associated with the packet. - * @param dataAlignmentIndicator The data alignment indicator associated with the packet. + * @param flags See {@link TsPayloadReader.Flags}. */ - void packetStarted(long pesTimeUs, boolean dataAlignmentIndicator); + void packetStarted(long pesTimeUs, @TsPayloadReader.Flags int flags); /** * Consumes (possibly partial) data from the current packet. diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/H262Reader.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/H262Reader.java index e9827893ee..1564157d44 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/H262Reader.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/H262Reader.java @@ -107,7 +107,8 @@ public final class H262Reader implements ElementaryStreamReader { } @Override - public void packetStarted(long pesTimeUs, boolean dataAlignmentIndicator) { + public void packetStarted(long pesTimeUs, @TsPayloadReader.Flags int flags) { + // TODO (Internal b/32267012): Consider using random access indicator. this.pesTimeUs = pesTimeUs; } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/H264Reader.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/H264Reader.java index 45e094f69d..d249c1b9da 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/H264Reader.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/H264Reader.java @@ -15,6 +15,8 @@ */ package com.google.android.exoplayer2.extractor.ts; +import static com.google.android.exoplayer2.extractor.ts.TsPayloadReader.FLAG_RANDOM_ACCESS_INDICATOR; + import android.util.SparseArray; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.Format; @@ -56,9 +58,12 @@ public final class H264Reader implements ElementaryStreamReader { // State that should not be reset on seek. private boolean hasOutputFormat; - // Per packet state that gets reset at the start of each packet. + // Per PES packet state that gets reset at the start of each PES packet. private long pesTimeUs; + // State inherited from the TS packet header. + private boolean randomAccessIndicator; + // Scratch variables to avoid allocations. private final ParsableByteArray seiWrapper; @@ -88,6 +93,7 @@ public final class H264Reader implements ElementaryStreamReader { sei.reset(); sampleReader.reset(); totalBytesWritten = 0; + randomAccessIndicator = false; } @Override @@ -100,8 +106,9 @@ public final class H264Reader implements ElementaryStreamReader { } @Override - public void packetStarted(long pesTimeUs, boolean dataAlignmentIndicator) { + public void packetStarted(long pesTimeUs, @TsPayloadReader.Flags int flags) { this.pesTimeUs = pesTimeUs; + randomAccessIndicator |= (flags & FLAG_RANDOM_ACCESS_INDICATOR) != 0; } @Override @@ -220,12 +227,17 @@ public final class H264Reader implements ElementaryStreamReader { seiWrapper.setPosition(4); // NAL prefix and nal_unit() header. seiReader.consume(pesTimeUs, seiWrapper); } - sampleReader.endNalUnit(position, offset); + boolean sampleIsKeyFrame = + sampleReader.endNalUnit(position, offset, hasOutputFormat, randomAccessIndicator); + if (sampleIsKeyFrame) { + // This is either an IDR frame or the first I-frame since the random access indicator, so mark + // it as a keyframe. Clear the flag so that subsequent non-IDR I-frames are not marked as + // keyframes until we see another random access indicator. + randomAccessIndicator = false; + } } - /** - * Consumes a stream of NAL units and outputs samples. - */ + /** Consumes a stream of NAL units and outputs samples. */ private static final class SampleReader { private static final int DEFAULT_BUFFER_SIZE = 128; @@ -430,11 +442,12 @@ public final class H264Reader implements ElementaryStreamReader { isFilling = false; } - public void endNalUnit(long position, int offset) { + public boolean endNalUnit( + long position, int offset, boolean hasOutputFormat, boolean randomAccessIndicator) { if (nalUnitType == NAL_UNIT_TYPE_AUD || (detectAccessUnits && sliceHeader.isFirstVclNalUnitOfPicture(previousSliceHeader))) { // If the NAL unit ending is the start of a new sample, output the previous one. - if (readingSample) { + if (hasOutputFormat && readingSample) { int nalUnitLength = (int) (position - nalUnitStartPosition); outputSample(offset + nalUnitLength); } @@ -443,8 +456,12 @@ public final class H264Reader implements ElementaryStreamReader { sampleIsKeyframe = false; readingSample = true; } - sampleIsKeyframe |= nalUnitType == NAL_UNIT_TYPE_IDR || (allowNonIdrKeyframes - && nalUnitType == NAL_UNIT_TYPE_NON_IDR && sliceHeader.isISlice()); + boolean treatIFrameAsKeyframe = + allowNonIdrKeyframes ? sliceHeader.isISlice() : randomAccessIndicator; + sampleIsKeyframe |= + nalUnitType == NAL_UNIT_TYPE_IDR + || (treatIFrameAsKeyframe && nalUnitType == NAL_UNIT_TYPE_NON_IDR); + return sampleIsKeyframe; } private void outputSample(int offset) { @@ -486,10 +503,21 @@ public final class H264Reader implements ElementaryStreamReader { hasSliceType = true; } - public void setAll(SpsData spsData, int nalRefIdc, int sliceType, int frameNum, - int picParameterSetId, boolean fieldPicFlag, boolean bottomFieldFlagPresent, - boolean bottomFieldFlag, boolean idrPicFlag, int idrPicId, int picOrderCntLsb, - int deltaPicOrderCntBottom, int deltaPicOrderCnt0, int deltaPicOrderCnt1) { + public void setAll( + SpsData spsData, + int nalRefIdc, + int sliceType, + int frameNum, + int picParameterSetId, + boolean fieldPicFlag, + boolean bottomFieldFlagPresent, + boolean bottomFieldFlag, + boolean idrPicFlag, + int idrPicId, + int picOrderCntLsb, + int deltaPicOrderCntBottom, + int deltaPicOrderCnt0, + int deltaPicOrderCnt1) { this.spsData = spsData; this.nalRefIdc = nalRefIdc; this.sliceType = sliceType; @@ -514,23 +542,26 @@ public final class H264Reader implements ElementaryStreamReader { private boolean isFirstVclNalUnitOfPicture(SliceHeaderData other) { // See ISO 14496-10 subsection 7.4.1.2.4. - return isComplete && (!other.isComplete || frameNum != other.frameNum - || picParameterSetId != other.picParameterSetId || fieldPicFlag != other.fieldPicFlag - || (bottomFieldFlagPresent && other.bottomFieldFlagPresent - && bottomFieldFlag != other.bottomFieldFlag) - || (nalRefIdc != other.nalRefIdc && (nalRefIdc == 0 || other.nalRefIdc == 0)) - || (spsData.picOrderCountType == 0 && other.spsData.picOrderCountType == 0 - && (picOrderCntLsb != other.picOrderCntLsb - || deltaPicOrderCntBottom != other.deltaPicOrderCntBottom)) - || (spsData.picOrderCountType == 1 && other.spsData.picOrderCountType == 1 - && (deltaPicOrderCnt0 != other.deltaPicOrderCnt0 - || deltaPicOrderCnt1 != other.deltaPicOrderCnt1)) - || idrPicFlag != other.idrPicFlag - || (idrPicFlag && other.idrPicFlag && idrPicId != other.idrPicId)); + return isComplete + && (!other.isComplete + || frameNum != other.frameNum + || picParameterSetId != other.picParameterSetId + || fieldPicFlag != other.fieldPicFlag + || (bottomFieldFlagPresent + && other.bottomFieldFlagPresent + && bottomFieldFlag != other.bottomFieldFlag) + || (nalRefIdc != other.nalRefIdc && (nalRefIdc == 0 || other.nalRefIdc == 0)) + || (spsData.picOrderCountType == 0 + && other.spsData.picOrderCountType == 0 + && (picOrderCntLsb != other.picOrderCntLsb + || deltaPicOrderCntBottom != other.deltaPicOrderCntBottom)) + || (spsData.picOrderCountType == 1 + && other.spsData.picOrderCountType == 1 + && (deltaPicOrderCnt0 != other.deltaPicOrderCnt0 + || deltaPicOrderCnt1 != other.deltaPicOrderCnt1)) + || idrPicFlag != other.idrPicFlag + || (idrPicFlag && other.idrPicFlag && idrPicId != other.idrPicId)); } - } - } - } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/H265Reader.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/H265Reader.java index 13d679c47c..88bde53746 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/H265Reader.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/H265Reader.java @@ -104,7 +104,8 @@ public final class H265Reader implements ElementaryStreamReader { } @Override - public void packetStarted(long pesTimeUs, boolean dataAlignmentIndicator) { + public void packetStarted(long pesTimeUs, @TsPayloadReader.Flags int flags) { + // TODO (Internal b/32267012): Consider using random access indicator. this.pesTimeUs = pesTimeUs; } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/Id3Reader.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/Id3Reader.java index 0f0f2ad981..f936fb9e43 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/Id3Reader.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/Id3Reader.java @@ -15,6 +15,8 @@ */ package com.google.android.exoplayer2.extractor.ts; +import static com.google.android.exoplayer2.extractor.ts.TsPayloadReader.FLAG_DATA_ALIGNMENT_INDICATOR; + import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.extractor.ExtractorOutput; @@ -63,8 +65,8 @@ public final class Id3Reader implements ElementaryStreamReader { } @Override - public void packetStarted(long pesTimeUs, boolean dataAlignmentIndicator) { - if (!dataAlignmentIndicator) { + public void packetStarted(long pesTimeUs, @TsPayloadReader.Flags int flags) { + if ((flags & FLAG_DATA_ALIGNMENT_INDICATOR) == 0) { return; } writingSample = true; diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/LatmReader.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/LatmReader.java index f401a6e736..2a633c191d 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/LatmReader.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/LatmReader.java @@ -93,7 +93,7 @@ public final class LatmReader implements ElementaryStreamReader { } @Override - public void packetStarted(long pesTimeUs, boolean dataAlignmentIndicator) { + public void packetStarted(long pesTimeUs, @TsPayloadReader.Flags int flags) { timeUs = pesTimeUs; } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/MpegAudioReader.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/MpegAudioReader.java index effa7d7c96..393e297818 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/MpegAudioReader.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/MpegAudioReader.java @@ -83,7 +83,7 @@ public final class MpegAudioReader implements ElementaryStreamReader { } @Override - public void packetStarted(long pesTimeUs, boolean dataAlignmentIndicator) { + public void packetStarted(long pesTimeUs, @TsPayloadReader.Flags int flags) { timeUs = pesTimeUs; } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/PesReader.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/PesReader.java index 91cd548367..ff755f4ece 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/PesReader.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/PesReader.java @@ -78,9 +78,8 @@ public final class PesReader implements TsPayloadReader { } @Override - public final void consume(ParsableByteArray data, boolean payloadUnitStartIndicator) - throws ParserException { - if (payloadUnitStartIndicator) { + public final void consume(ParsableByteArray data, @Flags int flags) throws ParserException { + if ((flags & FLAG_PAYLOAD_UNIT_START_INDICATOR) != 0) { switch (state) { case STATE_FINDING_HEADER: case STATE_READING_HEADER: @@ -122,7 +121,8 @@ public final class PesReader implements TsPayloadReader { if (continueRead(data, pesScratch.data, readLength) && continueRead(data, null, extendedHeaderLength)) { parseHeaderExtension(); - reader.packetStarted(timeUs, dataAlignmentIndicator); + flags |= dataAlignmentIndicator ? FLAG_DATA_ALIGNMENT_INDICATOR : 0; + reader.packetStarted(timeUs, flags); setState(STATE_READING_BODY); } break; diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/PsExtractor.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/PsExtractor.java index c7a082aeac..f453a9cc43 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/PsExtractor.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/PsExtractor.java @@ -343,7 +343,7 @@ public final class PsExtractor implements Extractor { data.readBytes(pesScratch.data, 0, extendedHeaderLength); pesScratch.setPosition(0); parseHeaderExtension(); - pesPayloadReader.packetStarted(timeUs, true); + pesPayloadReader.packetStarted(timeUs, TsPayloadReader.FLAG_DATA_ALIGNMENT_INDICATOR); pesPayloadReader.consume(data); // We always have complete PES packets with program stream. pesPayloadReader.packetFinished(); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/SectionReader.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/SectionReader.java index d217cfcb7a..101a1f74d9 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/SectionReader.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/SectionReader.java @@ -57,7 +57,8 @@ public final class SectionReader implements TsPayloadReader { } @Override - public void consume(ParsableByteArray data, boolean payloadUnitStartIndicator) { + public void consume(ParsableByteArray data, @Flags int flags) { + boolean payloadUnitStartIndicator = (flags & FLAG_PAYLOAD_UNIT_START_INDICATOR) != 0; int payloadStartPosition = C.POSITION_UNSET; if (payloadUnitStartIndicator) { int payloadStartOffset = data.readUnsignedByte(); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/TsExtractor.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/TsExtractor.java index f47a481d7e..d91842423d 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/TsExtractor.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/TsExtractor.java @@ -15,6 +15,8 @@ */ package com.google.android.exoplayer2.extractor.ts; +import static com.google.android.exoplayer2.extractor.ts.TsPayloadReader.FLAG_PAYLOAD_UNIT_START_INDICATOR; + import android.support.annotation.IntDef; import android.util.SparseArray; import android.util.SparseBooleanArray; @@ -279,6 +281,8 @@ public final class TsExtractor implements Extractor { return RESULT_CONTINUE; } + @TsPayloadReader.Flags int packetHeaderFlags = 0; + // Note: See ISO/IEC 13818-1, section 2.4.3.2 for details of the header format. int tsPacketHeader = tsPacketBuffer.readInt(); if ((tsPacketHeader & 0x800000) != 0) { // transport_error_indicator @@ -286,7 +290,7 @@ public final class TsExtractor implements Extractor { tsPacketBuffer.setPosition(endOfPacket); return RESULT_CONTINUE; } - boolean payloadUnitStartIndicator = (tsPacketHeader & 0x400000) != 0; + packetHeaderFlags |= (tsPacketHeader & 0x400000) != 0 ? FLAG_PAYLOAD_UNIT_START_INDICATOR : 0; // Ignoring transport_priority (tsPacketHeader & 0x200000) int pid = (tsPacketHeader & 0x1FFF00) >> 8; // Ignoring transport_scrambling_control (tsPacketHeader & 0xC0) @@ -317,14 +321,20 @@ public final class TsExtractor implements Extractor { // Skip the adaptation field. if (adaptationFieldExists) { int adaptationFieldLength = tsPacketBuffer.readUnsignedByte(); - tsPacketBuffer.skipBytes(adaptationFieldLength); + int adaptationFieldFlags = tsPacketBuffer.readUnsignedByte(); + + packetHeaderFlags |= + (adaptationFieldFlags & 0x40) != 0 // random_access_indicator. + ? TsPayloadReader.FLAG_RANDOM_ACCESS_INDICATOR + : 0; + tsPacketBuffer.skipBytes(adaptationFieldLength - 1 /* flags */); } // Read the payload. boolean wereTracksEnded = tracksEnded; if (shouldConsumePacketPayload(pid)) { tsPacketBuffer.setLimit(endOfPacket); - payloadReader.consume(tsPacketBuffer, payloadUnitStartIndicator); + payloadReader.consume(tsPacketBuffer, packetHeaderFlags); tsPacketBuffer.setLimit(limit); } if (mode != MODE_HLS && !wereTracksEnded && tracksEnded && inputLength != C.LENGTH_UNSET) { diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/TsPayloadReader.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/TsPayloadReader.java index 2ea25bb2e0..a034b05696 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/TsPayloadReader.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/TsPayloadReader.java @@ -15,12 +15,16 @@ */ package com.google.android.exoplayer2.extractor.ts; +import android.support.annotation.IntDef; import android.util.SparseArray; import com.google.android.exoplayer2.ParserException; import com.google.android.exoplayer2.extractor.ExtractorOutput; import com.google.android.exoplayer2.extractor.TrackOutput; import com.google.android.exoplayer2.util.ParsableByteArray; import com.google.android.exoplayer2.util.TimestampAdjuster; +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.Collections; import java.util.List; @@ -174,6 +178,29 @@ public interface TsPayloadReader { } + /** + * Contextual flags indicating the presence of indicators in the TS packet or PES packet headers. + */ + @Documented + @Retention(RetentionPolicy.SOURCE) + @IntDef( + flag = true, + value = { + FLAG_PAYLOAD_UNIT_START_INDICATOR, + FLAG_RANDOM_ACCESS_INDICATOR, + FLAG_DATA_ALIGNMENT_INDICATOR + }) + @interface Flags {} + + /** Indicates the presence of the payload_unit_start_indicator in the TS packet header. */ + int FLAG_PAYLOAD_UNIT_START_INDICATOR = 1; + /** + * Indicates the presence of the random_access_indicator in the TS packet header adaptation field. + */ + int FLAG_RANDOM_ACCESS_INDICATOR = 1 << 1; + /** Indicates the presence of the data_alignment_indicator in the PES header. */ + int FLAG_DATA_ALIGNMENT_INDICATOR = 1 << 2; + /** * Initializes the payload reader. * @@ -187,10 +214,10 @@ public interface TsPayloadReader { /** * Notifies the reader that a seek has occurred. - *
- * Following a call to this method, the data passed to the next invocation of - * {@link #consume(ParsableByteArray, boolean)} will not be a continuation of the data that was - * previously passed. Hence the reader should reset any internal state. + * + *
Following a call to this method, the data passed to the next invocation of {@link #consume}
+ * will not be a continuation of the data that was previously passed. Hence the reader should
+ * reset any internal state.
*/
void seek();
@@ -198,9 +225,8 @@ public interface TsPayloadReader {
* Consumes the payload of a TS packet.
*
* @param data The TS packet. The position will be set to the start of the payload.
- * @param payloadUnitStartIndicator Whether payloadUnitStartIndicator was set on the TS packet.
+ * @param flags See {@link Flags}.
* @throws ParserException If the payload could not be parsed.
*/
- void consume(ParsableByteArray data, boolean payloadUnitStartIndicator) throws ParserException;
-
+ void consume(ParsableByteArray data, @Flags int flags) throws ParserException;
}
diff --git a/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecInfo.java b/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecInfo.java
index 32f6bd5409..8cec75a66d 100644
--- a/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecInfo.java
+++ b/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecInfo.java
@@ -248,9 +248,15 @@ public final class MediaCodecInfo {
// If we don't know any better, we assume that the profile and level are supported.
return true;
}
+ int profile = codecProfileAndLevel.first;
+ int level = codecProfileAndLevel.second;
+ if (!isVideo && profile != CodecProfileLevel.AACObjectXHE) {
+ // Some devices/builds under-report audio capabilities, so assume support except for xHE-AAC
+ // which is not widely supported. See https://github.com/google/ExoPlayer/issues/5145.
+ return true;
+ }
for (CodecProfileLevel capabilities : getProfileLevels()) {
- if (capabilities.profile == codecProfileAndLevel.first
- && capabilities.level >= codecProfileAndLevel.second) {
+ if (capabilities.profile == profile && capabilities.level >= level) {
return true;
}
}
diff --git a/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.java b/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.java
index 86bbb330b7..6a813332e3 100644
--- a/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.java
+++ b/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.java
@@ -1622,7 +1622,8 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
*/
private static boolean codecNeedsEosFlushWorkaround(String name) {
return (Util.SDK_INT <= 23 && "OMX.google.vorbis.decoder".equals(name))
- || (Util.SDK_INT <= 19 && "hb2000".equals(Util.DEVICE)
+ || (Util.SDK_INT <= 19
+ && ("hb2000".equals(Util.DEVICE) || "stvm8".equals(Util.DEVICE))
&& ("OMX.amlogic.avc.decoder.awesome".equals(name)
|| "OMX.amlogic.avc.decoder.awesome.secure".equals(name)));
}
diff --git a/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecUtil.java b/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecUtil.java
index 893601a859..4d971d461e 100644
--- a/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecUtil.java
+++ b/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecUtil.java
@@ -318,7 +318,21 @@ public final class MediaCodecUtil {
}
// Work around https://github.com/google/ExoPlayer/issues/4519.
- if ("OMX.SEC.mp3.dec".equals(name) && "SM-T530".equals(Util.MODEL)) {
+ if ("OMX.SEC.mp3.dec".equals(name)
+ && (Util.MODEL.startsWith("GT-I9152")
+ || Util.MODEL.startsWith("GT-I9515")
+ || Util.MODEL.startsWith("GT-P5220")
+ || Util.MODEL.startsWith("GT-S7580")
+ || Util.MODEL.startsWith("SM-G350")
+ || Util.MODEL.startsWith("SM-G386")
+ || Util.MODEL.startsWith("SM-T231")
+ || Util.MODEL.startsWith("SM-T530"))) {
+ return false;
+ }
+ if ("OMX.brcm.audio.mp3.decoder".equals(name)
+ && (Util.MODEL.startsWith("GT-I9152")
+ || Util.MODEL.startsWith("GT-S7580")
+ || Util.MODEL.startsWith("SM-G350"))) {
return false;
}
diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/ClippingMediaSource.java b/library/core/src/main/java/com/google/android/exoplayer2/source/ClippingMediaSource.java
index 78e37c1869..3ed18049bf 100644
--- a/library/core/src/main/java/com/google/android/exoplayer2/source/ClippingMediaSource.java
+++ b/library/core/src/main/java/com/google/android/exoplayer2/source/ClippingMediaSource.java
@@ -216,6 +216,12 @@ public final class ClippingMediaSource extends CompositeMediaSource If the region defines an origin and extent, it is required that they're defined as
- * percentages of the viewport. Region declarations that define origin and extent in other formats
- * are unsupported, and null is returned.
+ * Supports both percentage and pixel defined regions. In case of pixel defined regions the
+ * passed {@code ttsExtent} is used as a reference window to convert the pixel values to
+ * fractions. In case of missing tts:extent the pixel defined regions can't be parsed, and null is
+ * returned.
*/
- private TtmlRegion parseRegionAttributes(XmlPullParser xmlParser, CellResolution cellResolution) {
+ private TtmlRegion parseRegionAttributes(
+ XmlPullParser xmlParser, CellResolution cellResolution, TtsExtent ttsExtent) {
String regionId = XmlPullParserUtil.getAttributeValue(xmlParser, TtmlNode.ATTR_ID);
if (regionId == null) {
return null;
@@ -270,13 +317,30 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder {
float position;
float line;
+
String regionOrigin = XmlPullParserUtil.getAttributeValue(xmlParser, TtmlNode.ATTR_TTS_ORIGIN);
if (regionOrigin != null) {
- Matcher originMatcher = PERCENTAGE_COORDINATES.matcher(regionOrigin);
- if (originMatcher.matches()) {
+ Matcher originPercentageMatcher = PERCENTAGE_COORDINATES.matcher(regionOrigin);
+ Matcher originPixelMatcher = PIXEL_COORDINATES.matcher(regionOrigin);
+ if (originPercentageMatcher.matches()) {
try {
- position = Float.parseFloat(originMatcher.group(1)) / 100f;
- line = Float.parseFloat(originMatcher.group(2)) / 100f;
+ position = Float.parseFloat(originPercentageMatcher.group(1)) / 100f;
+ line = Float.parseFloat(originPercentageMatcher.group(2)) / 100f;
+ } catch (NumberFormatException e) {
+ Log.w(TAG, "Ignoring region with malformed origin: " + regionOrigin);
+ return null;
+ }
+ } else if (originPixelMatcher.matches()) {
+ if (ttsExtent == null) {
+ Log.w(TAG, "Ignoring region with missing tts:extent: " + regionOrigin);
+ return null;
+ }
+ try {
+ int width = Integer.parseInt(originPixelMatcher.group(1));
+ int height = Integer.parseInt(originPixelMatcher.group(2));
+ // Convert pixel values to fractions.
+ position = width / (float) ttsExtent.width;
+ line = height / (float) ttsExtent.height;
} catch (NumberFormatException e) {
Log.w(TAG, "Ignoring region with malformed origin: " + regionOrigin);
return null;
@@ -299,11 +363,27 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder {
float height;
String regionExtent = XmlPullParserUtil.getAttributeValue(xmlParser, TtmlNode.ATTR_TTS_EXTENT);
if (regionExtent != null) {
- Matcher extentMatcher = PERCENTAGE_COORDINATES.matcher(regionExtent);
- if (extentMatcher.matches()) {
+ Matcher extentPercentageMatcher = PERCENTAGE_COORDINATES.matcher(regionExtent);
+ Matcher extentPixelMatcher = PIXEL_COORDINATES.matcher(regionExtent);
+ if (extentPercentageMatcher.matches()) {
try {
- width = Float.parseFloat(extentMatcher.group(1)) / 100f;
- height = Float.parseFloat(extentMatcher.group(2)) / 100f;
+ width = Float.parseFloat(extentPercentageMatcher.group(1)) / 100f;
+ height = Float.parseFloat(extentPercentageMatcher.group(2)) / 100f;
+ } catch (NumberFormatException e) {
+ Log.w(TAG, "Ignoring region with malformed extent: " + regionOrigin);
+ return null;
+ }
+ } else if (extentPixelMatcher.matches()) {
+ if (ttsExtent == null) {
+ Log.w(TAG, "Ignoring region with missing tts:extent: " + regionOrigin);
+ return null;
+ }
+ try {
+ int extentWidth = Integer.parseInt(extentPixelMatcher.group(1));
+ int extentHeight = Integer.parseInt(extentPixelMatcher.group(2));
+ // Convert pixel values to fractions.
+ width = extentWidth / (float) ttsExtent.width;
+ height = extentHeight / (float) ttsExtent.height;
} catch (NumberFormatException e) {
Log.w(TAG, "Ignoring region with malformed extent: " + regionOrigin);
return null;
@@ -457,6 +537,7 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder {
long startTime = C.TIME_UNSET;
long endTime = C.TIME_UNSET;
String regionId = TtmlNode.ANONYMOUS_REGION_ID;
+ String imageId = null;
String[] styleIds = null;
int attributeCount = parser.getAttributeCount();
TtmlStyle style = parseStyleAttributes(parser, null);
@@ -487,6 +568,13 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder {
regionId = value;
}
break;
+ case ATTR_IMAGE:
+ // Parse URI reference only if refers to an element in the same document (it must start
+ // with '#'). Resolving URIs from external sources is not supported.
+ if (value.startsWith("#")) {
+ imageId = value.substring(1);
+ }
+ break;
default:
// Do nothing.
break;
@@ -509,7 +597,8 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder {
endTime = parent.endTimeUs;
}
}
- return TtmlNode.buildNode(parser.getName(), startTime, endTime, style, styleIds, regionId);
+ return TtmlNode.buildNode(
+ parser.getName(), startTime, endTime, style, styleIds, regionId, imageId);
}
private static boolean isSupportedTag(String tag) {
@@ -525,9 +614,9 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder {
|| tag.equals(TtmlNode.TAG_LAYOUT)
|| tag.equals(TtmlNode.TAG_REGION)
|| tag.equals(TtmlNode.TAG_METADATA)
- || tag.equals(TtmlNode.TAG_SMPTE_IMAGE)
- || tag.equals(TtmlNode.TAG_SMPTE_DATA)
- || tag.equals(TtmlNode.TAG_SMPTE_INFORMATION);
+ || tag.equals(TtmlNode.TAG_IMAGE)
+ || tag.equals(TtmlNode.TAG_DATA)
+ || tag.equals(TtmlNode.TAG_INFORMATION);
}
private static void parseFontSize(String expression, TtmlStyle out) throws
@@ -651,4 +740,15 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder {
this.rows = rows;
}
}
+
+ /** Represents the tts:extent for a TTML file. */
+ private static final class TtsExtent {
+ final int width;
+ final int height;
+
+ TtsExtent(int width, int height) {
+ this.width = width;
+ this.height = height;
+ }
+ }
}
diff --git a/library/core/src/main/java/com/google/android/exoplayer2/text/ttml/TtmlNode.java b/library/core/src/main/java/com/google/android/exoplayer2/text/ttml/TtmlNode.java
index c8b9a59de4..020bbe201b 100644
--- a/library/core/src/main/java/com/google/android/exoplayer2/text/ttml/TtmlNode.java
+++ b/library/core/src/main/java/com/google/android/exoplayer2/text/ttml/TtmlNode.java
@@ -15,7 +15,12 @@
*/
package com.google.android.exoplayer2.text.ttml;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.support.annotation.Nullable;
import android.text.SpannableStringBuilder;
+import android.util.Base64;
+import android.util.Pair;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.text.Cue;
import com.google.android.exoplayer2.util.Assertions;
@@ -44,9 +49,9 @@ import java.util.TreeSet;
public static final String TAG_LAYOUT = "layout";
public static final String TAG_REGION = "region";
public static final String TAG_METADATA = "metadata";
- public static final String TAG_SMPTE_IMAGE = "smpte:image";
- public static final String TAG_SMPTE_DATA = "smpte:data";
- public static final String TAG_SMPTE_INFORMATION = "smpte:information";
+ public static final String TAG_IMAGE = "image";
+ public static final String TAG_DATA = "data";
+ public static final String TAG_INFORMATION = "information";
public static final String ANONYMOUS_REGION_ID = "";
public static final String ATTR_ID = "id";
@@ -75,34 +80,57 @@ import java.util.TreeSet;
public static final String START = "start";
public static final String END = "end";
- public final String tag;
- public final String text;
+ @Nullable public final String tag;
+ @Nullable public final String text;
public final boolean isTextNode;
public final long startTimeUs;
public final long endTimeUs;
- public final TtmlStyle style;
+ @Nullable public final TtmlStyle style;
+ @Nullable private final String[] styleIds;
public final String regionId;
+ @Nullable public final String imageId;
- private final String[] styleIds;
private final HashMap [1] (key1, id1) is removed from the in-memory index ... the index is not stored to disk ...
+ * [2] id1 is reused for a different key2 ... the index is not stored to disk ... [3] A file for
+ * key2 is partially written using a path corresponding to id1 ... the process is killed before
+ * the index is stored to disk ... [4] The index is read from disk, causing the partially written
+ * file to be incorrectly associated to key1
+ *
+ * By avoiding id reuse in step [2], a new id2 will be used instead. Step [4] will then delete
+ * the partially written file because the index does not contain an entry for id2.
+ *
+ * When the index is next stored (id -> null) entries are removed, making the ids eligible for
+ * reuse.
+ */
+ private final SparseArray<@NullableType String> idToKey;
+ /**
+ * Tracks ids for which (id -> null) entries are present in idToKey, so that they can be removed
+ * efficiently when the index is next stored.
+ */
+ private final SparseBooleanArray removedIds;
+
private final AtomicFile atomicFile;
private final Cipher cipher;
private final SecretKeySpec secretKeySpec;
@@ -104,6 +129,7 @@ import javax.crypto.spec.SecretKeySpec;
}
keyToContent = new HashMap<>();
idToKey = new SparseArray<>();
+ removedIds = new SparseBooleanArray();
atomicFile = new AtomicFile(new File(cacheDir, FILE_NAME));
}
@@ -124,6 +150,12 @@ import javax.crypto.spec.SecretKeySpec;
}
writeFile();
changed = false;
+ // Make ids that were removed since the index was last stored eligible for re-use.
+ int removedIdCount = removedIds.size();
+ for (int i = 0; i < removedIdCount; i++) {
+ idToKey.remove(removedIds.keyAt(i));
+ }
+ removedIds.clear();
}
/**
@@ -168,8 +200,11 @@ import javax.crypto.spec.SecretKeySpec;
CachedContent cachedContent = keyToContent.get(key);
if (cachedContent != null && cachedContent.isEmpty() && !cachedContent.isLocked()) {
keyToContent.remove(key);
- idToKey.remove(cachedContent.id);
changed = true;
+ // Keep an entry in idToKey to stop the id from being reused until the index is next stored.
+ idToKey.put(cachedContent.id, /* value= */ null);
+ // Track that the entry should be removed from idToKey when the index is next stored.
+ removedIds.put(cachedContent.id, /* value= */ true);
}
}
diff --git a/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/SimpleCache.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/SimpleCache.java
index ca2983c891..ab60be2b4b 100644
--- a/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/SimpleCache.java
+++ b/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/SimpleCache.java
@@ -146,13 +146,16 @@ public final class SimpleCache implements Cache {
}
@Override
- public synchronized void release() throws CacheException {
+ public synchronized void release() {
if (released) {
return;
}
listeners.clear();
+ removeStaleSpans();
try {
- removeStaleSpansAndCachedContents();
+ index.store();
+ } catch (CacheException e) {
+ Log.e(TAG, "Storing index file failed", e);
} finally {
unlockFolder(cacheDir);
released = true;
@@ -265,7 +268,7 @@ public final class SimpleCache implements Cache {
if (!cacheDir.exists()) {
// For some reason the cache directory doesn't exist. Make a best effort to create it.
cacheDir.mkdirs();
- removeStaleSpansAndCachedContents();
+ removeStaleSpans();
}
evictor.onStartFile(this, key, position, maxLength);
return SimpleCacheSpan.getCacheFile(
@@ -311,9 +314,9 @@ public final class SimpleCache implements Cache {
}
@Override
- public synchronized void removeSpan(CacheSpan span) throws CacheException {
+ public synchronized void removeSpan(CacheSpan span) {
Assertions.checkState(!released);
- removeSpan(span, true);
+ removeSpanInternal(span);
}
@Override
@@ -379,7 +382,7 @@ public final class SimpleCache implements Cache {
if (span.isCached && !span.file.exists()) {
// The file has been deleted from under us. It's likely that other files will have been
// deleted too, so scan the whole in-memory representation.
- removeStaleSpansAndCachedContents();
+ removeStaleSpans();
continue;
}
return span;
@@ -431,27 +434,21 @@ public final class SimpleCache implements Cache {
notifySpanAdded(span);
}
- private void removeSpan(CacheSpan span, boolean removeEmptyCachedContent) throws CacheException {
+ private void removeSpanInternal(CacheSpan span) {
CachedContent cachedContent = index.get(span.key);
if (cachedContent == null || !cachedContent.removeSpan(span)) {
return;
}
totalSpace -= span.length;
- try {
- if (removeEmptyCachedContent) {
- index.maybeRemove(cachedContent.key);
- index.store();
- }
- } finally {
- notifySpanRemoved(span);
- }
+ index.maybeRemove(cachedContent.key);
+ notifySpanRemoved(span);
}
/**
* Scans all of the cached spans in the in-memory representation, removing any for which files no
* longer exist.
*/
- private void removeStaleSpansAndCachedContents() throws CacheException {
+ private void removeStaleSpans() {
ArrayList
- * If true is returned then we explicitly disable the feature.
+ * Returns whether the device is known to do post processing by default that isn't compatible with
+ * ExoPlayer.
*
- * @return True if the device is known to enable frame-rate conversion logic that negatively
- * impacts ExoPlayer. False otherwise.
+ * @return Whether the device is known to do post processing by default that isn't compatible with
+ * ExoPlayer.
*/
- private static boolean deviceNeedsAutoFrcWorkaround() {
- // nVidia Shield prior to M tries to adjust the playback rate to better map the frame-rate of
+ private static boolean deviceNeedsNoPostProcessWorkaround() {
+ // Nvidia devices prior to M try to adjust the playback rate to better map the frame-rate of
// content to the refresh rate of the display. For example playback of 23.976fps content is
// adjusted to play at 1.001x speed when the output display is 60Hz. Unfortunately the
// implementation causes ExoPlayer's reported playback position to drift out of sync. Captions
- // also lose sync [Internal: b/26453592].
- return Util.SDK_INT <= 22 && "foster".equals(Util.DEVICE) && "NVIDIA".equals(Util.MANUFACTURER);
+ // also lose sync [Internal: b/26453592]. Even after M, the devices may apply post processing
+ // operations that can modify frame output timestamps, which is incompatible with ExoPlayer's
+ // logic for skipping decode-only frames.
+ return "NVIDIA".equals(Util.MANUFACTURER);
}
/*
@@ -1296,163 +1297,171 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
* incorrectly.
*/
protected boolean codecNeedsSetOutputSurfaceWorkaround(String name) {
- if (Util.SDK_INT >= 27 || name.startsWith("OMX.google")) {
- // Devices running API level 27 or later should also be unaffected. Google OMX decoders are
- // not known to have this issue on any API level.
+ if (name.startsWith("OMX.google")) {
+ // Google OMX decoders are not known to have this issue on any API level.
return false;
}
- // Work around:
- // https://github.com/google/ExoPlayer/issues/3236,
- // https://github.com/google/ExoPlayer/issues/3355,
- // https://github.com/google/ExoPlayer/issues/3439,
- // https://github.com/google/ExoPlayer/issues/3724,
- // https://github.com/google/ExoPlayer/issues/3835,
- // https://github.com/google/ExoPlayer/issues/4006,
- // https://github.com/google/ExoPlayer/issues/4084,
- // https://github.com/google/ExoPlayer/issues/4104,
- // https://github.com/google/ExoPlayer/issues/4134,
- // https://github.com/google/ExoPlayer/issues/4315,
- // https://github.com/google/ExoPlayer/issues/4419,
- // https://github.com/google/ExoPlayer/issues/4460,
- // https://github.com/google/ExoPlayer/issues/4468.
synchronized (MediaCodecVideoRenderer.class) {
if (!evaluatedDeviceNeedsSetOutputSurfaceWorkaround) {
- switch (Util.DEVICE) {
- case "1601":
- case "1713":
- case "1714":
- case "A10-70F":
- case "A1601":
- case "A2016a40":
- case "A7000-a":
- case "A7000plus":
- case "A7010a48":
- case "A7020a48":
- case "AquaPowerM":
- case "ASUS_X00AD_2":
- case "Aura_Note_2":
- case "BLACK-1X":
- case "BRAVIA_ATV2":
- case "C1":
- case "ComioS1":
- case "CP8676_I02":
- case "CPH1609":
- case "CPY83_I00":
- case "cv1":
- case "cv3":
- case "deb":
- case "E5643":
- case "ELUGA_A3_Pro":
- case "ELUGA_Note":
- case "ELUGA_Prim":
- case "ELUGA_Ray_X":
- case "EverStar_S":
- case "F3111":
- case "F3113":
- case "F3116":
- case "F3211":
- case "F3213":
- case "F3215":
- case "F3311":
- case "flo":
- case "GiONEE_CBL7513":
- case "GiONEE_GBL7319":
- case "GIONEE_GBL7360":
- case "GIONEE_SWW1609":
- case "GIONEE_SWW1627":
- case "GIONEE_SWW1631":
- case "GIONEE_WBL5708":
- case "GIONEE_WBL7365":
- case "GIONEE_WBL7519":
- case "griffin":
- case "htc_e56ml_dtul":
- case "hwALE-H":
- case "HWBLN-H":
- case "HWCAM-H":
- case "HWVNS-H":
- case "i9031":
- case "iball8735_9806":
- case "Infinix-X572":
- case "iris60":
- case "itel_S41":
- case "j2xlteins":
- case "JGZ":
- case "K50a40":
- case "kate":
- case "le_x6":
- case "LS-5017":
- case "M5c":
- case "manning":
- case "marino_f":
- case "MEIZU_M5":
- case "mh":
- case "mido":
- case "MX6":
- case "namath":
- case "nicklaus_f":
- case "NX541J":
- case "NX573J":
- case "OnePlus5T":
- case "p212":
- case "P681":
- case "P85":
- case "panell_d":
- case "panell_dl":
- case "panell_ds":
- case "panell_dt":
- case "PB2-670M":
- case "PGN528":
- case "PGN610":
- case "PGN611":
- case "Phantom6":
- case "Pixi4-7_3G":
- case "Pixi5-10_4G":
- case "PLE":
- case "PRO7S":
- case "Q350":
- case "Q4260":
- case "Q427":
- case "Q4310":
- case "Q5":
- case "QM16XE_U":
- case "QX1":
- case "santoni":
- case "Slate_Pro":
- case "SVP-DTV15":
- case "s905x018":
- case "taido_row":
- case "TB3-730F":
- case "TB3-730X":
- case "TB3-850F":
- case "TB3-850M":
- case "tcl_eu":
- case "V1":
- case "V23GB":
- case "V5":
- case "vernee_M5":
- case "watson":
- case "whyred":
- case "woods_f":
- case "woods_fn":
- case "X3_HK":
- case "XE2X":
- case "XT1663":
- case "Z12_PRO":
- case "Z80":
- deviceNeedsSetOutputSurfaceWorkaround = true;
- break;
- default:
- // Do nothing.
- break;
- }
- switch (Util.MODEL) {
- case "AFTA":
- case "AFTN":
- deviceNeedsSetOutputSurfaceWorkaround = true;
- break;
- default:
- // Do nothing.
- break;
+ if (Util.SDK_INT <= 27 && "dangal".equals(Util.DEVICE)) {
+ // Dangal is affected on API level 27: https://github.com/google/ExoPlayer/issues/5169.
+ deviceNeedsSetOutputSurfaceWorkaround = true;
+ } else if (Util.SDK_INT >= 27) {
+ // In general, devices running API level 27 or later should be unaffected. Do nothing.
+ } else {
+ // Enable the workaround on a per-device basis. Works around:
+ // https://github.com/google/ExoPlayer/issues/3236,
+ // https://github.com/google/ExoPlayer/issues/3355,
+ // https://github.com/google/ExoPlayer/issues/3439,
+ // https://github.com/google/ExoPlayer/issues/3724,
+ // https://github.com/google/ExoPlayer/issues/3835,
+ // https://github.com/google/ExoPlayer/issues/4006,
+ // https://github.com/google/ExoPlayer/issues/4084,
+ // https://github.com/google/ExoPlayer/issues/4104,
+ // https://github.com/google/ExoPlayer/issues/4134,
+ // https://github.com/google/ExoPlayer/issues/4315,
+ // https://github.com/google/ExoPlayer/issues/4419,
+ // https://github.com/google/ExoPlayer/issues/4460,
+ // https://github.com/google/ExoPlayer/issues/4468.
+ switch (Util.DEVICE) {
+ case "1601":
+ case "1713":
+ case "1714":
+ case "A10-70F":
+ case "A1601":
+ case "A2016a40":
+ case "A7000-a":
+ case "A7000plus":
+ case "A7010a48":
+ case "A7020a48":
+ case "AquaPowerM":
+ case "ASUS_X00AD_2":
+ case "Aura_Note_2":
+ case "BLACK-1X":
+ case "BRAVIA_ATV2":
+ case "BRAVIA_ATV3_4K":
+ case "C1":
+ case "ComioS1":
+ case "CP8676_I02":
+ case "CPH1609":
+ case "CPY83_I00":
+ case "cv1":
+ case "cv3":
+ case "deb":
+ case "E5643":
+ case "ELUGA_A3_Pro":
+ case "ELUGA_Note":
+ case "ELUGA_Prim":
+ case "ELUGA_Ray_X":
+ case "EverStar_S":
+ case "F3111":
+ case "F3113":
+ case "F3116":
+ case "F3211":
+ case "F3213":
+ case "F3215":
+ case "F3311":
+ case "flo":
+ case "fugu":
+ case "GiONEE_CBL7513":
+ case "GiONEE_GBL7319":
+ case "GIONEE_GBL7360":
+ case "GIONEE_SWW1609":
+ case "GIONEE_SWW1627":
+ case "GIONEE_SWW1631":
+ case "GIONEE_WBL5708":
+ case "GIONEE_WBL7365":
+ case "GIONEE_WBL7519":
+ case "griffin":
+ case "htc_e56ml_dtul":
+ case "hwALE-H":
+ case "HWBLN-H":
+ case "HWCAM-H":
+ case "HWVNS-H":
+ case "i9031":
+ case "iball8735_9806":
+ case "Infinix-X572":
+ case "iris60":
+ case "itel_S41":
+ case "j2xlteins":
+ case "JGZ":
+ case "K50a40":
+ case "kate":
+ case "le_x6":
+ case "LS-5017":
+ case "M5c":
+ case "manning":
+ case "marino_f":
+ case "MEIZU_M5":
+ case "mh":
+ case "mido":
+ case "MX6":
+ case "namath":
+ case "nicklaus_f":
+ case "NX541J":
+ case "NX573J":
+ case "OnePlus5T":
+ case "p212":
+ case "P681":
+ case "P85":
+ case "panell_d":
+ case "panell_dl":
+ case "panell_ds":
+ case "panell_dt":
+ case "PB2-670M":
+ case "PGN528":
+ case "PGN610":
+ case "PGN611":
+ case "Phantom6":
+ case "Pixi4-7_3G":
+ case "Pixi5-10_4G":
+ case "PLE":
+ case "PRO7S":
+ case "Q350":
+ case "Q4260":
+ case "Q427":
+ case "Q4310":
+ case "Q5":
+ case "QM16XE_U":
+ case "QX1":
+ case "santoni":
+ case "Slate_Pro":
+ case "SVP-DTV15":
+ case "s905x018":
+ case "taido_row":
+ case "TB3-730F":
+ case "TB3-730X":
+ case "TB3-850F":
+ case "TB3-850M":
+ case "tcl_eu":
+ case "V1":
+ case "V23GB":
+ case "V5":
+ case "vernee_M5":
+ case "watson":
+ case "whyred":
+ case "woods_f":
+ case "woods_fn":
+ case "X3_HK":
+ case "XE2X":
+ case "XT1663":
+ case "Z12_PRO":
+ case "Z80":
+ deviceNeedsSetOutputSurfaceWorkaround = true;
+ break;
+ default:
+ // Do nothing.
+ break;
+ }
+ switch (Util.MODEL) {
+ case "AFTA":
+ case "AFTN":
+ deviceNeedsSetOutputSurfaceWorkaround = true;
+ break;
+ default:
+ // Do nothing.
+ break;
+ }
}
evaluatedDeviceNeedsSetOutputSurfaceWorkaround = true;
}
diff --git a/library/core/src/main/java/com/google/android/exoplayer2/video/VideoRendererEventListener.java b/library/core/src/main/java/com/google/android/exoplayer2/video/VideoRendererEventListener.java
index 617211afb7..7d78ba03c7 100644
--- a/library/core/src/main/java/com/google/android/exoplayer2/video/VideoRendererEventListener.java
+++ b/library/core/src/main/java/com/google/android/exoplayer2/video/VideoRendererEventListener.java
@@ -26,7 +26,8 @@ import com.google.android.exoplayer2.decoder.DecoderCounters;
import com.google.android.exoplayer2.util.Assertions;
/**
- * Listener of video {@link Renderer} events.
+ * Listener of video {@link Renderer} events. All methods have no-op default implementations to
+ * allow selective overrides.
*/
public interface VideoRendererEventListener {
@@ -36,7 +37,7 @@ public interface VideoRendererEventListener {
* @param counters {@link DecoderCounters} that will be updated by the renderer for as long as it
* remains enabled.
*/
- void onVideoEnabled(DecoderCounters counters);
+ default void onVideoEnabled(DecoderCounters counters) {}
/**
* Called when a decoder is created.
@@ -46,15 +47,15 @@ public interface VideoRendererEventListener {
* finished.
* @param initializationDurationMs The time taken to initialize the decoder in milliseconds.
*/
- void onVideoDecoderInitialized(String decoderName, long initializedTimestampMs,
- long initializationDurationMs);
+ default void onVideoDecoderInitialized(
+ String decoderName, long initializedTimestampMs, long initializationDurationMs) {}
/**
* Called when the format of the media being consumed by the renderer changes.
*
* @param format The new format.
*/
- void onVideoInputFormatChanged(Format format);
+ default void onVideoInputFormatChanged(Format format) {}
/**
* Called to report the number of frames dropped by the renderer. Dropped frames are reported
@@ -62,12 +63,11 @@ public interface VideoRendererEventListener {
* reaches a specified threshold whilst the renderer is started.
*
* @param count The number of dropped frames.
- * @param elapsedMs The duration in milliseconds over which the frames were dropped. This
- * duration is timed from when the renderer was started or from when dropped frames were
- * last reported (whichever was more recent), and not from when the first of the reported
- * drops occurred.
+ * @param elapsedMs The duration in milliseconds over which the frames were dropped. This duration
+ * is timed from when the renderer was started or from when dropped frames were last reported
+ * (whichever was more recent), and not from when the first of the reported drops occurred.
*/
- void onDroppedFrames(int count, long elapsedMs);
+ default void onDroppedFrames(int count, long elapsedMs) {}
/**
* Called before a frame is rendered for the first time since setting the surface, and each time
@@ -82,12 +82,12 @@ public interface VideoRendererEventListener {
* this is not possible. Applications that use {@link TextureView} can apply the rotation by
* calling {@link TextureView#setTransform}. Applications that do not expect to encounter
* rotated videos can safely ignore this parameter.
- * @param pixelWidthHeightRatio The width to height ratio of each pixel. For the normal case
- * of square pixels this will be equal to 1.0. Different values are indicative of anamorphic
+ * @param pixelWidthHeightRatio The width to height ratio of each pixel. For the normal case of
+ * square pixels this will be equal to 1.0. Different values are indicative of anamorphic
* content.
*/
- void onVideoSizeChanged(int width, int height, int unappliedRotationDegrees,
- float pixelWidthHeightRatio);
+ default void onVideoSizeChanged(
+ int width, int height, int unappliedRotationDegrees, float pixelWidthHeightRatio) {}
/**
* Called when a frame is rendered for the first time since setting the surface, and when a frame
@@ -96,14 +96,14 @@ public interface VideoRendererEventListener {
* @param surface The {@link Surface} to which a first frame has been rendered, or {@code null} if
* the renderer renders to something that isn't a {@link Surface}.
*/
- void onRenderedFirstFrame(@Nullable Surface surface);
+ default void onRenderedFirstFrame(@Nullable Surface surface) {}
/**
* Called when the renderer is disabled.
*
* @param counters {@link DecoderCounters} that were updated by the renderer.
*/
- void onVideoDisabled(DecoderCounters counters);
+ default void onVideoDisabled(DecoderCounters counters) {}
/**
* Dispatches events to a {@link VideoRendererEventListener}.
diff --git a/library/core/src/test/assets/mp3/play-trimmed.mp3.0.dump b/library/core/src/test/assets/mp3/play-trimmed.mp3.0.dump
index 96b0cd259c..d4df3ffeba 100644
--- a/library/core/src/test/assets/mp3/play-trimmed.mp3.0.dump
+++ b/library/core/src/test/assets/mp3/play-trimmed.mp3.0.dump
@@ -1,6 +1,6 @@
seekMap:
isSeekable = true
- duration = 26125
+ duration = 26122
getPosition(0) = [[timeUs=0, position=0]]
numberOfTracks = 1
track 0:
diff --git a/library/core/src/test/assets/mp3/play-trimmed.mp3.1.dump b/library/core/src/test/assets/mp3/play-trimmed.mp3.1.dump
index 96b0cd259c..d4df3ffeba 100644
--- a/library/core/src/test/assets/mp3/play-trimmed.mp3.1.dump
+++ b/library/core/src/test/assets/mp3/play-trimmed.mp3.1.dump
@@ -1,6 +1,6 @@
seekMap:
isSeekable = true
- duration = 26125
+ duration = 26122
getPosition(0) = [[timeUs=0, position=0]]
numberOfTracks = 1
track 0:
diff --git a/library/core/src/test/assets/mp3/play-trimmed.mp3.2.dump b/library/core/src/test/assets/mp3/play-trimmed.mp3.2.dump
index 96b0cd259c..d4df3ffeba 100644
--- a/library/core/src/test/assets/mp3/play-trimmed.mp3.2.dump
+++ b/library/core/src/test/assets/mp3/play-trimmed.mp3.2.dump
@@ -1,6 +1,6 @@
seekMap:
isSeekable = true
- duration = 26125
+ duration = 26122
getPosition(0) = [[timeUs=0, position=0]]
numberOfTracks = 1
track 0:
diff --git a/library/core/src/test/assets/mp3/play-trimmed.mp3.3.dump b/library/core/src/test/assets/mp3/play-trimmed.mp3.3.dump
index 96b0cd259c..d4df3ffeba 100644
--- a/library/core/src/test/assets/mp3/play-trimmed.mp3.3.dump
+++ b/library/core/src/test/assets/mp3/play-trimmed.mp3.3.dump
@@ -1,6 +1,6 @@
seekMap:
isSeekable = true
- duration = 26125
+ duration = 26122
getPosition(0) = [[timeUs=0, position=0]]
numberOfTracks = 1
track 0:
diff --git a/library/core/src/test/assets/ttml/bitmap_percentage_region.xml b/library/core/src/test/assets/ttml/bitmap_percentage_region.xml
new file mode 100644
index 0000000000..9631650178
--- /dev/null
+++ b/library/core/src/test/assets/ttml/bitmap_percentage_region.xml
@@ -0,0 +1,26 @@
+
+
+
*
@@ -271,13 +271,13 @@ public class PlayerView extends FrameLayout {
private static final int SURFACE_TYPE_MONO360_VIEW = 3;
// LINT.ThenChange(../../../../../../res/values/attrs.xml)
- private final AspectRatioFrameLayout contentFrame;
+ @Nullable private final AspectRatioFrameLayout contentFrame;
private final View shutterView;
- private final View surfaceView;
+ @Nullable private final View surfaceView;
private final ImageView artworkView;
private final SubtitleView subtitleView;
- private final @Nullable View bufferingView;
- private final @Nullable TextView errorMessageView;
+ @Nullable private final View bufferingView;
+ @Nullable private final TextView errorMessageView;
private final PlayerControlView controller;
private final ComponentListener componentListener;
private final FrameLayout overlayFrameLayout;
@@ -285,11 +285,11 @@ public class PlayerView extends FrameLayout {
private Player player;
private boolean useController;
private boolean useArtwork;
- private @Nullable Drawable defaultArtwork;
+ @Nullable private Drawable defaultArtwork;
private @ShowBuffering int showBuffering;
private boolean keepContentOnPlayerReset;
- private @Nullable ErrorMessageProvider super ExoPlaybackException> errorMessageProvider;
- private @Nullable CharSequence customErrorMessage;
+ @Nullable private ErrorMessageProvider super ExoPlaybackException> errorMessageProvider;
+ @Nullable private CharSequence customErrorMessage;
private int controllerShowTimeoutMs;
private boolean controllerAutoShow;
private boolean controllerHideDuringAds;
@@ -474,9 +474,7 @@ public class PlayerView extends FrameLayout {
* @param newPlayerView The new view to attach to the player.
*/
public static void switchTargetView(
- @NonNull Player player,
- @Nullable PlayerView oldPlayerView,
- @Nullable PlayerView newPlayerView) {
+ Player player, @Nullable PlayerView oldPlayerView, @Nullable PlayerView newPlayerView) {
if (oldPlayerView == newPlayerView) {
return;
}
@@ -1074,6 +1072,26 @@ public class PlayerView extends FrameLayout {
}
}
+ /**
+ * Called when there's a change in the aspect ratio of the content being displayed. The default
+ * implementation sets the aspect ratio of the content frame to that of the content, unless the
+ * content view is a {@link SphericalSurfaceView} in which case the frame's aspect ratio is
+ * cleared.
+ *
+ * @param contentAspectRatio The aspect ratio of the content.
+ * @param contentFrame The content frame, or {@code null}.
+ * @param contentView The view that holds the content being displayed, or {@code null}.
+ */
+ protected void onContentAspectRatioChanged(
+ float contentAspectRatio,
+ @Nullable AspectRatioFrameLayout contentFrame,
+ @Nullable View contentView) {
+ if (contentFrame != null) {
+ contentFrame.setAspectRatio(
+ contentView instanceof SphericalSurfaceView ? 0 : contentAspectRatio);
+ }
+ }
+
private boolean toggleControllerVisibility() {
if (!useController || player == null) {
return false;
@@ -1187,9 +1205,8 @@ public class PlayerView extends FrameLayout {
int drawableWidth = drawable.getIntrinsicWidth();
int drawableHeight = drawable.getIntrinsicHeight();
if (drawableWidth > 0 && drawableHeight > 0) {
- if (contentFrame != null) {
- contentFrame.setAspectRatio((float) drawableWidth / drawableHeight);
- }
+ float artworkAspectRatio = (float) drawableWidth / drawableHeight;
+ onContentAspectRatioChanged(artworkAspectRatio, contentFrame, artworkView);
artworkView.setImageDrawable(drawable);
artworkView.setVisibility(VISIBLE);
return true;
@@ -1322,9 +1339,6 @@ public class PlayerView extends FrameLayout {
@Override
public void onVideoSizeChanged(
int width, int height, int unappliedRotationDegrees, float pixelWidthHeightRatio) {
- if (contentFrame == null) {
- return;
- }
float videoAspectRatio =
(height == 0 || width == 0) ? 1 : (width * pixelWidthHeightRatio) / height;
@@ -1345,11 +1359,9 @@ public class PlayerView extends FrameLayout {
surfaceView.addOnLayoutChangeListener(this);
}
applyTextureViewRotation((TextureView) surfaceView, textureViewRotation);
- } else if (surfaceView instanceof SphericalSurfaceView) {
- videoAspectRatio = 0;
}
- contentFrame.setAspectRatio(videoAspectRatio);
+ onContentAspectRatioChanged(videoAspectRatio, contentFrame, surfaceView);
}
@Override
diff --git a/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeMediaSource.java b/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeMediaSource.java
index 2fca4f42c7..1f0c0c1a40 100644
--- a/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeMediaSource.java
+++ b/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeMediaSource.java
@@ -88,6 +88,13 @@ public class FakeMediaSource extends BaseMediaSource {
this.trackGroupArray = trackGroupArray;
}
+ @Override
+ @Nullable
+ public Object getTag() {
+ boolean hasTimeline = timeline != null && !timeline.isEmpty();
+ return hasTimeline ? timeline.getWindow(0, new Timeline.Window()).tag : null;
+ }
+
@Override
public synchronized void prepareSourceInternal(
ExoPlayer player,
diff --git a/testutils_robolectric/src/main/java/com/google/android/exoplayer2/testutil/StubExoPlayer.java b/testutils_robolectric/src/main/java/com/google/android/exoplayer2/testutil/StubExoPlayer.java
index 156b573df8..724ed366bc 100644
--- a/testutils_robolectric/src/main/java/com/google/android/exoplayer2/testutil/StubExoPlayer.java
+++ b/testutils_robolectric/src/main/java/com/google/android/exoplayer2/testutil/StubExoPlayer.java
@@ -49,6 +49,11 @@ public abstract class StubExoPlayer extends BasePlayer implements ExoPlayer {
throw new UnsupportedOperationException();
}
+ @Override
+ public MetadataComponent getMetadataComponent() {
+ throw new UnsupportedOperationException();
+ }
+
@Override
public Looper getPlaybackLooper() {
throw new UnsupportedOperationException();