From 2cf8553049888d4fac70fba3777fb8a35cd8f5e9 Mon Sep 17 00:00:00 2001 From: Romain Caire Date: Sat, 14 Jan 2017 14:47:21 +0100 Subject: [PATCH 001/119] opus: use fixed max size in Opus decoding as per documentation: If this is less than the maximum packet duration (120 ms; 5760 for 48kHz), this function will not be capable of decoding some packets. --- extensions/opus/src/main/jni/opus_jni.cc | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/extensions/opus/src/main/jni/opus_jni.cc b/extensions/opus/src/main/jni/opus_jni.cc index 48c1bd5e6d..0ba9675484 100644 --- a/extensions/opus/src/main/jni/opus_jni.cc +++ b/extensions/opus/src/main/jni/opus_jni.cc @@ -59,6 +59,7 @@ jint JNI_OnLoad(JavaVM* vm, void* reserved) { } static const int kBytesPerSample = 2; // opus fixed point uses 16 bit samples. +static const int kMaxOpusOutputPacketSizeSamples = 960 * 6;// Maximum packet size used in Xiph's opusdec. static int channelCount; static int errorCode; @@ -101,7 +102,7 @@ DECODER_FUNC(jint, opusDecode, jlong jDecoder, jlong jTimeUs, const int32_t inputSampleCount = opus_packet_get_nb_samples(inputBuffer, inputSize, sampleRate); - const jint outputSize = inputSampleCount * kBytesPerSample * channelCount; + const jint outputSize = kMaxOpusOutputPacketSizeSamples * kBytesPerSample * channelCount; env->CallObjectMethod(jOutputBuffer, outputBufferInit, jTimeUs, outputSize); const jobject jOutputBufferData = env->CallObjectMethod(jOutputBuffer, @@ -110,7 +111,7 @@ DECODER_FUNC(jint, opusDecode, jlong jDecoder, jlong jTimeUs, int16_t* outputBufferData = reinterpret_cast( env->GetDirectBufferAddress(jOutputBufferData)); int sampleCount = opus_multistream_decode(decoder, inputBuffer, inputSize, - outputBufferData, outputSize, 0); + outputBufferData, kMaxOpusOutputPacketSizeSamples, 0); // record error code errorCode = (sampleCount < 0) ? sampleCount : 0; return (sampleCount < 0) ? sampleCount From f2de393d8395a42f3513b7a96fa76541f588d705 Mon Sep 17 00:00:00 2001 From: olly Date: Fri, 17 Mar 2017 07:20:17 -0700 Subject: [PATCH 002/119] Correctly handle stream replacement in text renderer ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=150436867 --- .../android/exoplayer2/text/TextRenderer.java | 87 +++++++++++++++---- 1 file changed, 70 insertions(+), 17 deletions(-) diff --git a/library/src/main/java/com/google/android/exoplayer2/text/TextRenderer.java b/library/src/main/java/com/google/android/exoplayer2/text/TextRenderer.java index a7e05a010a..2f07fe5294 100644 --- a/library/src/main/java/com/google/android/exoplayer2/text/TextRenderer.java +++ b/library/src/main/java/com/google/android/exoplayer2/text/TextRenderer.java @@ -19,6 +19,7 @@ import android.os.Handler; import android.os.Handler.Callback; import android.os.Looper; import android.os.Message; +import android.support.annotation.IntDef; import com.google.android.exoplayer2.BaseRenderer; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.ExoPlaybackException; @@ -26,6 +27,8 @@ import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.FormatHolder; import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.MimeTypes; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.Collections; import java.util.List; @@ -52,6 +55,27 @@ public final class TextRenderer extends BaseRenderer implements Callback { } + @Retention(RetentionPolicy.SOURCE) + @IntDef({REPLACEMENT_STATE_NONE, REPLACEMENT_STATE_SIGNAL_END_OF_STREAM, + REPLACEMENT_STATE_WAIT_END_OF_STREAM}) + private @interface ReplacementState {} + /** + * The decoder does not need to be replaced. + */ + private static final int REPLACEMENT_STATE_NONE = 0; + /** + * The decoder needs to be replaced, but we haven't yet signaled an end of stream to the existing + * decoder. We need to do so in order to ensure that it outputs any remaining buffers before we + * release it. + */ + private static final int REPLACEMENT_STATE_SIGNAL_END_OF_STREAM = 1; + /** + * The decoder needs to be replaced, and we've signaled an end of stream to the existing decoder. + * We're waiting for the decoder to output an end of stream signal to indicate that it has output + * any remaining buffers before we release it. + */ + private static final int REPLACEMENT_STATE_WAIT_END_OF_STREAM = 2; + private static final int MSG_UPDATE_OUTPUT = 0; private final Handler outputHandler; @@ -61,6 +85,8 @@ public final class TextRenderer extends BaseRenderer implements Callback { private boolean inputStreamEnded; private boolean outputStreamEnded; + @ReplacementState private int decoderReplacementState; + private Format streamFormat; private SubtitleDecoder decoder; private SubtitleInputBuffer nextInputBuffer; private SubtitleOutputBuffer subtitle; @@ -105,20 +131,25 @@ public final class TextRenderer extends BaseRenderer implements Callback { @Override protected void onStreamChanged(Format[] formats) throws ExoPlaybackException { + streamFormat = formats[0]; if (decoder != null) { - decoder.release(); - nextInputBuffer = null; + decoderReplacementState = REPLACEMENT_STATE_SIGNAL_END_OF_STREAM; + } else { + decoder = decoderFactory.createDecoder(streamFormat); } - decoder = decoderFactory.createDecoder(formats[0]); } @Override protected void onPositionReset(long positionUs, boolean joining) { clearOutput(); - resetBuffers(); - decoder.flush(); inputStreamEnded = false; outputStreamEnded = false; + if (decoderReplacementState != REPLACEMENT_STATE_NONE) { + replaceDecoder(); + } else { + releaseBuffers(); + decoder.flush(); + } } @Override @@ -155,13 +186,12 @@ public final class TextRenderer extends BaseRenderer implements Callback { if (nextSubtitle != null) { if (nextSubtitle.isEndOfStream()) { if (!textRendererNeedsUpdate && getNextEventTime() == Long.MAX_VALUE) { - if (subtitle != null) { - subtitle.release(); - subtitle = null; + if (decoderReplacementState == REPLACEMENT_STATE_WAIT_END_OF_STREAM) { + replaceDecoder(); + } else { + releaseBuffers(); + outputStreamEnded = true; } - nextSubtitle.release(); - nextSubtitle = null; - outputStreamEnded = true; } } else if (nextSubtitle.timeUs <= positionUs) { // Advance to the next subtitle. Sync the next event index and trigger an update. @@ -180,6 +210,10 @@ public final class TextRenderer extends BaseRenderer implements Callback { updateOutput(subtitle.getCues(positionUs)); } + if (decoderReplacementState == REPLACEMENT_STATE_WAIT_END_OF_STREAM) { + return; + } + try { while (!inputStreamEnded) { if (nextInputBuffer == null) { @@ -188,6 +222,13 @@ public final class TextRenderer extends BaseRenderer implements Callback { return; } } + if (decoderReplacementState == REPLACEMENT_STATE_SIGNAL_END_OF_STREAM) { + nextInputBuffer.setFlags(C.BUFFER_FLAG_END_OF_STREAM); + decoder.queueInputBuffer(nextInputBuffer); + nextInputBuffer = null; + decoderReplacementState = REPLACEMENT_STATE_WAIT_END_OF_STREAM; + return; + } // Try and read the next subtitle from the source. int result = readSource(formatHolder, nextInputBuffer, false); if (result == C.RESULT_BUFFER_READ) { @@ -200,7 +241,7 @@ public final class TextRenderer extends BaseRenderer implements Callback { decoder.queueInputBuffer(nextInputBuffer); nextInputBuffer = null; } else if (result == C.RESULT_NOTHING_READ) { - break; + return; } } } catch (SubtitleDecoderException e) { @@ -210,10 +251,9 @@ public final class TextRenderer extends BaseRenderer implements Callback { @Override protected void onDisabled() { + streamFormat = null; clearOutput(); - resetBuffers(); - decoder.release(); - decoder = null; + releaseDecoder(); super.onDisabled(); } @@ -229,7 +269,7 @@ public final class TextRenderer extends BaseRenderer implements Callback { return true; } - private void resetBuffers() { + private void releaseBuffers() { nextInputBuffer = null; nextSubtitleEventIndex = C.INDEX_UNSET; if (subtitle != null) { @@ -242,6 +282,18 @@ public final class TextRenderer extends BaseRenderer implements Callback { } } + private void releaseDecoder() { + releaseBuffers(); + decoder.release(); + decoder = null; + decoderReplacementState = REPLACEMENT_STATE_NONE; + } + + private void replaceDecoder() { + releaseDecoder(); + decoder = decoderFactory.createDecoder(streamFormat); + } + private long getNextEventTime() { return ((nextSubtitleEventIndex == C.INDEX_UNSET) || (nextSubtitleEventIndex >= subtitle.getEventTimeCount())) ? Long.MAX_VALUE @@ -267,8 +319,9 @@ public final class TextRenderer extends BaseRenderer implements Callback { case MSG_UPDATE_OUTPUT: invokeUpdateOutputInternal((List) msg.obj); return true; + default: + throw new IllegalStateException(); } - return false; } private void invokeUpdateOutputInternal(List cues) { From ce55d1a712f686ba1749804302eca141d952a6dc Mon Sep 17 00:00:00 2001 From: olly Date: Fri, 17 Mar 2017 10:36:15 -0700 Subject: [PATCH 003/119] Modularize ExoPlayer steps 1 + 2. 1. Move entire library from v2/library/ to v2/library/core 2. Add v2/library/all that depends on v2/library/core Issue: #2139 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=150455693 --- extensions/cronet/build.gradle | 2 +- extensions/ffmpeg/build.gradle | 2 +- extensions/flac/build.gradle | 2 +- extensions/gvr/build.gradle | 2 +- extensions/okhttp/build.gradle | 2 +- extensions/opus/build.gradle | 2 +- extensions/vp9/build.gradle | 2 +- library/{ => all}/build.gradle | 24 +------- .../{ => all}/src/main/AndroidManifest.xml | 0 library/core/build.gradle | 52 ++++++++++++++++++ library/{ => core}/proguard-rules.txt | 0 .../src/androidTest/AndroidManifest.xml | 4 +- .../src/androidTest/assets/dash/sample_mpd_1 | 0 .../dash/sample_mpd_2_unknown_mime_type | 0 .../assets/dash/sample_mpd_3_segment_template | 0 .../src/androidTest/assets/flv/sample.flv | Bin .../androidTest/assets/flv/sample.flv.0.dump | 0 .../src/androidTest/assets/mkv/sample.mkv | Bin .../androidTest/assets/mkv/sample.mkv.0.dump | 0 .../androidTest/assets/mkv/sample.mkv.1.dump | 0 .../androidTest/assets/mkv/sample.mkv.2.dump | 0 .../androidTest/assets/mkv/sample.mkv.3.dump | 0 .../mkv/subsample_encrypted_altref.webm | Bin .../subsample_encrypted_altref.webm.0.dump | 0 .../mkv/subsample_encrypted_noaltref.webm | Bin .../subsample_encrypted_noaltref.webm.0.dump | 0 .../src/androidTest/assets/mp3/bear.mp3 | Bin .../androidTest/assets/mp3/bear.mp3.0.dump | 0 .../androidTest/assets/mp3/bear.mp3.1.dump | 0 .../androidTest/assets/mp3/bear.mp3.2.dump | 0 .../androidTest/assets/mp3/bear.mp3.3.dump | 0 .../androidTest/assets/mp3/play-trimmed.mp3 | Bin .../assets/mp3/play-trimmed.mp3.0.dump | 0 .../assets/mp3/play-trimmed.mp3.1.dump | 0 .../assets/mp3/play-trimmed.mp3.2.dump | 0 .../assets/mp3/play-trimmed.mp3.3.dump | 0 .../assets/mp3/play-trimmed.mp3.unklen.dump | 0 .../src/androidTest/assets/mp4/sample.mp4 | Bin .../androidTest/assets/mp4/sample.mp4.0.dump | 0 .../androidTest/assets/mp4/sample.mp4.1.dump | 0 .../androidTest/assets/mp4/sample.mp4.2.dump | 0 .../androidTest/assets/mp4/sample.mp4.3.dump | 0 .../assets/mp4/sample_fragmented.mp4 | Bin .../assets/mp4/sample_fragmented.mp4.0.dump | 0 .../assets/mp4/sample_fragmented_sei.mp4 | Bin .../mp4/sample_fragmented_sei.mp4.0.dump | 0 .../mp4/sample_fragmented_zero_size_atom.mp4 | Bin .../src/androidTest/assets/ogg/bear.opus | Bin .../androidTest/assets/ogg/bear.opus.0.dump | 0 .../androidTest/assets/ogg/bear.opus.1.dump | 0 .../androidTest/assets/ogg/bear.opus.2.dump | 0 .../androidTest/assets/ogg/bear.opus.3.dump | 0 .../assets/ogg/bear.opus.unklen.dump | 0 .../src/androidTest/assets/ogg/bear_flac.ogg | Bin .../assets/ogg/bear_flac.ogg.0.dump | 0 .../assets/ogg/bear_flac.ogg.1.dump | 0 .../assets/ogg/bear_flac.ogg.2.dump | 0 .../assets/ogg/bear_flac.ogg.3.dump | 0 .../assets/ogg/bear_flac.ogg.unklen.dump | 0 .../assets/ogg/bear_flac_noseektable.ogg | Bin .../ogg/bear_flac_noseektable.ogg.0.dump | 0 .../ogg/bear_flac_noseektable.ogg.1.dump | 0 .../ogg/bear_flac_noseektable.ogg.2.dump | 0 .../ogg/bear_flac_noseektable.ogg.3.dump | 0 .../ogg/bear_flac_noseektable.ogg.unklen.dump | 0 .../androidTest/assets/ogg/bear_vorbis.ogg | Bin .../assets/ogg/bear_vorbis.ogg.0.dump | 0 .../assets/ogg/bear_vorbis.ogg.1.dump | 0 .../assets/ogg/bear_vorbis.ogg.2.dump | 0 .../assets/ogg/bear_vorbis.ogg.3.dump | 0 .../assets/ogg/bear_vorbis.ogg.unklen.dump | 0 .../src/androidTest/assets/rawcc/sample.rawcc | Bin .../assets/rawcc/sample.rawcc.0.dump | 0 .../assets/smoothstreaming/sample_ismc_1 | 0 .../assets/smoothstreaming/sample_ismc_2 | 0 .../src/androidTest/assets/subrip/empty | 0 .../assets/subrip/no_end_timecodes | 0 .../src/androidTest/assets/subrip/typical | 0 .../assets/subrip/typical_extra_blank_line | 0 .../assets/subrip/typical_missing_sequence | 0 .../assets/subrip/typical_missing_timecode | 0 .../assets/subrip/typical_negative_timestamps | 0 .../subrip/typical_with_byte_order_mark | 0 .../src/androidTest/assets/ts/sample.ac3 | Bin .../androidTest/assets/ts/sample.ac3.0.dump | 0 .../src/androidTest/assets/ts/sample.adts | Bin .../androidTest/assets/ts/sample.adts.0.dump | 0 .../src/androidTest/assets/ts/sample.ps | Bin .../androidTest/assets/ts/sample.ps.0.dump | 0 .../src/androidTest/assets/ts/sample.ts | Bin .../androidTest/assets/ts/sample.ts.0.dump | 0 .../androidTest/assets/ts/sample_with_sdt.ts | Bin .../assets/ttml/chain_multiple_styles.xml | 0 .../src/androidTest/assets/ttml/font_size.xml | 0 .../assets/ttml/font_size_empty.xml | 0 .../assets/ttml/font_size_invalid.xml | 0 .../assets/ttml/font_size_no_unit.xml | 0 .../androidTest/assets/ttml/frame_rate.xml | 0 .../ttml/inherit_and_override_style.xml | 0 .../assets/ttml/inherit_global_and_parent.xml | 0 .../assets/ttml/inherit_multiple_styles.xml | 0 .../androidTest/assets/ttml/inherit_style.xml | 0 .../assets/ttml/inline_style_attributes.xml | 0 .../assets/ttml/multiple_regions.xml | 0 .../assets/ttml/no_underline_linethrough.xml | 0 .../src/androidTest/assets/wav/sample.wav | Bin .../androidTest/assets/wav/sample.wav.0.dump | 0 .../androidTest/assets/wav/sample.wav.1.dump | 0 .../androidTest/assets/wav/sample.wav.2.dump | 0 .../androidTest/assets/wav/sample.wav.3.dump | 0 .../assets/webm/vorbis_codec_private | Bin .../src/androidTest/assets/webvtt/empty | 0 .../src/androidTest/assets/webvtt/typical | 0 .../assets/webvtt/typical_with_comments | 0 .../assets/webvtt/typical_with_identifiers | 0 .../assets/webvtt/with_bad_cue_header | 0 .../assets/webvtt/with_css_complex_selectors | 0 .../androidTest/assets/webvtt/with_css_styles | 0 .../assets/webvtt/with_positioning | 0 .../src/androidTest/assets/webvtt/with_tags | 0 .../com/google/android/exoplayer2/CTest.java | 0 .../android/exoplayer2/ExoPlayerTest.java | 0 .../google/android/exoplayer2/FormatTest.java | 0 .../exoplayer2/drm/DrmInitDataTest.java | 0 .../drm/OfflineLicenseHelperTest.java | 0 .../extractor/DefaultExtractorInputTest.java | 0 .../exoplayer2/extractor/ExtractorTest.java | 0 .../extractor/flv/FlvExtractorTest.java | 0 .../extractor/mkv/DefaultEbmlReaderTest.java | 0 .../extractor/mkv/MatroskaExtractorTest.java | 0 .../extractor/mkv/VarintReaderTest.java | 0 .../extractor/mp3/Mp3ExtractorTest.java | 0 .../extractor/mp3/XingSeekerTest.java | 0 .../extractor/mp4/AtomParsersTest.java | 0 .../mp4/FragmentedMp4ExtractorTest.java | 0 .../extractor/mp4/Mp4ExtractorTest.java | 0 .../extractor/ogg/DefaultOggSeekerTest.java | 0 .../ogg/DefaultOggSeekerUtilMethodsTest.java | 0 .../extractor/ogg/OggExtractorTest.java | 0 .../extractor/ogg/OggPacketTest.java | 0 .../extractor/ogg/OggPageHeaderTest.java | 0 .../exoplayer2/extractor/ogg/OggTestFile.java | 0 .../exoplayer2/extractor/ogg/TestData.java | 0 .../extractor/ogg/VorbisBitArrayTest.java | 0 .../extractor/ogg/VorbisReaderTest.java | 0 .../extractor/ogg/VorbisUtilTest.java | 0 .../extractor/rawcc/RawCcExtractorTest.java | 0 .../extractor/ts/Ac3ExtractorTest.java | 0 .../extractor/ts/AdtsExtractorTest.java | 0 .../extractor/ts/AdtsReaderTest.java | 0 .../extractor/ts/PsExtractorTest.java | 0 .../extractor/ts/SectionReaderTest.java | 0 .../extractor/ts/TsExtractorTest.java | 0 .../extractor/wav/WavExtractorTest.java | 0 .../emsg/EventMessageDecoderTest.java | 0 .../metadata/emsg/EventMessageTest.java | 0 .../metadata/id3/ChapterFrameTest.java | 0 .../metadata/id3/ChapterTocFrameTest.java | 0 .../metadata/id3/Id3DecoderTest.java | 0 .../scte35/SpliceInfoDecoderTest.java | 0 .../source/ClippingMediaSourceTest.java | 0 .../dash/manifest/DashManifestParserTest.java | 0 .../dash/manifest/DashManifestTest.java | 0 .../source/dash/manifest/RangedUriTest.java | 0 .../dash/manifest/RepresentationTest.java | 0 .../source/dash/manifest/UrlTemplateTest.java | 0 .../playlist/HlsMasterPlaylistParserTest.java | 0 .../playlist/HlsMediaPlaylistParserTest.java | 0 .../manifest/SsManifestParserTest.java | 0 .../text/subrip/SubripDecoderTest.java | 0 .../exoplayer2/text/ttml/TtmlDecoderTest.java | 0 .../text/ttml/TtmlRenderUtilTest.java | 0 .../exoplayer2/text/ttml/TtmlStyleTest.java | 0 .../exoplayer2/text/webvtt/CssParserTest.java | 0 .../text/webvtt/Mp4WebvttDecoderTest.java | 0 .../text/webvtt/WebvttCueParserTest.java | 0 .../text/webvtt/WebvttDecoderTest.java | 0 .../text/webvtt/WebvttSubtitleTest.java | 0 .../upstream/ByteArrayDataSourceTest.java | 0 .../upstream/DataSourceInputStreamTest.java | 0 .../upstream/cache/CacheDataSourceTest.java | 0 .../upstream/cache/CacheDataSourceTest2.java | 0 .../cache/CachedContentIndexTest.java | 0 .../cache/CachedRegionTrackerTest.java | 0 .../upstream/cache/SimpleCacheSpanTest.java | 0 .../upstream/cache/SimpleCacheTest.java | 0 .../crypto/AesFlushingCipherTest.java | 0 .../exoplayer2/util/AtomicFileTest.java | 0 .../exoplayer2/util/ColorParserTest.java | 0 .../exoplayer2/util/NalUnitUtilTest.java | 0 .../util/ParsableByteArrayTest.java | 0 .../util/ParsableNalUnitBitArrayTest.java | 0 .../ReusableBufferedOutputStreamTest.java | 0 .../android/exoplayer2/util/UriUtilTest.java | 0 .../android/exoplayer2/util/UtilTest.java | 0 library/core/src/main/AndroidManifest.xml | 17 ++++++ .../android/exoplayer2/BaseRenderer.java | 0 .../java/com/google/android/exoplayer2/C.java | 0 .../exoplayer2/DefaultLoadControl.java | 0 .../exoplayer2/ExoPlaybackException.java | 0 .../google/android/exoplayer2/ExoPlayer.java | 0 .../android/exoplayer2/ExoPlayerFactory.java | 0 .../android/exoplayer2/ExoPlayerImpl.java | 0 .../exoplayer2/ExoPlayerImplInternal.java | 0 .../exoplayer2/ExoPlayerLibraryInfo.java | 0 .../com/google/android/exoplayer2/Format.java | 0 .../android/exoplayer2/FormatHolder.java | 0 .../IllegalSeekPositionException.java | 0 .../android/exoplayer2/LoadControl.java | 0 .../android/exoplayer2/ParserException.java | 0 .../google/android/exoplayer2/Renderer.java | 0 .../exoplayer2/RendererCapabilities.java | 0 .../exoplayer2/RendererConfiguration.java | 0 .../android/exoplayer2/SimpleExoPlayer.java | 0 .../google/android/exoplayer2/Timeline.java | 0 .../android/exoplayer2/audio/Ac3Util.java | 0 .../exoplayer2/audio/AudioCapabilities.java | 0 .../audio/AudioCapabilitiesReceiver.java | 0 .../audio/AudioDecoderException.java | 0 .../exoplayer2/audio/AudioProcessor.java | 0 .../audio/AudioRendererEventListener.java | 0 .../android/exoplayer2/audio/AudioTrack.java | 0 .../audio/ChannelMappingAudioProcessor.java | 0 .../android/exoplayer2/audio/DtsUtil.java | 0 .../audio/MediaCodecAudioRenderer.java | 0 .../audio/ResamplingAudioProcessor.java | 0 .../audio/SimpleDecoderAudioRenderer.java | 0 .../android/exoplayer2/decoder/Buffer.java | 0 .../exoplayer2/decoder/CryptoInfo.java | 0 .../android/exoplayer2/decoder/Decoder.java | 0 .../exoplayer2/decoder/DecoderCounters.java | 0 .../decoder/DecoderInputBuffer.java | 0 .../exoplayer2/decoder/OutputBuffer.java | 0 .../exoplayer2/decoder/SimpleDecoder.java | 0 .../decoder/SimpleOutputBuffer.java | 0 .../exoplayer2/drm/DecryptionException.java | 0 .../drm/DefaultDrmSessionManager.java | 0 .../android/exoplayer2/drm/DrmInitData.java | 0 .../android/exoplayer2/drm/DrmSession.java | 0 .../exoplayer2/drm/DrmSessionManager.java | 0 .../exoplayer2/drm/ExoMediaCrypto.java | 0 .../android/exoplayer2/drm/ExoMediaDrm.java | 0 .../exoplayer2/drm/FrameworkMediaCrypto.java | 0 .../exoplayer2/drm/FrameworkMediaDrm.java | 0 .../exoplayer2/drm/HttpMediaDrmCallback.java | 0 .../exoplayer2/drm/KeysExpiredException.java | 0 .../exoplayer2/drm/MediaDrmCallback.java | 0 .../exoplayer2/drm/OfflineLicenseHelper.java | 0 .../drm/UnsupportedDrmException.java | 0 .../android/exoplayer2/drm/WidevineUtil.java | 0 .../exoplayer2/extractor/ChunkIndex.java | 0 .../extractor/DefaultExtractorInput.java | 0 .../extractor/DefaultExtractorsFactory.java | 0 .../extractor/DefaultTrackOutput.java | 0 .../extractor/DummyTrackOutput.java | 0 .../exoplayer2/extractor/Extractor.java | 0 .../exoplayer2/extractor/ExtractorInput.java | 0 .../exoplayer2/extractor/ExtractorOutput.java | 0 .../extractor/ExtractorsFactory.java | 0 .../extractor/GaplessInfoHolder.java | 0 .../exoplayer2/extractor/MpegAudioHeader.java | 0 .../exoplayer2/extractor/PositionHolder.java | 0 .../android/exoplayer2/extractor/SeekMap.java | 0 .../exoplayer2/extractor/TrackOutput.java | 0 .../extractor/flv/AudioTagPayloadReader.java | 0 .../extractor/flv/FlvExtractor.java | 0 .../extractor/flv/ScriptTagPayloadReader.java | 0 .../extractor/flv/TagPayloadReader.java | 0 .../extractor/flv/VideoTagPayloadReader.java | 0 .../extractor/mkv/DefaultEbmlReader.java | 0 .../exoplayer2/extractor/mkv/EbmlReader.java | 0 .../extractor/mkv/EbmlReaderOutput.java | 0 .../extractor/mkv/MatroskaExtractor.java | 0 .../exoplayer2/extractor/mkv/Sniffer.java | 0 .../extractor/mkv/VarintReader.java | 0 .../extractor/mp3/ConstantBitrateSeeker.java | 0 .../extractor/mp3/Mp3Extractor.java | 0 .../exoplayer2/extractor/mp3/VbriSeeker.java | 0 .../exoplayer2/extractor/mp3/XingSeeker.java | 0 .../exoplayer2/extractor/mp4/Atom.java | 0 .../exoplayer2/extractor/mp4/AtomParsers.java | 0 .../extractor/mp4/DefaultSampleValues.java | 0 .../mp4/FixedSampleSizeRechunker.java | 0 .../extractor/mp4/FragmentedMp4Extractor.java | 0 .../extractor/mp4/MetadataUtil.java | 0 .../extractor/mp4/Mp4Extractor.java | 0 .../extractor/mp4/PsshAtomUtil.java | 0 .../exoplayer2/extractor/mp4/Sniffer.java | 0 .../exoplayer2/extractor/mp4/Track.java | 0 .../extractor/mp4/TrackEncryptionBox.java | 0 .../extractor/mp4/TrackFragment.java | 0 .../extractor/mp4/TrackSampleTable.java | 0 .../extractor/ogg/DefaultOggSeeker.java | 0 .../exoplayer2/extractor/ogg/FlacReader.java | 0 .../extractor/ogg/OggExtractor.java | 0 .../exoplayer2/extractor/ogg/OggPacket.java | 0 .../extractor/ogg/OggPageHeader.java | 0 .../exoplayer2/extractor/ogg/OggSeeker.java | 0 .../exoplayer2/extractor/ogg/OpusReader.java | 0 .../extractor/ogg/StreamReader.java | 0 .../extractor/ogg/VorbisBitArray.java | 0 .../extractor/ogg/VorbisReader.java | 0 .../exoplayer2/extractor/ogg/VorbisUtil.java | 0 .../extractor/rawcc/RawCcExtractor.java | 0 .../exoplayer2/extractor/ts/Ac3Extractor.java | 0 .../exoplayer2/extractor/ts/Ac3Reader.java | 0 .../extractor/ts/AdtsExtractor.java | 0 .../exoplayer2/extractor/ts/AdtsReader.java | 0 .../ts/DefaultTsPayloadReaderFactory.java | 0 .../exoplayer2/extractor/ts/DtsReader.java | 0 .../extractor/ts/ElementaryStreamReader.java | 0 .../exoplayer2/extractor/ts/H262Reader.java | 0 .../exoplayer2/extractor/ts/H264Reader.java | 0 .../exoplayer2/extractor/ts/H265Reader.java | 0 .../exoplayer2/extractor/ts/Id3Reader.java | 0 .../extractor/ts/MpegAudioReader.java | 0 .../extractor/ts/NalUnitTargetBuffer.java | 0 .../exoplayer2/extractor/ts/PesReader.java | 0 .../exoplayer2/extractor/ts/PsExtractor.java | 0 .../extractor/ts/SectionPayloadReader.java | 0 .../extractor/ts/SectionReader.java | 0 .../exoplayer2/extractor/ts/SeiReader.java | 0 .../extractor/ts/SpliceInfoSectionReader.java | 0 .../exoplayer2/extractor/ts/TsExtractor.java | 0 .../extractor/ts/TsPayloadReader.java | 0 .../extractor/wav/WavExtractor.java | 0 .../exoplayer2/extractor/wav/WavHeader.java | 0 .../extractor/wav/WavHeaderReader.java | 0 .../exoplayer2/mediacodec/MediaCodecInfo.java | 0 .../mediacodec/MediaCodecRenderer.java | 0 .../mediacodec/MediaCodecSelector.java | 0 .../exoplayer2/mediacodec/MediaCodecUtil.java | 0 .../android/exoplayer2/metadata/Metadata.java | 0 .../exoplayer2/metadata/MetadataDecoder.java | 0 .../metadata/MetadataDecoderException.java | 0 .../metadata/MetadataDecoderFactory.java | 0 .../metadata/MetadataInputBuffer.java | 0 .../exoplayer2/metadata/MetadataRenderer.java | 0 .../metadata/emsg/EventMessage.java | 0 .../metadata/emsg/EventMessageDecoder.java | 0 .../exoplayer2/metadata/id3/ApicFrame.java | 0 .../exoplayer2/metadata/id3/BinaryFrame.java | 0 .../exoplayer2/metadata/id3/ChapterFrame.java | 0 .../metadata/id3/ChapterTocFrame.java | 0 .../exoplayer2/metadata/id3/CommentFrame.java | 0 .../exoplayer2/metadata/id3/GeobFrame.java | 0 .../exoplayer2/metadata/id3/Id3Decoder.java | 0 .../exoplayer2/metadata/id3/Id3Frame.java | 0 .../exoplayer2/metadata/id3/PrivFrame.java | 0 .../metadata/id3/TextInformationFrame.java | 0 .../exoplayer2/metadata/id3/UrlLinkFrame.java | 0 .../metadata/scte35/PrivateCommand.java | 0 .../metadata/scte35/SpliceCommand.java | 0 .../metadata/scte35/SpliceInfoDecoder.java | 0 .../metadata/scte35/SpliceInsertCommand.java | 0 .../metadata/scte35/SpliceNullCommand.java | 0 .../scte35/SpliceScheduleCommand.java | 0 .../metadata/scte35/TimeSignalCommand.java | 0 .../AdaptiveMediaSourceEventListener.java | 0 .../source/BehindLiveWindowException.java | 0 .../source/ClippingMediaPeriod.java | 0 .../source/ClippingMediaSource.java | 0 .../source/CompositeSequenceableLoader.java | 0 .../source/ConcatenatingMediaSource.java | 0 .../exoplayer2/source/EmptySampleStream.java | 0 .../source/ExtractorMediaPeriod.java | 0 .../source/ExtractorMediaSource.java | 0 .../exoplayer2/source/LoopingMediaSource.java | 0 .../exoplayer2/source/MediaPeriod.java | 0 .../exoplayer2/source/MediaSource.java | 0 .../exoplayer2/source/MergingMediaPeriod.java | 0 .../exoplayer2/source/MergingMediaSource.java | 0 .../exoplayer2/source/SampleStream.java | 0 .../exoplayer2/source/SequenceableLoader.java | 0 .../source/SinglePeriodTimeline.java | 0 .../source/SingleSampleMediaPeriod.java | 0 .../source/SingleSampleMediaSource.java | 0 .../android/exoplayer2/source/TrackGroup.java | 0 .../exoplayer2/source/TrackGroupArray.java | 0 .../UnrecognizedInputFormatException.java | 0 .../source/chunk/BaseMediaChunk.java | 0 .../source/chunk/BaseMediaChunkOutput.java | 0 .../exoplayer2/source/chunk/Chunk.java | 0 .../source/chunk/ChunkExtractorWrapper.java | 0 .../exoplayer2/source/chunk/ChunkHolder.java | 0 .../source/chunk/ChunkSampleStream.java | 0 .../exoplayer2/source/chunk/ChunkSource.java | 0 .../chunk/ChunkedTrackBlacklistUtil.java | 0 .../source/chunk/ContainerMediaChunk.java | 0 .../exoplayer2/source/chunk/DataChunk.java | 0 .../source/chunk/InitializationChunk.java | 0 .../exoplayer2/source/chunk/MediaChunk.java | 0 .../source/chunk/SingleSampleMediaChunk.java | 0 .../source/dash/DashChunkSource.java | 0 .../source/dash/DashMediaPeriod.java | 0 .../source/dash/DashMediaSource.java | 0 .../source/dash/DashSegmentIndex.java | 0 .../exoplayer2/source/dash/DashUtil.java | 0 .../source/dash/DashWrappingSegmentIndex.java | 0 .../source/dash/DefaultDashChunkSource.java | 0 .../source/dash/manifest/AdaptationSet.java | 0 .../source/dash/manifest/DashManifest.java | 0 .../dash/manifest/DashManifestParser.java | 0 .../source/dash/manifest/Period.java | 0 .../source/dash/manifest/RangedUri.java | 0 .../source/dash/manifest/Representation.java | 0 .../dash/manifest/RepresentationKey.java | 0 .../source/dash/manifest/SchemeValuePair.java | 0 .../source/dash/manifest/SegmentBase.java | 0 .../dash/manifest/SingleSegmentIndex.java | 0 .../source/dash/manifest/UrlTemplate.java | 0 .../dash/manifest/UtcTimingElement.java | 0 .../source/hls/Aes128DataSource.java | 0 .../hls/DefaultHlsDataSourceFactory.java | 0 .../exoplayer2/source/hls/HlsChunkSource.java | 0 .../source/hls/HlsDataSourceFactory.java | 0 .../exoplayer2/source/hls/HlsManifest.java | 0 .../exoplayer2/source/hls/HlsMediaChunk.java | 0 .../exoplayer2/source/hls/HlsMediaPeriod.java | 0 .../exoplayer2/source/hls/HlsMediaSource.java | 0 .../source/hls/HlsSampleStream.java | 0 .../source/hls/HlsSampleStreamWrapper.java | 0 .../source/hls/TimestampAdjusterProvider.java | 0 .../source/hls/WebvttExtractor.java | 0 .../hls/playlist/HlsMasterPlaylist.java | 0 .../source/hls/playlist/HlsMediaPlaylist.java | 0 .../source/hls/playlist/HlsPlaylist.java | 0 .../hls/playlist/HlsPlaylistParser.java | 0 .../hls/playlist/HlsPlaylistTracker.java | 0 .../smoothstreaming/DefaultSsChunkSource.java | 0 .../source/smoothstreaming/SsChunkSource.java | 0 .../source/smoothstreaming/SsMediaPeriod.java | 0 .../source/smoothstreaming/SsMediaSource.java | 0 .../smoothstreaming/manifest/SsManifest.java | 0 .../manifest/SsManifestParser.java | 0 .../exoplayer2/text/CaptionStyleCompat.java | 0 .../google/android/exoplayer2/text/Cue.java | 0 .../text/SimpleSubtitleDecoder.java | 0 .../text/SimpleSubtitleOutputBuffer.java | 0 .../android/exoplayer2/text/Subtitle.java | 0 .../exoplayer2/text/SubtitleDecoder.java | 0 .../text/SubtitleDecoderException.java | 0 .../text/SubtitleDecoderFactory.java | 0 .../exoplayer2/text/SubtitleInputBuffer.java | 0 .../exoplayer2/text/SubtitleOutputBuffer.java | 0 .../android/exoplayer2/text/TextRenderer.java | 0 .../exoplayer2/text/cea/Cea608Decoder.java | 0 .../exoplayer2/text/cea/Cea708Cue.java | 0 .../exoplayer2/text/cea/Cea708Decoder.java | 0 .../exoplayer2/text/cea/CeaDecoder.java | 0 .../exoplayer2/text/cea/CeaOutputBuffer.java | 0 .../exoplayer2/text/cea/CeaSubtitle.java | 0 .../android/exoplayer2/text/cea/CeaUtil.java | 0 .../exoplayer2/text/subrip/SubripDecoder.java | 0 .../text/subrip/SubripSubtitle.java | 0 .../exoplayer2/text/ttml/TtmlDecoder.java | 0 .../exoplayer2/text/ttml/TtmlNode.java | 0 .../exoplayer2/text/ttml/TtmlRegion.java | 0 .../exoplayer2/text/ttml/TtmlRenderUtil.java | 0 .../exoplayer2/text/ttml/TtmlStyle.java | 0 .../exoplayer2/text/ttml/TtmlSubtitle.java | 0 .../exoplayer2/text/tx3g/Tx3gDecoder.java | 0 .../exoplayer2/text/tx3g/Tx3gSubtitle.java | 0 .../exoplayer2/text/webvtt/CssParser.java | 0 .../text/webvtt/Mp4WebvttDecoder.java | 0 .../text/webvtt/Mp4WebvttSubtitle.java | 0 .../text/webvtt/WebvttCssStyle.java | 0 .../exoplayer2/text/webvtt/WebvttCue.java | 0 .../text/webvtt/WebvttCueParser.java | 0 .../exoplayer2/text/webvtt/WebvttDecoder.java | 0 .../text/webvtt/WebvttParserUtil.java | 0 .../text/webvtt/WebvttSubtitle.java | 0 .../AdaptiveTrackSelection.java | 0 .../trackselection/BaseTrackSelection.java | 0 .../trackselection/DefaultTrackSelector.java | 0 .../trackselection/FixedTrackSelection.java | 0 .../trackselection/MappingTrackSelector.java | 0 .../trackselection/RandomTrackSelection.java | 0 .../trackselection/TrackSelection.java | 0 .../trackselection/TrackSelectionArray.java | 0 .../trackselection/TrackSelector.java | 0 .../trackselection/TrackSelectorResult.java | 0 .../exoplayer2/ui/AspectRatioFrameLayout.java | 2 +- .../exoplayer2/ui/DebugTextViewHelper.java | 0 .../exoplayer2/ui/PlaybackControlView.java | 2 +- .../exoplayer2/ui/SimpleExoPlayerView.java | 2 +- .../exoplayer2/ui/SubtitlePainter.java | 0 .../android/exoplayer2/ui/SubtitleView.java | 0 .../exoplayer2/upstream/Allocation.java | 0 .../exoplayer2/upstream/Allocator.java | 0 .../exoplayer2/upstream/AssetDataSource.java | 0 .../exoplayer2/upstream/BandwidthMeter.java | 0 .../upstream/ByteArrayDataSink.java | 0 .../upstream/ByteArrayDataSource.java | 0 .../upstream/ContentDataSource.java | 0 .../android/exoplayer2/upstream/DataSink.java | 0 .../exoplayer2/upstream/DataSource.java | 0 .../upstream/DataSourceException.java | 0 .../upstream/DataSourceInputStream.java | 0 .../android/exoplayer2/upstream/DataSpec.java | 0 .../exoplayer2/upstream/DefaultAllocator.java | 0 .../upstream/DefaultBandwidthMeter.java | 0 .../upstream/DefaultDataSource.java | 0 .../upstream/DefaultDataSourceFactory.java | 0 .../upstream/DefaultHttpDataSource.java | 0 .../DefaultHttpDataSourceFactory.java | 0 .../exoplayer2/upstream/FileDataSource.java | 0 .../upstream/FileDataSourceFactory.java | 0 .../exoplayer2/upstream/HttpDataSource.java | 0 .../android/exoplayer2/upstream/Loader.java | 0 .../upstream/LoaderErrorThrower.java | 0 .../exoplayer2/upstream/ParsingLoadable.java | 0 .../upstream/PriorityDataSource.java | 0 .../upstream/PriorityDataSourceFactory.java | 0 .../upstream/RawResourceDataSource.java | 0 .../exoplayer2/upstream/TeeDataSource.java | 0 .../exoplayer2/upstream/TransferListener.java | 0 .../exoplayer2/upstream/UdpDataSource.java | 0 .../exoplayer2/upstream/cache/Cache.java | 0 .../upstream/cache/CacheDataSink.java | 0 .../upstream/cache/CacheDataSinkFactory.java | 0 .../upstream/cache/CacheDataSource.java | 0 .../cache/CacheDataSourceFactory.java | 0 .../upstream/cache/CacheEvictor.java | 0 .../exoplayer2/upstream/cache/CacheSpan.java | 0 .../upstream/cache/CachedContent.java | 0 .../upstream/cache/CachedContentIndex.java | 0 .../upstream/cache/CachedRegionTracker.java | 0 .../cache/LeastRecentlyUsedCacheEvictor.java | 0 .../upstream/cache/NoOpCacheEvictor.java | 0 .../upstream/cache/SimpleCache.java | 0 .../upstream/cache/SimpleCacheSpan.java | 0 .../upstream/crypto/AesCipherDataSink.java | 0 .../upstream/crypto/AesCipherDataSource.java | 0 .../upstream/crypto/AesFlushingCipher.java | 0 .../upstream/crypto/CryptoUtil.java | 0 .../android/exoplayer2/util/Assertions.java | 0 .../android/exoplayer2/util/AtomicFile.java | 0 .../google/android/exoplayer2/util/Clock.java | 0 .../android/exoplayer2/util/ClosedSource.java | 31 +++++++++++ .../util/CodecSpecificDataUtil.java | 0 .../android/exoplayer2/util/ColorParser.java | 0 .../exoplayer2/util/ConditionVariable.java | 0 .../exoplayer2/util/FlacStreamInfo.java | 0 .../exoplayer2/util/LibraryLoader.java | 0 .../android/exoplayer2/util/LongArray.java | 0 .../android/exoplayer2/util/MediaClock.java | 0 .../android/exoplayer2/util/MimeTypes.java | 0 .../android/exoplayer2/util/NalUnitUtil.java | 0 .../exoplayer2/util/ParsableBitArray.java | 0 .../exoplayer2/util/ParsableByteArray.java | 0 .../util/ParsableNalUnitBitArray.java | 0 .../android/exoplayer2/util/Predicate.java | 0 .../util/PriorityHandlerThread.java | 0 .../exoplayer2/util/PriorityTaskManager.java | 0 .../util/ReusableBufferedOutputStream.java | 0 .../exoplayer2/util/SlidingPercentile.java | 0 .../exoplayer2/util/StandaloneMediaClock.java | 0 .../android/exoplayer2/util/SystemClock.java | 0 .../exoplayer2/util/TimestampAdjuster.java | 0 .../android/exoplayer2/util/TraceUtil.java | 0 .../android/exoplayer2/util/UriUtil.java | 0 .../google/android/exoplayer2/util/Util.java | 0 .../exoplayer2/util/XmlPullParserUtil.java | 0 .../android/exoplayer2/video/AvcConfig.java | 0 .../android/exoplayer2/video/HevcConfig.java | 0 .../video/MediaCodecVideoRenderer.java | 0 .../video/VideoFrameReleaseTimeHelper.java | 0 .../video/VideoRendererEventListener.java | 0 .../doc-files/exoplayer-threading-model.svg | 0 .../exoplayer2/doc-files/renderer-states.svg | 0 .../doc-files/timeline-advanced.svg | 0 .../doc-files/timeline-live-indefinite.svg | 0 .../doc-files/timeline-live-limited.svg | 0 .../doc-files/timeline-live-multi-period.svg | 0 .../exoplayer2/doc-files/timeline-period.svg | 0 .../doc-files/timeline-playlist.svg | 0 .../doc-files/timeline-single-file.svg | 0 .../exoplayer2/doc-files/timeline-window.svg | 0 library/core/src/main/proguard-rules.txt | 7 +++ .../exo_controls_fastforward.xml | 0 .../drawable-anydpi-v21/exo_controls_next.xml | 0 .../exo_controls_pause.xml | 0 .../drawable-anydpi-v21/exo_controls_play.xml | 0 .../exo_controls_previous.xml | 0 .../exo_controls_rewind.xml | 0 .../exo_controls_fastforward.png | Bin .../res/drawable-hdpi/exo_controls_next.png | Bin .../res/drawable-hdpi/exo_controls_pause.png | Bin .../res/drawable-hdpi/exo_controls_play.png | Bin .../drawable-hdpi/exo_controls_previous.png | Bin .../res/drawable-hdpi/exo_controls_rewind.png | Bin .../exo_controls_fastforward.png | Bin .../res/drawable-ldpi/exo_controls_next.png | Bin .../res/drawable-ldpi/exo_controls_pause.png | Bin .../res/drawable-ldpi/exo_controls_play.png | Bin .../drawable-ldpi/exo_controls_previous.png | Bin .../res/drawable-ldpi/exo_controls_rewind.png | Bin .../exo_controls_fastforward.png | Bin .../res/drawable-mdpi/exo_controls_next.png | Bin .../res/drawable-mdpi/exo_controls_pause.png | Bin .../res/drawable-mdpi/exo_controls_play.png | Bin .../drawable-mdpi/exo_controls_previous.png | Bin .../res/drawable-mdpi/exo_controls_rewind.png | Bin .../exo_controls_fastforward.png | Bin .../res/drawable-xhdpi/exo_controls_next.png | Bin .../res/drawable-xhdpi/exo_controls_pause.png | Bin .../res/drawable-xhdpi/exo_controls_play.png | Bin .../drawable-xhdpi/exo_controls_previous.png | Bin .../drawable-xhdpi/exo_controls_rewind.png | Bin .../exo_controls_fastforward.png | Bin .../res/drawable-xxhdpi/exo_controls_next.png | Bin .../drawable-xxhdpi/exo_controls_pause.png | Bin .../res/drawable-xxhdpi/exo_controls_play.png | Bin .../drawable-xxhdpi/exo_controls_previous.png | Bin .../drawable-xxhdpi/exo_controls_rewind.png | Bin .../res/layout/exo_playback_control_view.xml | 0 .../res/layout/exo_simple_player_view.xml | 0 .../src/main/res/values-af/strings.xml | 0 .../src/main/res/values-am/strings.xml | 0 .../src/main/res/values-ar/strings.xml | 0 .../src/main/res/values-az-rAZ/strings.xml | 0 .../src/main/res/values-b+sr+Latn/strings.xml | 0 .../src/main/res/values-be-rBY/strings.xml | 0 .../src/main/res/values-bg/strings.xml | 0 .../src/main/res/values-bn-rBD/strings.xml | 0 .../src/main/res/values-bs-rBA/strings.xml | 0 .../src/main/res/values-ca/strings.xml | 0 .../src/main/res/values-cs/strings.xml | 0 .../src/main/res/values-da/strings.xml | 0 .../src/main/res/values-de/strings.xml | 0 .../src/main/res/values-el/strings.xml | 0 .../src/main/res/values-en-rAU/strings.xml | 0 .../src/main/res/values-en-rGB/strings.xml | 0 .../src/main/res/values-en-rIN/strings.xml | 0 .../src/main/res/values-es-rUS/strings.xml | 0 .../src/main/res/values-es/strings.xml | 0 .../src/main/res/values-et-rEE/strings.xml | 0 .../src/main/res/values-eu-rES/strings.xml | 0 .../src/main/res/values-fa/strings.xml | 0 .../src/main/res/values-fi/strings.xml | 0 .../src/main/res/values-fr-rCA/strings.xml | 0 .../src/main/res/values-fr/strings.xml | 0 .../src/main/res/values-gl-rES/strings.xml | 0 .../src/main/res/values-gu-rIN/strings.xml | 0 .../src/main/res/values-hi/strings.xml | 0 .../src/main/res/values-hr/strings.xml | 0 .../src/main/res/values-hu/strings.xml | 0 .../src/main/res/values-hy-rAM/strings.xml | 0 .../src/main/res/values-in/strings.xml | 0 .../src/main/res/values-is-rIS/strings.xml | 0 .../src/main/res/values-it/strings.xml | 0 .../src/main/res/values-iw/strings.xml | 0 .../src/main/res/values-ja/strings.xml | 0 .../src/main/res/values-ka-rGE/strings.xml | 0 .../src/main/res/values-kk-rKZ/strings.xml | 0 .../src/main/res/values-km-rKH/strings.xml | 0 .../src/main/res/values-kn-rIN/strings.xml | 0 .../src/main/res/values-ko/strings.xml | 0 .../src/main/res/values-ky-rKG/strings.xml | 0 .../src/main/res/values-lo-rLA/strings.xml | 0 .../src/main/res/values-lt/strings.xml | 0 .../src/main/res/values-lv/strings.xml | 0 .../src/main/res/values-mk-rMK/strings.xml | 0 .../src/main/res/values-ml-rIN/strings.xml | 0 .../src/main/res/values-mn-rMN/strings.xml | 0 .../src/main/res/values-mr-rIN/strings.xml | 0 .../src/main/res/values-ms-rMY/strings.xml | 0 .../src/main/res/values-my-rMM/strings.xml | 0 .../src/main/res/values-nb/strings.xml | 0 .../src/main/res/values-ne-rNP/strings.xml | 0 .../src/main/res/values-nl/strings.xml | 0 .../src/main/res/values-pa-rIN/strings.xml | 0 .../src/main/res/values-pl/strings.xml | 0 .../src/main/res/values-pt-rBR/strings.xml | 0 .../src/main/res/values-pt-rPT/strings.xml | 0 .../src/main/res/values-pt/strings.xml | 0 .../src/main/res/values-ro/strings.xml | 0 .../src/main/res/values-ru/strings.xml | 0 .../src/main/res/values-si-rLK/strings.xml | 0 .../src/main/res/values-sk/strings.xml | 0 .../src/main/res/values-sl/strings.xml | 0 .../src/main/res/values-sq-rAL/strings.xml | 0 .../src/main/res/values-sr/strings.xml | 0 .../src/main/res/values-sv/strings.xml | 0 .../src/main/res/values-sw/strings.xml | 0 .../src/main/res/values-ta-rIN/strings.xml | 0 .../src/main/res/values-te-rIN/strings.xml | 0 .../src/main/res/values-th/strings.xml | 0 .../src/main/res/values-tl/strings.xml | 0 .../src/main/res/values-tr/strings.xml | 0 .../src/main/res/values-uk/strings.xml | 0 .../src/main/res/values-ur-rPK/strings.xml | 0 .../src/main/res/values-uz-rUZ/strings.xml | 0 .../src/main/res/values-v11/styles.xml | 0 .../src/main/res/values-vi/strings.xml | 0 .../src/main/res/values-zh-rCN/strings.xml | 0 .../src/main/res/values-zh-rHK/strings.xml | 0 .../src/main/res/values-zh-rTW/strings.xml | 0 .../src/main/res/values-zu/strings.xml | 0 .../{ => core}/src/main/res/values/attrs.xml | 0 .../src/main/res/values/constants.xml | 0 .../{ => core}/src/main/res/values/ids.xml | 0 .../src/main/res/values/strings.xml | 0 .../{ => core}/src/main/res/values/styles.xml | 0 settings.gradle | 3 + 706 files changed, 125 insertions(+), 33 deletions(-) rename library/{ => all}/build.gradle (74%) rename library/{ => all}/src/main/AndroidManifest.xml (100%) create mode 100644 library/core/build.gradle rename library/{ => core}/proguard-rules.txt (100%) rename library/{ => core}/src/androidTest/AndroidManifest.xml (90%) rename library/{ => core}/src/androidTest/assets/dash/sample_mpd_1 (100%) rename library/{ => core}/src/androidTest/assets/dash/sample_mpd_2_unknown_mime_type (100%) rename library/{ => core}/src/androidTest/assets/dash/sample_mpd_3_segment_template (100%) rename library/{ => core}/src/androidTest/assets/flv/sample.flv (100%) rename library/{ => core}/src/androidTest/assets/flv/sample.flv.0.dump (100%) rename library/{ => core}/src/androidTest/assets/mkv/sample.mkv (100%) rename library/{ => core}/src/androidTest/assets/mkv/sample.mkv.0.dump (100%) rename library/{ => core}/src/androidTest/assets/mkv/sample.mkv.1.dump (100%) rename library/{ => core}/src/androidTest/assets/mkv/sample.mkv.2.dump (100%) rename library/{ => core}/src/androidTest/assets/mkv/sample.mkv.3.dump (100%) rename library/{ => core}/src/androidTest/assets/mkv/subsample_encrypted_altref.webm (100%) rename library/{ => core}/src/androidTest/assets/mkv/subsample_encrypted_altref.webm.0.dump (100%) rename library/{ => core}/src/androidTest/assets/mkv/subsample_encrypted_noaltref.webm (100%) rename library/{ => core}/src/androidTest/assets/mkv/subsample_encrypted_noaltref.webm.0.dump (100%) rename library/{ => core}/src/androidTest/assets/mp3/bear.mp3 (100%) rename library/{ => core}/src/androidTest/assets/mp3/bear.mp3.0.dump (100%) rename library/{ => core}/src/androidTest/assets/mp3/bear.mp3.1.dump (100%) rename library/{ => core}/src/androidTest/assets/mp3/bear.mp3.2.dump (100%) rename library/{ => core}/src/androidTest/assets/mp3/bear.mp3.3.dump (100%) rename library/{ => core}/src/androidTest/assets/mp3/play-trimmed.mp3 (100%) rename library/{ => core}/src/androidTest/assets/mp3/play-trimmed.mp3.0.dump (100%) rename library/{ => core}/src/androidTest/assets/mp3/play-trimmed.mp3.1.dump (100%) rename library/{ => core}/src/androidTest/assets/mp3/play-trimmed.mp3.2.dump (100%) rename library/{ => core}/src/androidTest/assets/mp3/play-trimmed.mp3.3.dump (100%) rename library/{ => core}/src/androidTest/assets/mp3/play-trimmed.mp3.unklen.dump (100%) rename library/{ => core}/src/androidTest/assets/mp4/sample.mp4 (100%) rename library/{ => core}/src/androidTest/assets/mp4/sample.mp4.0.dump (100%) rename library/{ => core}/src/androidTest/assets/mp4/sample.mp4.1.dump (100%) rename library/{ => core}/src/androidTest/assets/mp4/sample.mp4.2.dump (100%) rename library/{ => core}/src/androidTest/assets/mp4/sample.mp4.3.dump (100%) rename library/{ => core}/src/androidTest/assets/mp4/sample_fragmented.mp4 (100%) rename library/{ => core}/src/androidTest/assets/mp4/sample_fragmented.mp4.0.dump (100%) rename library/{ => core}/src/androidTest/assets/mp4/sample_fragmented_sei.mp4 (100%) rename library/{ => core}/src/androidTest/assets/mp4/sample_fragmented_sei.mp4.0.dump (100%) rename library/{ => core}/src/androidTest/assets/mp4/sample_fragmented_zero_size_atom.mp4 (100%) rename library/{ => core}/src/androidTest/assets/ogg/bear.opus (100%) rename library/{ => core}/src/androidTest/assets/ogg/bear.opus.0.dump (100%) rename library/{ => core}/src/androidTest/assets/ogg/bear.opus.1.dump (100%) rename library/{ => core}/src/androidTest/assets/ogg/bear.opus.2.dump (100%) rename library/{ => core}/src/androidTest/assets/ogg/bear.opus.3.dump (100%) rename library/{ => core}/src/androidTest/assets/ogg/bear.opus.unklen.dump (100%) rename library/{ => core}/src/androidTest/assets/ogg/bear_flac.ogg (100%) rename library/{ => core}/src/androidTest/assets/ogg/bear_flac.ogg.0.dump (100%) rename library/{ => core}/src/androidTest/assets/ogg/bear_flac.ogg.1.dump (100%) rename library/{ => core}/src/androidTest/assets/ogg/bear_flac.ogg.2.dump (100%) rename library/{ => core}/src/androidTest/assets/ogg/bear_flac.ogg.3.dump (100%) rename library/{ => core}/src/androidTest/assets/ogg/bear_flac.ogg.unklen.dump (100%) rename library/{ => core}/src/androidTest/assets/ogg/bear_flac_noseektable.ogg (100%) rename library/{ => core}/src/androidTest/assets/ogg/bear_flac_noseektable.ogg.0.dump (100%) rename library/{ => core}/src/androidTest/assets/ogg/bear_flac_noseektable.ogg.1.dump (100%) rename library/{ => core}/src/androidTest/assets/ogg/bear_flac_noseektable.ogg.2.dump (100%) rename library/{ => core}/src/androidTest/assets/ogg/bear_flac_noseektable.ogg.3.dump (100%) rename library/{ => core}/src/androidTest/assets/ogg/bear_flac_noseektable.ogg.unklen.dump (100%) rename library/{ => core}/src/androidTest/assets/ogg/bear_vorbis.ogg (100%) rename library/{ => core}/src/androidTest/assets/ogg/bear_vorbis.ogg.0.dump (100%) rename library/{ => core}/src/androidTest/assets/ogg/bear_vorbis.ogg.1.dump (100%) rename library/{ => core}/src/androidTest/assets/ogg/bear_vorbis.ogg.2.dump (100%) rename library/{ => core}/src/androidTest/assets/ogg/bear_vorbis.ogg.3.dump (100%) rename library/{ => core}/src/androidTest/assets/ogg/bear_vorbis.ogg.unklen.dump (100%) rename library/{ => core}/src/androidTest/assets/rawcc/sample.rawcc (100%) rename library/{ => core}/src/androidTest/assets/rawcc/sample.rawcc.0.dump (100%) rename library/{ => core}/src/androidTest/assets/smoothstreaming/sample_ismc_1 (100%) rename library/{ => core}/src/androidTest/assets/smoothstreaming/sample_ismc_2 (100%) rename library/{ => core}/src/androidTest/assets/subrip/empty (100%) rename library/{ => core}/src/androidTest/assets/subrip/no_end_timecodes (100%) rename library/{ => core}/src/androidTest/assets/subrip/typical (100%) rename library/{ => core}/src/androidTest/assets/subrip/typical_extra_blank_line (100%) rename library/{ => core}/src/androidTest/assets/subrip/typical_missing_sequence (100%) rename library/{ => core}/src/androidTest/assets/subrip/typical_missing_timecode (100%) rename library/{ => core}/src/androidTest/assets/subrip/typical_negative_timestamps (100%) rename library/{ => core}/src/androidTest/assets/subrip/typical_with_byte_order_mark (100%) rename library/{ => core}/src/androidTest/assets/ts/sample.ac3 (100%) rename library/{ => core}/src/androidTest/assets/ts/sample.ac3.0.dump (100%) rename library/{ => core}/src/androidTest/assets/ts/sample.adts (100%) rename library/{ => core}/src/androidTest/assets/ts/sample.adts.0.dump (100%) rename library/{ => core}/src/androidTest/assets/ts/sample.ps (100%) rename library/{ => core}/src/androidTest/assets/ts/sample.ps.0.dump (100%) rename library/{ => core}/src/androidTest/assets/ts/sample.ts (100%) rename library/{ => core}/src/androidTest/assets/ts/sample.ts.0.dump (100%) rename library/{ => core}/src/androidTest/assets/ts/sample_with_sdt.ts (100%) rename library/{ => core}/src/androidTest/assets/ttml/chain_multiple_styles.xml (100%) rename library/{ => core}/src/androidTest/assets/ttml/font_size.xml (100%) rename library/{ => core}/src/androidTest/assets/ttml/font_size_empty.xml (100%) rename library/{ => core}/src/androidTest/assets/ttml/font_size_invalid.xml (100%) rename library/{ => core}/src/androidTest/assets/ttml/font_size_no_unit.xml (100%) rename library/{ => core}/src/androidTest/assets/ttml/frame_rate.xml (100%) rename library/{ => core}/src/androidTest/assets/ttml/inherit_and_override_style.xml (100%) rename library/{ => core}/src/androidTest/assets/ttml/inherit_global_and_parent.xml (100%) rename library/{ => core}/src/androidTest/assets/ttml/inherit_multiple_styles.xml (100%) rename library/{ => core}/src/androidTest/assets/ttml/inherit_style.xml (100%) rename library/{ => core}/src/androidTest/assets/ttml/inline_style_attributes.xml (100%) rename library/{ => core}/src/androidTest/assets/ttml/multiple_regions.xml (100%) rename library/{ => core}/src/androidTest/assets/ttml/no_underline_linethrough.xml (100%) rename library/{ => core}/src/androidTest/assets/wav/sample.wav (100%) rename library/{ => core}/src/androidTest/assets/wav/sample.wav.0.dump (100%) rename library/{ => core}/src/androidTest/assets/wav/sample.wav.1.dump (100%) rename library/{ => core}/src/androidTest/assets/wav/sample.wav.2.dump (100%) rename library/{ => core}/src/androidTest/assets/wav/sample.wav.3.dump (100%) rename library/{ => core}/src/androidTest/assets/webm/vorbis_codec_private (100%) rename library/{ => core}/src/androidTest/assets/webvtt/empty (100%) rename library/{ => core}/src/androidTest/assets/webvtt/typical (100%) rename library/{ => core}/src/androidTest/assets/webvtt/typical_with_comments (100%) rename library/{ => core}/src/androidTest/assets/webvtt/typical_with_identifiers (100%) rename library/{ => core}/src/androidTest/assets/webvtt/with_bad_cue_header (100%) rename library/{ => core}/src/androidTest/assets/webvtt/with_css_complex_selectors (100%) rename library/{ => core}/src/androidTest/assets/webvtt/with_css_styles (100%) rename library/{ => core}/src/androidTest/assets/webvtt/with_positioning (100%) rename library/{ => core}/src/androidTest/assets/webvtt/with_tags (100%) rename library/{ => core}/src/androidTest/java/com/google/android/exoplayer2/CTest.java (100%) rename library/{ => core}/src/androidTest/java/com/google/android/exoplayer2/ExoPlayerTest.java (100%) rename library/{ => core}/src/androidTest/java/com/google/android/exoplayer2/FormatTest.java (100%) rename library/{ => core}/src/androidTest/java/com/google/android/exoplayer2/drm/DrmInitDataTest.java (100%) rename library/{ => core}/src/androidTest/java/com/google/android/exoplayer2/drm/OfflineLicenseHelperTest.java (100%) rename library/{ => core}/src/androidTest/java/com/google/android/exoplayer2/extractor/DefaultExtractorInputTest.java (100%) rename library/{ => core}/src/androidTest/java/com/google/android/exoplayer2/extractor/ExtractorTest.java (100%) rename library/{ => core}/src/androidTest/java/com/google/android/exoplayer2/extractor/flv/FlvExtractorTest.java (100%) rename library/{ => core}/src/androidTest/java/com/google/android/exoplayer2/extractor/mkv/DefaultEbmlReaderTest.java (100%) rename library/{ => core}/src/androidTest/java/com/google/android/exoplayer2/extractor/mkv/MatroskaExtractorTest.java (100%) rename library/{ => core}/src/androidTest/java/com/google/android/exoplayer2/extractor/mkv/VarintReaderTest.java (100%) rename library/{ => core}/src/androidTest/java/com/google/android/exoplayer2/extractor/mp3/Mp3ExtractorTest.java (100%) rename library/{ => core}/src/androidTest/java/com/google/android/exoplayer2/extractor/mp3/XingSeekerTest.java (100%) rename library/{ => core}/src/androidTest/java/com/google/android/exoplayer2/extractor/mp4/AtomParsersTest.java (100%) rename library/{ => core}/src/androidTest/java/com/google/android/exoplayer2/extractor/mp4/FragmentedMp4ExtractorTest.java (100%) rename library/{ => core}/src/androidTest/java/com/google/android/exoplayer2/extractor/mp4/Mp4ExtractorTest.java (100%) rename library/{ => core}/src/androidTest/java/com/google/android/exoplayer2/extractor/ogg/DefaultOggSeekerTest.java (100%) rename library/{ => core}/src/androidTest/java/com/google/android/exoplayer2/extractor/ogg/DefaultOggSeekerUtilMethodsTest.java (100%) rename library/{ => core}/src/androidTest/java/com/google/android/exoplayer2/extractor/ogg/OggExtractorTest.java (100%) rename library/{ => core}/src/androidTest/java/com/google/android/exoplayer2/extractor/ogg/OggPacketTest.java (100%) rename library/{ => core}/src/androidTest/java/com/google/android/exoplayer2/extractor/ogg/OggPageHeaderTest.java (100%) rename library/{ => core}/src/androidTest/java/com/google/android/exoplayer2/extractor/ogg/OggTestFile.java (100%) rename library/{ => core}/src/androidTest/java/com/google/android/exoplayer2/extractor/ogg/TestData.java (100%) rename library/{ => core}/src/androidTest/java/com/google/android/exoplayer2/extractor/ogg/VorbisBitArrayTest.java (100%) rename library/{ => core}/src/androidTest/java/com/google/android/exoplayer2/extractor/ogg/VorbisReaderTest.java (100%) rename library/{ => core}/src/androidTest/java/com/google/android/exoplayer2/extractor/ogg/VorbisUtilTest.java (100%) rename library/{ => core}/src/androidTest/java/com/google/android/exoplayer2/extractor/rawcc/RawCcExtractorTest.java (100%) rename library/{ => core}/src/androidTest/java/com/google/android/exoplayer2/extractor/ts/Ac3ExtractorTest.java (100%) rename library/{ => core}/src/androidTest/java/com/google/android/exoplayer2/extractor/ts/AdtsExtractorTest.java (100%) rename library/{ => core}/src/androidTest/java/com/google/android/exoplayer2/extractor/ts/AdtsReaderTest.java (100%) rename library/{ => core}/src/androidTest/java/com/google/android/exoplayer2/extractor/ts/PsExtractorTest.java (100%) rename library/{ => core}/src/androidTest/java/com/google/android/exoplayer2/extractor/ts/SectionReaderTest.java (100%) rename library/{ => core}/src/androidTest/java/com/google/android/exoplayer2/extractor/ts/TsExtractorTest.java (100%) rename library/{ => core}/src/androidTest/java/com/google/android/exoplayer2/extractor/wav/WavExtractorTest.java (100%) rename library/{ => core}/src/androidTest/java/com/google/android/exoplayer2/metadata/emsg/EventMessageDecoderTest.java (100%) rename library/{ => core}/src/androidTest/java/com/google/android/exoplayer2/metadata/emsg/EventMessageTest.java (100%) rename library/{ => core}/src/androidTest/java/com/google/android/exoplayer2/metadata/id3/ChapterFrameTest.java (100%) rename library/{ => core}/src/androidTest/java/com/google/android/exoplayer2/metadata/id3/ChapterTocFrameTest.java (100%) rename library/{ => core}/src/androidTest/java/com/google/android/exoplayer2/metadata/id3/Id3DecoderTest.java (100%) rename library/{ => core}/src/androidTest/java/com/google/android/exoplayer2/metadata/scte35/SpliceInfoDecoderTest.java (100%) rename library/{ => core}/src/androidTest/java/com/google/android/exoplayer2/source/ClippingMediaSourceTest.java (100%) rename library/{ => core}/src/androidTest/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestParserTest.java (100%) rename library/{ => core}/src/androidTest/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestTest.java (100%) rename library/{ => core}/src/androidTest/java/com/google/android/exoplayer2/source/dash/manifest/RangedUriTest.java (100%) rename library/{ => core}/src/androidTest/java/com/google/android/exoplayer2/source/dash/manifest/RepresentationTest.java (100%) rename library/{ => core}/src/androidTest/java/com/google/android/exoplayer2/source/dash/manifest/UrlTemplateTest.java (100%) rename library/{ => core}/src/androidTest/java/com/google/android/exoplayer2/source/hls/playlist/HlsMasterPlaylistParserTest.java (100%) rename library/{ => core}/src/androidTest/java/com/google/android/exoplayer2/source/hls/playlist/HlsMediaPlaylistParserTest.java (100%) rename library/{ => core}/src/androidTest/java/com/google/android/exoplayer2/source/smoothstreaming/manifest/SsManifestParserTest.java (100%) rename library/{ => core}/src/androidTest/java/com/google/android/exoplayer2/text/subrip/SubripDecoderTest.java (100%) rename library/{ => core}/src/androidTest/java/com/google/android/exoplayer2/text/ttml/TtmlDecoderTest.java (100%) rename library/{ => core}/src/androidTest/java/com/google/android/exoplayer2/text/ttml/TtmlRenderUtilTest.java (100%) rename library/{ => core}/src/androidTest/java/com/google/android/exoplayer2/text/ttml/TtmlStyleTest.java (100%) rename library/{ => core}/src/androidTest/java/com/google/android/exoplayer2/text/webvtt/CssParserTest.java (100%) rename library/{ => core}/src/androidTest/java/com/google/android/exoplayer2/text/webvtt/Mp4WebvttDecoderTest.java (100%) rename library/{ => core}/src/androidTest/java/com/google/android/exoplayer2/text/webvtt/WebvttCueParserTest.java (100%) rename library/{ => core}/src/androidTest/java/com/google/android/exoplayer2/text/webvtt/WebvttDecoderTest.java (100%) rename library/{ => core}/src/androidTest/java/com/google/android/exoplayer2/text/webvtt/WebvttSubtitleTest.java (100%) rename library/{ => core}/src/androidTest/java/com/google/android/exoplayer2/upstream/ByteArrayDataSourceTest.java (100%) rename library/{ => core}/src/androidTest/java/com/google/android/exoplayer2/upstream/DataSourceInputStreamTest.java (100%) rename library/{ => core}/src/androidTest/java/com/google/android/exoplayer2/upstream/cache/CacheDataSourceTest.java (100%) rename library/{ => core}/src/androidTest/java/com/google/android/exoplayer2/upstream/cache/CacheDataSourceTest2.java (100%) rename library/{ => core}/src/androidTest/java/com/google/android/exoplayer2/upstream/cache/CachedContentIndexTest.java (100%) rename library/{ => core}/src/androidTest/java/com/google/android/exoplayer2/upstream/cache/CachedRegionTrackerTest.java (100%) rename library/{ => core}/src/androidTest/java/com/google/android/exoplayer2/upstream/cache/SimpleCacheSpanTest.java (100%) rename library/{ => core}/src/androidTest/java/com/google/android/exoplayer2/upstream/cache/SimpleCacheTest.java (100%) rename library/{ => core}/src/androidTest/java/com/google/android/exoplayer2/upstream/crypto/AesFlushingCipherTest.java (100%) rename library/{ => core}/src/androidTest/java/com/google/android/exoplayer2/util/AtomicFileTest.java (100%) rename library/{ => core}/src/androidTest/java/com/google/android/exoplayer2/util/ColorParserTest.java (100%) rename library/{ => core}/src/androidTest/java/com/google/android/exoplayer2/util/NalUnitUtilTest.java (100%) rename library/{ => core}/src/androidTest/java/com/google/android/exoplayer2/util/ParsableByteArrayTest.java (100%) rename library/{ => core}/src/androidTest/java/com/google/android/exoplayer2/util/ParsableNalUnitBitArrayTest.java (100%) rename library/{ => core}/src/androidTest/java/com/google/android/exoplayer2/util/ReusableBufferedOutputStreamTest.java (100%) rename library/{ => core}/src/androidTest/java/com/google/android/exoplayer2/util/UriUtilTest.java (100%) rename library/{ => core}/src/androidTest/java/com/google/android/exoplayer2/util/UtilTest.java (100%) create mode 100644 library/core/src/main/AndroidManifest.xml rename library/{ => core}/src/main/java/com/google/android/exoplayer2/BaseRenderer.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/C.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/DefaultLoadControl.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/ExoPlaybackException.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/ExoPlayer.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/ExoPlayerFactory.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/ExoPlayerLibraryInfo.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/Format.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/FormatHolder.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/IllegalSeekPositionException.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/LoadControl.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/ParserException.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/Renderer.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/RendererCapabilities.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/RendererConfiguration.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/Timeline.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/audio/Ac3Util.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/audio/AudioCapabilities.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/audio/AudioCapabilitiesReceiver.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/audio/AudioDecoderException.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/audio/AudioProcessor.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/audio/AudioRendererEventListener.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/audio/AudioTrack.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/audio/ChannelMappingAudioProcessor.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/audio/DtsUtil.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/audio/MediaCodecAudioRenderer.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/audio/ResamplingAudioProcessor.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/audio/SimpleDecoderAudioRenderer.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/decoder/Buffer.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/decoder/CryptoInfo.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/decoder/Decoder.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/decoder/DecoderCounters.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/decoder/DecoderInputBuffer.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/decoder/OutputBuffer.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/decoder/SimpleDecoder.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/decoder/SimpleOutputBuffer.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/drm/DecryptionException.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/drm/DefaultDrmSessionManager.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/drm/DrmInitData.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/drm/DrmSession.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/drm/DrmSessionManager.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/drm/ExoMediaCrypto.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/drm/ExoMediaDrm.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/drm/FrameworkMediaCrypto.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/drm/FrameworkMediaDrm.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/drm/HttpMediaDrmCallback.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/drm/KeysExpiredException.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/drm/MediaDrmCallback.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/drm/OfflineLicenseHelper.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/drm/UnsupportedDrmException.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/drm/WidevineUtil.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/extractor/ChunkIndex.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/extractor/DefaultExtractorInput.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/extractor/DefaultExtractorsFactory.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/extractor/DefaultTrackOutput.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/extractor/DummyTrackOutput.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/extractor/Extractor.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/extractor/ExtractorInput.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/extractor/ExtractorOutput.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/extractor/ExtractorsFactory.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/extractor/GaplessInfoHolder.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/extractor/MpegAudioHeader.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/extractor/PositionHolder.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/extractor/SeekMap.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/extractor/TrackOutput.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/extractor/flv/AudioTagPayloadReader.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/extractor/flv/FlvExtractor.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/extractor/flv/ScriptTagPayloadReader.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/extractor/flv/TagPayloadReader.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/extractor/flv/VideoTagPayloadReader.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/extractor/mkv/DefaultEbmlReader.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/extractor/mkv/EbmlReader.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/extractor/mkv/EbmlReaderOutput.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/extractor/mkv/MatroskaExtractor.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/extractor/mkv/Sniffer.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/extractor/mkv/VarintReader.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/extractor/mp3/ConstantBitrateSeeker.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/extractor/mp3/Mp3Extractor.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/extractor/mp3/VbriSeeker.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/extractor/mp3/XingSeeker.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/extractor/mp4/Atom.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/extractor/mp4/AtomParsers.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/extractor/mp4/DefaultSampleValues.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/extractor/mp4/FixedSampleSizeRechunker.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/extractor/mp4/FragmentedMp4Extractor.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/extractor/mp4/MetadataUtil.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/extractor/mp4/Mp4Extractor.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/extractor/mp4/PsshAtomUtil.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/extractor/mp4/Sniffer.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/extractor/mp4/Track.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/extractor/mp4/TrackEncryptionBox.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/extractor/mp4/TrackFragment.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/extractor/mp4/TrackSampleTable.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/extractor/ogg/DefaultOggSeeker.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/extractor/ogg/FlacReader.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/extractor/ogg/OggExtractor.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/extractor/ogg/OggPacket.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/extractor/ogg/OggPageHeader.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/extractor/ogg/OggSeeker.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/extractor/ogg/OpusReader.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/extractor/ogg/StreamReader.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/extractor/ogg/VorbisBitArray.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/extractor/ogg/VorbisReader.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/extractor/ogg/VorbisUtil.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/extractor/rawcc/RawCcExtractor.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/extractor/ts/Ac3Extractor.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/extractor/ts/Ac3Reader.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/extractor/ts/AdtsExtractor.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/extractor/ts/AdtsReader.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/extractor/ts/DefaultTsPayloadReaderFactory.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/extractor/ts/DtsReader.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/extractor/ts/ElementaryStreamReader.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/extractor/ts/H262Reader.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/extractor/ts/H264Reader.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/extractor/ts/H265Reader.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/extractor/ts/Id3Reader.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/extractor/ts/MpegAudioReader.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/extractor/ts/NalUnitTargetBuffer.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/extractor/ts/PesReader.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/extractor/ts/PsExtractor.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/extractor/ts/SectionPayloadReader.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/extractor/ts/SectionReader.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/extractor/ts/SeiReader.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/extractor/ts/SpliceInfoSectionReader.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/extractor/ts/TsExtractor.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/extractor/ts/TsPayloadReader.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/extractor/wav/WavExtractor.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/extractor/wav/WavHeader.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/extractor/wav/WavHeaderReader.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecInfo.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecSelector.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecUtil.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/metadata/Metadata.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/metadata/MetadataDecoder.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/metadata/MetadataDecoderException.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/metadata/MetadataDecoderFactory.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/metadata/MetadataInputBuffer.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/metadata/MetadataRenderer.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/metadata/emsg/EventMessage.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/metadata/emsg/EventMessageDecoder.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/metadata/id3/ApicFrame.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/metadata/id3/BinaryFrame.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/metadata/id3/ChapterFrame.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/metadata/id3/ChapterTocFrame.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/metadata/id3/CommentFrame.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/metadata/id3/GeobFrame.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/metadata/id3/Id3Decoder.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/metadata/id3/Id3Frame.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/metadata/id3/PrivFrame.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/metadata/id3/TextInformationFrame.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/metadata/id3/UrlLinkFrame.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/metadata/scte35/PrivateCommand.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/metadata/scte35/SpliceCommand.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/metadata/scte35/SpliceInfoDecoder.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/metadata/scte35/SpliceInsertCommand.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/metadata/scte35/SpliceNullCommand.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/metadata/scte35/SpliceScheduleCommand.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/metadata/scte35/TimeSignalCommand.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/source/AdaptiveMediaSourceEventListener.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/source/BehindLiveWindowException.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/source/ClippingMediaPeriod.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/source/ClippingMediaSource.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/source/CompositeSequenceableLoader.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/source/ConcatenatingMediaSource.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/source/EmptySampleStream.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/source/ExtractorMediaPeriod.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/source/ExtractorMediaSource.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/source/LoopingMediaSource.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/source/MediaPeriod.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/source/MediaSource.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/source/MergingMediaPeriod.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/source/MergingMediaSource.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/source/SampleStream.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/source/SequenceableLoader.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/source/SinglePeriodTimeline.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/source/SingleSampleMediaPeriod.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/source/SingleSampleMediaSource.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/source/TrackGroup.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/source/TrackGroupArray.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/source/UnrecognizedInputFormatException.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/source/chunk/BaseMediaChunk.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/source/chunk/BaseMediaChunkOutput.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/source/chunk/Chunk.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/source/chunk/ChunkExtractorWrapper.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/source/chunk/ChunkHolder.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/source/chunk/ChunkSampleStream.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/source/chunk/ChunkSource.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/source/chunk/ChunkedTrackBlacklistUtil.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/source/chunk/ContainerMediaChunk.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/source/chunk/DataChunk.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/source/chunk/InitializationChunk.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/source/chunk/MediaChunk.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/source/chunk/SingleSampleMediaChunk.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/source/dash/DashChunkSource.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaPeriod.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaSource.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/source/dash/DashSegmentIndex.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/source/dash/DashUtil.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/source/dash/DashWrappingSegmentIndex.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/source/dash/DefaultDashChunkSource.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/source/dash/manifest/AdaptationSet.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/source/dash/manifest/DashManifest.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestParser.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/source/dash/manifest/Period.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/source/dash/manifest/RangedUri.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/source/dash/manifest/Representation.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/source/dash/manifest/RepresentationKey.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/source/dash/manifest/SchemeValuePair.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/source/dash/manifest/SegmentBase.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/source/dash/manifest/SingleSegmentIndex.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/source/dash/manifest/UrlTemplate.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/source/dash/manifest/UtcTimingElement.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/source/hls/Aes128DataSource.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/source/hls/DefaultHlsDataSourceFactory.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/source/hls/HlsChunkSource.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/source/hls/HlsDataSourceFactory.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/source/hls/HlsManifest.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaChunk.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaPeriod.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaSource.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/source/hls/HlsSampleStream.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/source/hls/HlsSampleStreamWrapper.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/source/hls/TimestampAdjusterProvider.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/source/hls/WebvttExtractor.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsMasterPlaylist.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsMediaPlaylist.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylist.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylistParser.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylistTracker.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/DefaultSsChunkSource.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsChunkSource.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsMediaPeriod.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsMediaSource.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/manifest/SsManifest.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/manifest/SsManifestParser.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/text/CaptionStyleCompat.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/text/Cue.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/text/SimpleSubtitleDecoder.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/text/SimpleSubtitleOutputBuffer.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/text/Subtitle.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/text/SubtitleDecoder.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/text/SubtitleDecoderException.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/text/SubtitleDecoderFactory.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/text/SubtitleInputBuffer.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/text/SubtitleOutputBuffer.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/text/TextRenderer.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/text/cea/Cea608Decoder.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/text/cea/Cea708Cue.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/text/cea/Cea708Decoder.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/text/cea/CeaDecoder.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/text/cea/CeaOutputBuffer.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/text/cea/CeaSubtitle.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/text/cea/CeaUtil.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/text/subrip/SubripDecoder.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/text/subrip/SubripSubtitle.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/text/ttml/TtmlDecoder.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/text/ttml/TtmlNode.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/text/ttml/TtmlRegion.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/text/ttml/TtmlRenderUtil.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/text/ttml/TtmlStyle.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/text/ttml/TtmlSubtitle.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/text/tx3g/Tx3gDecoder.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/text/tx3g/Tx3gSubtitle.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/text/webvtt/CssParser.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/text/webvtt/Mp4WebvttDecoder.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/text/webvtt/Mp4WebvttSubtitle.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/text/webvtt/WebvttCssStyle.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/text/webvtt/WebvttCue.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/text/webvtt/WebvttCueParser.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/text/webvtt/WebvttDecoder.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/text/webvtt/WebvttParserUtil.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/text/webvtt/WebvttSubtitle.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/trackselection/AdaptiveTrackSelection.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/trackselection/BaseTrackSelection.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelector.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/trackselection/FixedTrackSelection.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/trackselection/MappingTrackSelector.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/trackselection/RandomTrackSelection.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/trackselection/TrackSelection.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/trackselection/TrackSelectionArray.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/trackselection/TrackSelector.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/trackselection/TrackSelectorResult.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/ui/AspectRatioFrameLayout.java (99%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/ui/DebugTextViewHelper.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/ui/PlaybackControlView.java (99%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/ui/SimpleExoPlayerView.java (99%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/ui/SubtitlePainter.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/ui/SubtitleView.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/upstream/Allocation.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/upstream/Allocator.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/upstream/AssetDataSource.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/upstream/BandwidthMeter.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/upstream/ByteArrayDataSink.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/upstream/ByteArrayDataSource.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/upstream/ContentDataSource.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/upstream/DataSink.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/upstream/DataSource.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/upstream/DataSourceException.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/upstream/DataSourceInputStream.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/upstream/DataSpec.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/upstream/DefaultAllocator.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/upstream/DefaultBandwidthMeter.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/upstream/DefaultDataSource.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/upstream/DefaultDataSourceFactory.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/upstream/DefaultHttpDataSource.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/upstream/DefaultHttpDataSourceFactory.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/upstream/FileDataSource.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/upstream/FileDataSourceFactory.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/upstream/HttpDataSource.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/upstream/Loader.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/upstream/LoaderErrorThrower.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/upstream/ParsingLoadable.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/upstream/PriorityDataSource.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/upstream/PriorityDataSourceFactory.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/upstream/RawResourceDataSource.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/upstream/TeeDataSource.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/upstream/TransferListener.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/upstream/UdpDataSource.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/upstream/cache/Cache.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheDataSink.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheDataSinkFactory.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheDataSource.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheDataSourceFactory.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheEvictor.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheSpan.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/upstream/cache/CachedContent.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/upstream/cache/CachedContentIndex.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/upstream/cache/CachedRegionTracker.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/upstream/cache/LeastRecentlyUsedCacheEvictor.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/upstream/cache/NoOpCacheEvictor.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/upstream/cache/SimpleCache.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/upstream/cache/SimpleCacheSpan.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/upstream/crypto/AesCipherDataSink.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/upstream/crypto/AesCipherDataSource.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/upstream/crypto/AesFlushingCipher.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/upstream/crypto/CryptoUtil.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/util/Assertions.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/util/AtomicFile.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/util/Clock.java (100%) create mode 100644 library/core/src/main/java/com/google/android/exoplayer2/util/ClosedSource.java rename library/{ => core}/src/main/java/com/google/android/exoplayer2/util/CodecSpecificDataUtil.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/util/ColorParser.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/util/ConditionVariable.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/util/FlacStreamInfo.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/util/LibraryLoader.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/util/LongArray.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/util/MediaClock.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/util/MimeTypes.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/util/NalUnitUtil.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/util/ParsableBitArray.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/util/ParsableByteArray.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/util/ParsableNalUnitBitArray.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/util/Predicate.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/util/PriorityHandlerThread.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/util/PriorityTaskManager.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/util/ReusableBufferedOutputStream.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/util/SlidingPercentile.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/util/StandaloneMediaClock.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/util/SystemClock.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/util/TimestampAdjuster.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/util/TraceUtil.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/util/UriUtil.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/util/Util.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/util/XmlPullParserUtil.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/video/AvcConfig.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/video/HevcConfig.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/video/VideoFrameReleaseTimeHelper.java (100%) rename library/{ => core}/src/main/java/com/google/android/exoplayer2/video/VideoRendererEventListener.java (100%) rename library/{ => core}/src/main/javadoc/com/google/android/exoplayer2/doc-files/exoplayer-threading-model.svg (100%) rename library/{ => core}/src/main/javadoc/com/google/android/exoplayer2/doc-files/renderer-states.svg (100%) rename library/{ => core}/src/main/javadoc/com/google/android/exoplayer2/doc-files/timeline-advanced.svg (100%) rename library/{ => core}/src/main/javadoc/com/google/android/exoplayer2/doc-files/timeline-live-indefinite.svg (100%) rename library/{ => core}/src/main/javadoc/com/google/android/exoplayer2/doc-files/timeline-live-limited.svg (100%) rename library/{ => core}/src/main/javadoc/com/google/android/exoplayer2/doc-files/timeline-live-multi-period.svg (100%) rename library/{ => core}/src/main/javadoc/com/google/android/exoplayer2/doc-files/timeline-period.svg (100%) rename library/{ => core}/src/main/javadoc/com/google/android/exoplayer2/doc-files/timeline-playlist.svg (100%) rename library/{ => core}/src/main/javadoc/com/google/android/exoplayer2/doc-files/timeline-single-file.svg (100%) rename library/{ => core}/src/main/javadoc/com/google/android/exoplayer2/doc-files/timeline-window.svg (100%) create mode 100644 library/core/src/main/proguard-rules.txt rename library/{ => core}/src/main/res/drawable-anydpi-v21/exo_controls_fastforward.xml (100%) rename library/{ => core}/src/main/res/drawable-anydpi-v21/exo_controls_next.xml (100%) rename library/{ => core}/src/main/res/drawable-anydpi-v21/exo_controls_pause.xml (100%) rename library/{ => core}/src/main/res/drawable-anydpi-v21/exo_controls_play.xml (100%) rename library/{ => core}/src/main/res/drawable-anydpi-v21/exo_controls_previous.xml (100%) rename library/{ => core}/src/main/res/drawable-anydpi-v21/exo_controls_rewind.xml (100%) rename library/{ => core}/src/main/res/drawable-hdpi/exo_controls_fastforward.png (100%) rename library/{ => core}/src/main/res/drawable-hdpi/exo_controls_next.png (100%) rename library/{ => core}/src/main/res/drawable-hdpi/exo_controls_pause.png (100%) rename library/{ => core}/src/main/res/drawable-hdpi/exo_controls_play.png (100%) rename library/{ => core}/src/main/res/drawable-hdpi/exo_controls_previous.png (100%) rename library/{ => core}/src/main/res/drawable-hdpi/exo_controls_rewind.png (100%) rename library/{ => core}/src/main/res/drawable-ldpi/exo_controls_fastforward.png (100%) rename library/{ => core}/src/main/res/drawable-ldpi/exo_controls_next.png (100%) rename library/{ => core}/src/main/res/drawable-ldpi/exo_controls_pause.png (100%) rename library/{ => core}/src/main/res/drawable-ldpi/exo_controls_play.png (100%) rename library/{ => core}/src/main/res/drawable-ldpi/exo_controls_previous.png (100%) rename library/{ => core}/src/main/res/drawable-ldpi/exo_controls_rewind.png (100%) rename library/{ => core}/src/main/res/drawable-mdpi/exo_controls_fastforward.png (100%) rename library/{ => core}/src/main/res/drawable-mdpi/exo_controls_next.png (100%) rename library/{ => core}/src/main/res/drawable-mdpi/exo_controls_pause.png (100%) rename library/{ => core}/src/main/res/drawable-mdpi/exo_controls_play.png (100%) rename library/{ => core}/src/main/res/drawable-mdpi/exo_controls_previous.png (100%) rename library/{ => core}/src/main/res/drawable-mdpi/exo_controls_rewind.png (100%) rename library/{ => core}/src/main/res/drawable-xhdpi/exo_controls_fastforward.png (100%) rename library/{ => core}/src/main/res/drawable-xhdpi/exo_controls_next.png (100%) rename library/{ => core}/src/main/res/drawable-xhdpi/exo_controls_pause.png (100%) rename library/{ => core}/src/main/res/drawable-xhdpi/exo_controls_play.png (100%) rename library/{ => core}/src/main/res/drawable-xhdpi/exo_controls_previous.png (100%) rename library/{ => core}/src/main/res/drawable-xhdpi/exo_controls_rewind.png (100%) rename library/{ => core}/src/main/res/drawable-xxhdpi/exo_controls_fastforward.png (100%) rename library/{ => core}/src/main/res/drawable-xxhdpi/exo_controls_next.png (100%) rename library/{ => core}/src/main/res/drawable-xxhdpi/exo_controls_pause.png (100%) rename library/{ => core}/src/main/res/drawable-xxhdpi/exo_controls_play.png (100%) rename library/{ => core}/src/main/res/drawable-xxhdpi/exo_controls_previous.png (100%) rename library/{ => core}/src/main/res/drawable-xxhdpi/exo_controls_rewind.png (100%) rename library/{ => core}/src/main/res/layout/exo_playback_control_view.xml (100%) rename library/{ => core}/src/main/res/layout/exo_simple_player_view.xml (100%) rename library/{ => core}/src/main/res/values-af/strings.xml (100%) rename library/{ => core}/src/main/res/values-am/strings.xml (100%) rename library/{ => core}/src/main/res/values-ar/strings.xml (100%) rename library/{ => core}/src/main/res/values-az-rAZ/strings.xml (100%) rename library/{ => core}/src/main/res/values-b+sr+Latn/strings.xml (100%) rename library/{ => core}/src/main/res/values-be-rBY/strings.xml (100%) rename library/{ => core}/src/main/res/values-bg/strings.xml (100%) rename library/{ => core}/src/main/res/values-bn-rBD/strings.xml (100%) rename library/{ => core}/src/main/res/values-bs-rBA/strings.xml (100%) rename library/{ => core}/src/main/res/values-ca/strings.xml (100%) rename library/{ => core}/src/main/res/values-cs/strings.xml (100%) rename library/{ => core}/src/main/res/values-da/strings.xml (100%) rename library/{ => core}/src/main/res/values-de/strings.xml (100%) rename library/{ => core}/src/main/res/values-el/strings.xml (100%) rename library/{ => core}/src/main/res/values-en-rAU/strings.xml (100%) rename library/{ => core}/src/main/res/values-en-rGB/strings.xml (100%) rename library/{ => core}/src/main/res/values-en-rIN/strings.xml (100%) rename library/{ => core}/src/main/res/values-es-rUS/strings.xml (100%) rename library/{ => core}/src/main/res/values-es/strings.xml (100%) rename library/{ => core}/src/main/res/values-et-rEE/strings.xml (100%) rename library/{ => core}/src/main/res/values-eu-rES/strings.xml (100%) rename library/{ => core}/src/main/res/values-fa/strings.xml (100%) rename library/{ => core}/src/main/res/values-fi/strings.xml (100%) rename library/{ => core}/src/main/res/values-fr-rCA/strings.xml (100%) rename library/{ => core}/src/main/res/values-fr/strings.xml (100%) rename library/{ => core}/src/main/res/values-gl-rES/strings.xml (100%) rename library/{ => core}/src/main/res/values-gu-rIN/strings.xml (100%) rename library/{ => core}/src/main/res/values-hi/strings.xml (100%) rename library/{ => core}/src/main/res/values-hr/strings.xml (100%) rename library/{ => core}/src/main/res/values-hu/strings.xml (100%) rename library/{ => core}/src/main/res/values-hy-rAM/strings.xml (100%) rename library/{ => core}/src/main/res/values-in/strings.xml (100%) rename library/{ => core}/src/main/res/values-is-rIS/strings.xml (100%) rename library/{ => core}/src/main/res/values-it/strings.xml (100%) rename library/{ => core}/src/main/res/values-iw/strings.xml (100%) rename library/{ => core}/src/main/res/values-ja/strings.xml (100%) rename library/{ => core}/src/main/res/values-ka-rGE/strings.xml (100%) rename library/{ => core}/src/main/res/values-kk-rKZ/strings.xml (100%) rename library/{ => core}/src/main/res/values-km-rKH/strings.xml (100%) rename library/{ => core}/src/main/res/values-kn-rIN/strings.xml (100%) rename library/{ => core}/src/main/res/values-ko/strings.xml (100%) rename library/{ => core}/src/main/res/values-ky-rKG/strings.xml (100%) rename library/{ => core}/src/main/res/values-lo-rLA/strings.xml (100%) rename library/{ => core}/src/main/res/values-lt/strings.xml (100%) rename library/{ => core}/src/main/res/values-lv/strings.xml (100%) rename library/{ => core}/src/main/res/values-mk-rMK/strings.xml (100%) rename library/{ => core}/src/main/res/values-ml-rIN/strings.xml (100%) rename library/{ => core}/src/main/res/values-mn-rMN/strings.xml (100%) rename library/{ => core}/src/main/res/values-mr-rIN/strings.xml (100%) rename library/{ => core}/src/main/res/values-ms-rMY/strings.xml (100%) rename library/{ => core}/src/main/res/values-my-rMM/strings.xml (100%) rename library/{ => core}/src/main/res/values-nb/strings.xml (100%) rename library/{ => core}/src/main/res/values-ne-rNP/strings.xml (100%) rename library/{ => core}/src/main/res/values-nl/strings.xml (100%) rename library/{ => core}/src/main/res/values-pa-rIN/strings.xml (100%) rename library/{ => core}/src/main/res/values-pl/strings.xml (100%) rename library/{ => core}/src/main/res/values-pt-rBR/strings.xml (100%) rename library/{ => core}/src/main/res/values-pt-rPT/strings.xml (100%) rename library/{ => core}/src/main/res/values-pt/strings.xml (100%) rename library/{ => core}/src/main/res/values-ro/strings.xml (100%) rename library/{ => core}/src/main/res/values-ru/strings.xml (100%) rename library/{ => core}/src/main/res/values-si-rLK/strings.xml (100%) rename library/{ => core}/src/main/res/values-sk/strings.xml (100%) rename library/{ => core}/src/main/res/values-sl/strings.xml (100%) rename library/{ => core}/src/main/res/values-sq-rAL/strings.xml (100%) rename library/{ => core}/src/main/res/values-sr/strings.xml (100%) rename library/{ => core}/src/main/res/values-sv/strings.xml (100%) rename library/{ => core}/src/main/res/values-sw/strings.xml (100%) rename library/{ => core}/src/main/res/values-ta-rIN/strings.xml (100%) rename library/{ => core}/src/main/res/values-te-rIN/strings.xml (100%) rename library/{ => core}/src/main/res/values-th/strings.xml (100%) rename library/{ => core}/src/main/res/values-tl/strings.xml (100%) rename library/{ => core}/src/main/res/values-tr/strings.xml (100%) rename library/{ => core}/src/main/res/values-uk/strings.xml (100%) rename library/{ => core}/src/main/res/values-ur-rPK/strings.xml (100%) rename library/{ => core}/src/main/res/values-uz-rUZ/strings.xml (100%) rename library/{ => core}/src/main/res/values-v11/styles.xml (100%) rename library/{ => core}/src/main/res/values-vi/strings.xml (100%) rename library/{ => core}/src/main/res/values-zh-rCN/strings.xml (100%) rename library/{ => core}/src/main/res/values-zh-rHK/strings.xml (100%) rename library/{ => core}/src/main/res/values-zh-rTW/strings.xml (100%) rename library/{ => core}/src/main/res/values-zu/strings.xml (100%) rename library/{ => core}/src/main/res/values/attrs.xml (100%) rename library/{ => core}/src/main/res/values/constants.xml (100%) rename library/{ => core}/src/main/res/values/ids.xml (100%) rename library/{ => core}/src/main/res/values/strings.xml (100%) rename library/{ => core}/src/main/res/values/styles.xml (100%) diff --git a/extensions/cronet/build.gradle b/extensions/cronet/build.gradle index f031a9dc48..47d4cbef98 100644 --- a/extensions/cronet/build.gradle +++ b/extensions/cronet/build.gradle @@ -29,7 +29,7 @@ android { } dependencies { - compile project(':library') + compile project(':library-core') compile files('libs/cronet_api.jar') compile files('libs/cronet_impl_common_java.jar') compile files('libs/cronet_impl_native_java.jar') diff --git a/extensions/ffmpeg/build.gradle b/extensions/ffmpeg/build.gradle index a6523788cb..be8ad761bb 100644 --- a/extensions/ffmpeg/build.gradle +++ b/extensions/ffmpeg/build.gradle @@ -30,5 +30,5 @@ android { } dependencies { - compile project(':library') + compile project(':library-core') } diff --git a/extensions/flac/build.gradle b/extensions/flac/build.gradle index 1c23b9987c..33f8266deb 100644 --- a/extensions/flac/build.gradle +++ b/extensions/flac/build.gradle @@ -30,7 +30,7 @@ android { } dependencies { - compile project(':library') + compile project(':library-core') androidTestCompile project(':testutils') } diff --git a/extensions/gvr/build.gradle b/extensions/gvr/build.gradle index 5156cf0540..0e7a478186 100644 --- a/extensions/gvr/build.gradle +++ b/extensions/gvr/build.gradle @@ -24,7 +24,7 @@ android { } dependencies { - compile project(':library') + compile project(':library-core') compile 'com.google.vr:sdk-audio:1.30.0' } diff --git a/extensions/okhttp/build.gradle b/extensions/okhttp/build.gradle index 3a2daefb8f..3679ae8877 100644 --- a/extensions/okhttp/build.gradle +++ b/extensions/okhttp/build.gradle @@ -29,7 +29,7 @@ android { } dependencies { - compile project(':library') + compile project(':library-core') compile('com.squareup.okhttp3:okhttp:3.6.0') { exclude group: 'org.json' } diff --git a/extensions/opus/build.gradle b/extensions/opus/build.gradle index a6523788cb..be8ad761bb 100644 --- a/extensions/opus/build.gradle +++ b/extensions/opus/build.gradle @@ -30,5 +30,5 @@ android { } dependencies { - compile project(':library') + compile project(':library-core') } diff --git a/extensions/vp9/build.gradle b/extensions/vp9/build.gradle index 91d80f4970..5c72f46a9f 100644 --- a/extensions/vp9/build.gradle +++ b/extensions/vp9/build.gradle @@ -30,6 +30,6 @@ android { } dependencies { - compile project(':library') + compile project(':library-core') } diff --git a/library/build.gradle b/library/all/build.gradle similarity index 74% rename from library/build.gradle rename to library/all/build.gradle index abca404cfa..0799a184f3 100644 --- a/library/build.gradle +++ b/library/all/build.gradle @@ -22,29 +22,11 @@ android { defaultConfig { minSdkVersion project.ext.minSdkVersion targetSdkVersion project.ext.targetSdkVersion - consumerProguardFiles 'proguard-rules.txt' - } - - buildTypes { - // Re-enable test coverage when the following issue is fixed: - // https://code.google.com/p/android/issues/detail?id=226070 - // debug { - // testCoverageEnabled = true - // } - } - - sourceSets { - androidTest { - java.srcDirs += "../testutils/src/main/java/" - } } } dependencies { - compile 'com.android.support:support-annotations:25.2.0' - androidTestCompile 'com.google.dexmaker:dexmaker:1.2' - androidTestCompile 'com.google.dexmaker:dexmaker-mockito:1.2' - androidTestCompile 'org.mockito:mockito-core:1.9.5' + compile project(':library-core') } android.libraryVariants.all { variant -> @@ -82,6 +64,6 @@ android.libraryVariants.all { variant -> ext { releaseArtifact = 'exoplayer' - releaseDescription = 'The ExoPlayer library.' + releaseDescription = 'The ExoPlayer library (all modules).' } -apply from: '../publish.gradle' +apply from: '../../publish.gradle' diff --git a/library/src/main/AndroidManifest.xml b/library/all/src/main/AndroidManifest.xml similarity index 100% rename from library/src/main/AndroidManifest.xml rename to library/all/src/main/AndroidManifest.xml diff --git a/library/core/build.gradle b/library/core/build.gradle new file mode 100644 index 0000000000..980d5ffe2c --- /dev/null +++ b/library/core/build.gradle @@ -0,0 +1,52 @@ +// Copyright (C) 2016 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +apply plugin: 'com.android.library' + +android { + compileSdkVersion project.ext.compileSdkVersion + buildToolsVersion project.ext.buildToolsVersion + + defaultConfig { + minSdkVersion project.ext.minSdkVersion + targetSdkVersion project.ext.targetSdkVersion + consumerProguardFiles 'proguard-rules.txt' + } + + buildTypes { + // Re-enable test coverage when the following issue is fixed: + // https://code.google.com/p/android/issues/detail?id=226070 + // debug { + // testCoverageEnabled = true + // } + } + + sourceSets { + androidTest { + java.srcDirs += "../../testutils/src/main/java/" + } + } +} + +dependencies { + compile 'com.android.support:support-annotations:25.2.0' + androidTestCompile 'com.google.dexmaker:dexmaker:1.2' + androidTestCompile 'com.google.dexmaker:dexmaker-mockito:1.2' + androidTestCompile 'org.mockito:mockito-core:1.9.5' +} + +ext { + releaseArtifact = 'exoplayer-core' + releaseDescription = 'The ExoPlayer library core module.' +} +apply from: '../../publish.gradle' diff --git a/library/proguard-rules.txt b/library/core/proguard-rules.txt similarity index 100% rename from library/proguard-rules.txt rename to library/core/proguard-rules.txt diff --git a/library/src/androidTest/AndroidManifest.xml b/library/core/src/androidTest/AndroidManifest.xml similarity index 90% rename from library/src/androidTest/AndroidManifest.xml rename to library/core/src/androidTest/AndroidManifest.xml index d8c7a2ce9f..9eab386b51 100644 --- a/library/src/androidTest/AndroidManifest.xml +++ b/library/core/src/androidTest/AndroidManifest.xml @@ -16,7 +16,7 @@ + package="com.google.android.exoplayer2.core.test"> @@ -27,7 +27,7 @@ diff --git a/library/src/androidTest/assets/dash/sample_mpd_1 b/library/core/src/androidTest/assets/dash/sample_mpd_1 similarity index 100% rename from library/src/androidTest/assets/dash/sample_mpd_1 rename to library/core/src/androidTest/assets/dash/sample_mpd_1 diff --git a/library/src/androidTest/assets/dash/sample_mpd_2_unknown_mime_type b/library/core/src/androidTest/assets/dash/sample_mpd_2_unknown_mime_type similarity index 100% rename from library/src/androidTest/assets/dash/sample_mpd_2_unknown_mime_type rename to library/core/src/androidTest/assets/dash/sample_mpd_2_unknown_mime_type diff --git a/library/src/androidTest/assets/dash/sample_mpd_3_segment_template b/library/core/src/androidTest/assets/dash/sample_mpd_3_segment_template similarity index 100% rename from library/src/androidTest/assets/dash/sample_mpd_3_segment_template rename to library/core/src/androidTest/assets/dash/sample_mpd_3_segment_template diff --git a/library/src/androidTest/assets/flv/sample.flv b/library/core/src/androidTest/assets/flv/sample.flv similarity index 100% rename from library/src/androidTest/assets/flv/sample.flv rename to library/core/src/androidTest/assets/flv/sample.flv diff --git a/library/src/androidTest/assets/flv/sample.flv.0.dump b/library/core/src/androidTest/assets/flv/sample.flv.0.dump similarity index 100% rename from library/src/androidTest/assets/flv/sample.flv.0.dump rename to library/core/src/androidTest/assets/flv/sample.flv.0.dump diff --git a/library/src/androidTest/assets/mkv/sample.mkv b/library/core/src/androidTest/assets/mkv/sample.mkv similarity index 100% rename from library/src/androidTest/assets/mkv/sample.mkv rename to library/core/src/androidTest/assets/mkv/sample.mkv diff --git a/library/src/androidTest/assets/mkv/sample.mkv.0.dump b/library/core/src/androidTest/assets/mkv/sample.mkv.0.dump similarity index 100% rename from library/src/androidTest/assets/mkv/sample.mkv.0.dump rename to library/core/src/androidTest/assets/mkv/sample.mkv.0.dump diff --git a/library/src/androidTest/assets/mkv/sample.mkv.1.dump b/library/core/src/androidTest/assets/mkv/sample.mkv.1.dump similarity index 100% rename from library/src/androidTest/assets/mkv/sample.mkv.1.dump rename to library/core/src/androidTest/assets/mkv/sample.mkv.1.dump diff --git a/library/src/androidTest/assets/mkv/sample.mkv.2.dump b/library/core/src/androidTest/assets/mkv/sample.mkv.2.dump similarity index 100% rename from library/src/androidTest/assets/mkv/sample.mkv.2.dump rename to library/core/src/androidTest/assets/mkv/sample.mkv.2.dump diff --git a/library/src/androidTest/assets/mkv/sample.mkv.3.dump b/library/core/src/androidTest/assets/mkv/sample.mkv.3.dump similarity index 100% rename from library/src/androidTest/assets/mkv/sample.mkv.3.dump rename to library/core/src/androidTest/assets/mkv/sample.mkv.3.dump diff --git a/library/src/androidTest/assets/mkv/subsample_encrypted_altref.webm b/library/core/src/androidTest/assets/mkv/subsample_encrypted_altref.webm similarity index 100% rename from library/src/androidTest/assets/mkv/subsample_encrypted_altref.webm rename to library/core/src/androidTest/assets/mkv/subsample_encrypted_altref.webm diff --git a/library/src/androidTest/assets/mkv/subsample_encrypted_altref.webm.0.dump b/library/core/src/androidTest/assets/mkv/subsample_encrypted_altref.webm.0.dump similarity index 100% rename from library/src/androidTest/assets/mkv/subsample_encrypted_altref.webm.0.dump rename to library/core/src/androidTest/assets/mkv/subsample_encrypted_altref.webm.0.dump diff --git a/library/src/androidTest/assets/mkv/subsample_encrypted_noaltref.webm b/library/core/src/androidTest/assets/mkv/subsample_encrypted_noaltref.webm similarity index 100% rename from library/src/androidTest/assets/mkv/subsample_encrypted_noaltref.webm rename to library/core/src/androidTest/assets/mkv/subsample_encrypted_noaltref.webm diff --git a/library/src/androidTest/assets/mkv/subsample_encrypted_noaltref.webm.0.dump b/library/core/src/androidTest/assets/mkv/subsample_encrypted_noaltref.webm.0.dump similarity index 100% rename from library/src/androidTest/assets/mkv/subsample_encrypted_noaltref.webm.0.dump rename to library/core/src/androidTest/assets/mkv/subsample_encrypted_noaltref.webm.0.dump diff --git a/library/src/androidTest/assets/mp3/bear.mp3 b/library/core/src/androidTest/assets/mp3/bear.mp3 similarity index 100% rename from library/src/androidTest/assets/mp3/bear.mp3 rename to library/core/src/androidTest/assets/mp3/bear.mp3 diff --git a/library/src/androidTest/assets/mp3/bear.mp3.0.dump b/library/core/src/androidTest/assets/mp3/bear.mp3.0.dump similarity index 100% rename from library/src/androidTest/assets/mp3/bear.mp3.0.dump rename to library/core/src/androidTest/assets/mp3/bear.mp3.0.dump diff --git a/library/src/androidTest/assets/mp3/bear.mp3.1.dump b/library/core/src/androidTest/assets/mp3/bear.mp3.1.dump similarity index 100% rename from library/src/androidTest/assets/mp3/bear.mp3.1.dump rename to library/core/src/androidTest/assets/mp3/bear.mp3.1.dump diff --git a/library/src/androidTest/assets/mp3/bear.mp3.2.dump b/library/core/src/androidTest/assets/mp3/bear.mp3.2.dump similarity index 100% rename from library/src/androidTest/assets/mp3/bear.mp3.2.dump rename to library/core/src/androidTest/assets/mp3/bear.mp3.2.dump diff --git a/library/src/androidTest/assets/mp3/bear.mp3.3.dump b/library/core/src/androidTest/assets/mp3/bear.mp3.3.dump similarity index 100% rename from library/src/androidTest/assets/mp3/bear.mp3.3.dump rename to library/core/src/androidTest/assets/mp3/bear.mp3.3.dump diff --git a/library/src/androidTest/assets/mp3/play-trimmed.mp3 b/library/core/src/androidTest/assets/mp3/play-trimmed.mp3 similarity index 100% rename from library/src/androidTest/assets/mp3/play-trimmed.mp3 rename to library/core/src/androidTest/assets/mp3/play-trimmed.mp3 diff --git a/library/src/androidTest/assets/mp3/play-trimmed.mp3.0.dump b/library/core/src/androidTest/assets/mp3/play-trimmed.mp3.0.dump similarity index 100% rename from library/src/androidTest/assets/mp3/play-trimmed.mp3.0.dump rename to library/core/src/androidTest/assets/mp3/play-trimmed.mp3.0.dump diff --git a/library/src/androidTest/assets/mp3/play-trimmed.mp3.1.dump b/library/core/src/androidTest/assets/mp3/play-trimmed.mp3.1.dump similarity index 100% rename from library/src/androidTest/assets/mp3/play-trimmed.mp3.1.dump rename to library/core/src/androidTest/assets/mp3/play-trimmed.mp3.1.dump diff --git a/library/src/androidTest/assets/mp3/play-trimmed.mp3.2.dump b/library/core/src/androidTest/assets/mp3/play-trimmed.mp3.2.dump similarity index 100% rename from library/src/androidTest/assets/mp3/play-trimmed.mp3.2.dump rename to library/core/src/androidTest/assets/mp3/play-trimmed.mp3.2.dump diff --git a/library/src/androidTest/assets/mp3/play-trimmed.mp3.3.dump b/library/core/src/androidTest/assets/mp3/play-trimmed.mp3.3.dump similarity index 100% rename from library/src/androidTest/assets/mp3/play-trimmed.mp3.3.dump rename to library/core/src/androidTest/assets/mp3/play-trimmed.mp3.3.dump diff --git a/library/src/androidTest/assets/mp3/play-trimmed.mp3.unklen.dump b/library/core/src/androidTest/assets/mp3/play-trimmed.mp3.unklen.dump similarity index 100% rename from library/src/androidTest/assets/mp3/play-trimmed.mp3.unklen.dump rename to library/core/src/androidTest/assets/mp3/play-trimmed.mp3.unklen.dump diff --git a/library/src/androidTest/assets/mp4/sample.mp4 b/library/core/src/androidTest/assets/mp4/sample.mp4 similarity index 100% rename from library/src/androidTest/assets/mp4/sample.mp4 rename to library/core/src/androidTest/assets/mp4/sample.mp4 diff --git a/library/src/androidTest/assets/mp4/sample.mp4.0.dump b/library/core/src/androidTest/assets/mp4/sample.mp4.0.dump similarity index 100% rename from library/src/androidTest/assets/mp4/sample.mp4.0.dump rename to library/core/src/androidTest/assets/mp4/sample.mp4.0.dump diff --git a/library/src/androidTest/assets/mp4/sample.mp4.1.dump b/library/core/src/androidTest/assets/mp4/sample.mp4.1.dump similarity index 100% rename from library/src/androidTest/assets/mp4/sample.mp4.1.dump rename to library/core/src/androidTest/assets/mp4/sample.mp4.1.dump diff --git a/library/src/androidTest/assets/mp4/sample.mp4.2.dump b/library/core/src/androidTest/assets/mp4/sample.mp4.2.dump similarity index 100% rename from library/src/androidTest/assets/mp4/sample.mp4.2.dump rename to library/core/src/androidTest/assets/mp4/sample.mp4.2.dump diff --git a/library/src/androidTest/assets/mp4/sample.mp4.3.dump b/library/core/src/androidTest/assets/mp4/sample.mp4.3.dump similarity index 100% rename from library/src/androidTest/assets/mp4/sample.mp4.3.dump rename to library/core/src/androidTest/assets/mp4/sample.mp4.3.dump diff --git a/library/src/androidTest/assets/mp4/sample_fragmented.mp4 b/library/core/src/androidTest/assets/mp4/sample_fragmented.mp4 similarity index 100% rename from library/src/androidTest/assets/mp4/sample_fragmented.mp4 rename to library/core/src/androidTest/assets/mp4/sample_fragmented.mp4 diff --git a/library/src/androidTest/assets/mp4/sample_fragmented.mp4.0.dump b/library/core/src/androidTest/assets/mp4/sample_fragmented.mp4.0.dump similarity index 100% rename from library/src/androidTest/assets/mp4/sample_fragmented.mp4.0.dump rename to library/core/src/androidTest/assets/mp4/sample_fragmented.mp4.0.dump diff --git a/library/src/androidTest/assets/mp4/sample_fragmented_sei.mp4 b/library/core/src/androidTest/assets/mp4/sample_fragmented_sei.mp4 similarity index 100% rename from library/src/androidTest/assets/mp4/sample_fragmented_sei.mp4 rename to library/core/src/androidTest/assets/mp4/sample_fragmented_sei.mp4 diff --git a/library/src/androidTest/assets/mp4/sample_fragmented_sei.mp4.0.dump b/library/core/src/androidTest/assets/mp4/sample_fragmented_sei.mp4.0.dump similarity index 100% rename from library/src/androidTest/assets/mp4/sample_fragmented_sei.mp4.0.dump rename to library/core/src/androidTest/assets/mp4/sample_fragmented_sei.mp4.0.dump diff --git a/library/src/androidTest/assets/mp4/sample_fragmented_zero_size_atom.mp4 b/library/core/src/androidTest/assets/mp4/sample_fragmented_zero_size_atom.mp4 similarity index 100% rename from library/src/androidTest/assets/mp4/sample_fragmented_zero_size_atom.mp4 rename to library/core/src/androidTest/assets/mp4/sample_fragmented_zero_size_atom.mp4 diff --git a/library/src/androidTest/assets/ogg/bear.opus b/library/core/src/androidTest/assets/ogg/bear.opus similarity index 100% rename from library/src/androidTest/assets/ogg/bear.opus rename to library/core/src/androidTest/assets/ogg/bear.opus diff --git a/library/src/androidTest/assets/ogg/bear.opus.0.dump b/library/core/src/androidTest/assets/ogg/bear.opus.0.dump similarity index 100% rename from library/src/androidTest/assets/ogg/bear.opus.0.dump rename to library/core/src/androidTest/assets/ogg/bear.opus.0.dump diff --git a/library/src/androidTest/assets/ogg/bear.opus.1.dump b/library/core/src/androidTest/assets/ogg/bear.opus.1.dump similarity index 100% rename from library/src/androidTest/assets/ogg/bear.opus.1.dump rename to library/core/src/androidTest/assets/ogg/bear.opus.1.dump diff --git a/library/src/androidTest/assets/ogg/bear.opus.2.dump b/library/core/src/androidTest/assets/ogg/bear.opus.2.dump similarity index 100% rename from library/src/androidTest/assets/ogg/bear.opus.2.dump rename to library/core/src/androidTest/assets/ogg/bear.opus.2.dump diff --git a/library/src/androidTest/assets/ogg/bear.opus.3.dump b/library/core/src/androidTest/assets/ogg/bear.opus.3.dump similarity index 100% rename from library/src/androidTest/assets/ogg/bear.opus.3.dump rename to library/core/src/androidTest/assets/ogg/bear.opus.3.dump diff --git a/library/src/androidTest/assets/ogg/bear.opus.unklen.dump b/library/core/src/androidTest/assets/ogg/bear.opus.unklen.dump similarity index 100% rename from library/src/androidTest/assets/ogg/bear.opus.unklen.dump rename to library/core/src/androidTest/assets/ogg/bear.opus.unklen.dump diff --git a/library/src/androidTest/assets/ogg/bear_flac.ogg b/library/core/src/androidTest/assets/ogg/bear_flac.ogg similarity index 100% rename from library/src/androidTest/assets/ogg/bear_flac.ogg rename to library/core/src/androidTest/assets/ogg/bear_flac.ogg diff --git a/library/src/androidTest/assets/ogg/bear_flac.ogg.0.dump b/library/core/src/androidTest/assets/ogg/bear_flac.ogg.0.dump similarity index 100% rename from library/src/androidTest/assets/ogg/bear_flac.ogg.0.dump rename to library/core/src/androidTest/assets/ogg/bear_flac.ogg.0.dump diff --git a/library/src/androidTest/assets/ogg/bear_flac.ogg.1.dump b/library/core/src/androidTest/assets/ogg/bear_flac.ogg.1.dump similarity index 100% rename from library/src/androidTest/assets/ogg/bear_flac.ogg.1.dump rename to library/core/src/androidTest/assets/ogg/bear_flac.ogg.1.dump diff --git a/library/src/androidTest/assets/ogg/bear_flac.ogg.2.dump b/library/core/src/androidTest/assets/ogg/bear_flac.ogg.2.dump similarity index 100% rename from library/src/androidTest/assets/ogg/bear_flac.ogg.2.dump rename to library/core/src/androidTest/assets/ogg/bear_flac.ogg.2.dump diff --git a/library/src/androidTest/assets/ogg/bear_flac.ogg.3.dump b/library/core/src/androidTest/assets/ogg/bear_flac.ogg.3.dump similarity index 100% rename from library/src/androidTest/assets/ogg/bear_flac.ogg.3.dump rename to library/core/src/androidTest/assets/ogg/bear_flac.ogg.3.dump diff --git a/library/src/androidTest/assets/ogg/bear_flac.ogg.unklen.dump b/library/core/src/androidTest/assets/ogg/bear_flac.ogg.unklen.dump similarity index 100% rename from library/src/androidTest/assets/ogg/bear_flac.ogg.unklen.dump rename to library/core/src/androidTest/assets/ogg/bear_flac.ogg.unklen.dump diff --git a/library/src/androidTest/assets/ogg/bear_flac_noseektable.ogg b/library/core/src/androidTest/assets/ogg/bear_flac_noseektable.ogg similarity index 100% rename from library/src/androidTest/assets/ogg/bear_flac_noseektable.ogg rename to library/core/src/androidTest/assets/ogg/bear_flac_noseektable.ogg diff --git a/library/src/androidTest/assets/ogg/bear_flac_noseektable.ogg.0.dump b/library/core/src/androidTest/assets/ogg/bear_flac_noseektable.ogg.0.dump similarity index 100% rename from library/src/androidTest/assets/ogg/bear_flac_noseektable.ogg.0.dump rename to library/core/src/androidTest/assets/ogg/bear_flac_noseektable.ogg.0.dump diff --git a/library/src/androidTest/assets/ogg/bear_flac_noseektable.ogg.1.dump b/library/core/src/androidTest/assets/ogg/bear_flac_noseektable.ogg.1.dump similarity index 100% rename from library/src/androidTest/assets/ogg/bear_flac_noseektable.ogg.1.dump rename to library/core/src/androidTest/assets/ogg/bear_flac_noseektable.ogg.1.dump diff --git a/library/src/androidTest/assets/ogg/bear_flac_noseektable.ogg.2.dump b/library/core/src/androidTest/assets/ogg/bear_flac_noseektable.ogg.2.dump similarity index 100% rename from library/src/androidTest/assets/ogg/bear_flac_noseektable.ogg.2.dump rename to library/core/src/androidTest/assets/ogg/bear_flac_noseektable.ogg.2.dump diff --git a/library/src/androidTest/assets/ogg/bear_flac_noseektable.ogg.3.dump b/library/core/src/androidTest/assets/ogg/bear_flac_noseektable.ogg.3.dump similarity index 100% rename from library/src/androidTest/assets/ogg/bear_flac_noseektable.ogg.3.dump rename to library/core/src/androidTest/assets/ogg/bear_flac_noseektable.ogg.3.dump diff --git a/library/src/androidTest/assets/ogg/bear_flac_noseektable.ogg.unklen.dump b/library/core/src/androidTest/assets/ogg/bear_flac_noseektable.ogg.unklen.dump similarity index 100% rename from library/src/androidTest/assets/ogg/bear_flac_noseektable.ogg.unklen.dump rename to library/core/src/androidTest/assets/ogg/bear_flac_noseektable.ogg.unklen.dump diff --git a/library/src/androidTest/assets/ogg/bear_vorbis.ogg b/library/core/src/androidTest/assets/ogg/bear_vorbis.ogg similarity index 100% rename from library/src/androidTest/assets/ogg/bear_vorbis.ogg rename to library/core/src/androidTest/assets/ogg/bear_vorbis.ogg diff --git a/library/src/androidTest/assets/ogg/bear_vorbis.ogg.0.dump b/library/core/src/androidTest/assets/ogg/bear_vorbis.ogg.0.dump similarity index 100% rename from library/src/androidTest/assets/ogg/bear_vorbis.ogg.0.dump rename to library/core/src/androidTest/assets/ogg/bear_vorbis.ogg.0.dump diff --git a/library/src/androidTest/assets/ogg/bear_vorbis.ogg.1.dump b/library/core/src/androidTest/assets/ogg/bear_vorbis.ogg.1.dump similarity index 100% rename from library/src/androidTest/assets/ogg/bear_vorbis.ogg.1.dump rename to library/core/src/androidTest/assets/ogg/bear_vorbis.ogg.1.dump diff --git a/library/src/androidTest/assets/ogg/bear_vorbis.ogg.2.dump b/library/core/src/androidTest/assets/ogg/bear_vorbis.ogg.2.dump similarity index 100% rename from library/src/androidTest/assets/ogg/bear_vorbis.ogg.2.dump rename to library/core/src/androidTest/assets/ogg/bear_vorbis.ogg.2.dump diff --git a/library/src/androidTest/assets/ogg/bear_vorbis.ogg.3.dump b/library/core/src/androidTest/assets/ogg/bear_vorbis.ogg.3.dump similarity index 100% rename from library/src/androidTest/assets/ogg/bear_vorbis.ogg.3.dump rename to library/core/src/androidTest/assets/ogg/bear_vorbis.ogg.3.dump diff --git a/library/src/androidTest/assets/ogg/bear_vorbis.ogg.unklen.dump b/library/core/src/androidTest/assets/ogg/bear_vorbis.ogg.unklen.dump similarity index 100% rename from library/src/androidTest/assets/ogg/bear_vorbis.ogg.unklen.dump rename to library/core/src/androidTest/assets/ogg/bear_vorbis.ogg.unklen.dump diff --git a/library/src/androidTest/assets/rawcc/sample.rawcc b/library/core/src/androidTest/assets/rawcc/sample.rawcc similarity index 100% rename from library/src/androidTest/assets/rawcc/sample.rawcc rename to library/core/src/androidTest/assets/rawcc/sample.rawcc diff --git a/library/src/androidTest/assets/rawcc/sample.rawcc.0.dump b/library/core/src/androidTest/assets/rawcc/sample.rawcc.0.dump similarity index 100% rename from library/src/androidTest/assets/rawcc/sample.rawcc.0.dump rename to library/core/src/androidTest/assets/rawcc/sample.rawcc.0.dump diff --git a/library/src/androidTest/assets/smoothstreaming/sample_ismc_1 b/library/core/src/androidTest/assets/smoothstreaming/sample_ismc_1 similarity index 100% rename from library/src/androidTest/assets/smoothstreaming/sample_ismc_1 rename to library/core/src/androidTest/assets/smoothstreaming/sample_ismc_1 diff --git a/library/src/androidTest/assets/smoothstreaming/sample_ismc_2 b/library/core/src/androidTest/assets/smoothstreaming/sample_ismc_2 similarity index 100% rename from library/src/androidTest/assets/smoothstreaming/sample_ismc_2 rename to library/core/src/androidTest/assets/smoothstreaming/sample_ismc_2 diff --git a/library/src/androidTest/assets/subrip/empty b/library/core/src/androidTest/assets/subrip/empty similarity index 100% rename from library/src/androidTest/assets/subrip/empty rename to library/core/src/androidTest/assets/subrip/empty diff --git a/library/src/androidTest/assets/subrip/no_end_timecodes b/library/core/src/androidTest/assets/subrip/no_end_timecodes similarity index 100% rename from library/src/androidTest/assets/subrip/no_end_timecodes rename to library/core/src/androidTest/assets/subrip/no_end_timecodes diff --git a/library/src/androidTest/assets/subrip/typical b/library/core/src/androidTest/assets/subrip/typical similarity index 100% rename from library/src/androidTest/assets/subrip/typical rename to library/core/src/androidTest/assets/subrip/typical diff --git a/library/src/androidTest/assets/subrip/typical_extra_blank_line b/library/core/src/androidTest/assets/subrip/typical_extra_blank_line similarity index 100% rename from library/src/androidTest/assets/subrip/typical_extra_blank_line rename to library/core/src/androidTest/assets/subrip/typical_extra_blank_line diff --git a/library/src/androidTest/assets/subrip/typical_missing_sequence b/library/core/src/androidTest/assets/subrip/typical_missing_sequence similarity index 100% rename from library/src/androidTest/assets/subrip/typical_missing_sequence rename to library/core/src/androidTest/assets/subrip/typical_missing_sequence diff --git a/library/src/androidTest/assets/subrip/typical_missing_timecode b/library/core/src/androidTest/assets/subrip/typical_missing_timecode similarity index 100% rename from library/src/androidTest/assets/subrip/typical_missing_timecode rename to library/core/src/androidTest/assets/subrip/typical_missing_timecode diff --git a/library/src/androidTest/assets/subrip/typical_negative_timestamps b/library/core/src/androidTest/assets/subrip/typical_negative_timestamps similarity index 100% rename from library/src/androidTest/assets/subrip/typical_negative_timestamps rename to library/core/src/androidTest/assets/subrip/typical_negative_timestamps diff --git a/library/src/androidTest/assets/subrip/typical_with_byte_order_mark b/library/core/src/androidTest/assets/subrip/typical_with_byte_order_mark similarity index 100% rename from library/src/androidTest/assets/subrip/typical_with_byte_order_mark rename to library/core/src/androidTest/assets/subrip/typical_with_byte_order_mark diff --git a/library/src/androidTest/assets/ts/sample.ac3 b/library/core/src/androidTest/assets/ts/sample.ac3 similarity index 100% rename from library/src/androidTest/assets/ts/sample.ac3 rename to library/core/src/androidTest/assets/ts/sample.ac3 diff --git a/library/src/androidTest/assets/ts/sample.ac3.0.dump b/library/core/src/androidTest/assets/ts/sample.ac3.0.dump similarity index 100% rename from library/src/androidTest/assets/ts/sample.ac3.0.dump rename to library/core/src/androidTest/assets/ts/sample.ac3.0.dump diff --git a/library/src/androidTest/assets/ts/sample.adts b/library/core/src/androidTest/assets/ts/sample.adts similarity index 100% rename from library/src/androidTest/assets/ts/sample.adts rename to library/core/src/androidTest/assets/ts/sample.adts diff --git a/library/src/androidTest/assets/ts/sample.adts.0.dump b/library/core/src/androidTest/assets/ts/sample.adts.0.dump similarity index 100% rename from library/src/androidTest/assets/ts/sample.adts.0.dump rename to library/core/src/androidTest/assets/ts/sample.adts.0.dump diff --git a/library/src/androidTest/assets/ts/sample.ps b/library/core/src/androidTest/assets/ts/sample.ps similarity index 100% rename from library/src/androidTest/assets/ts/sample.ps rename to library/core/src/androidTest/assets/ts/sample.ps diff --git a/library/src/androidTest/assets/ts/sample.ps.0.dump b/library/core/src/androidTest/assets/ts/sample.ps.0.dump similarity index 100% rename from library/src/androidTest/assets/ts/sample.ps.0.dump rename to library/core/src/androidTest/assets/ts/sample.ps.0.dump diff --git a/library/src/androidTest/assets/ts/sample.ts b/library/core/src/androidTest/assets/ts/sample.ts similarity index 100% rename from library/src/androidTest/assets/ts/sample.ts rename to library/core/src/androidTest/assets/ts/sample.ts diff --git a/library/src/androidTest/assets/ts/sample.ts.0.dump b/library/core/src/androidTest/assets/ts/sample.ts.0.dump similarity index 100% rename from library/src/androidTest/assets/ts/sample.ts.0.dump rename to library/core/src/androidTest/assets/ts/sample.ts.0.dump diff --git a/library/src/androidTest/assets/ts/sample_with_sdt.ts b/library/core/src/androidTest/assets/ts/sample_with_sdt.ts similarity index 100% rename from library/src/androidTest/assets/ts/sample_with_sdt.ts rename to library/core/src/androidTest/assets/ts/sample_with_sdt.ts diff --git a/library/src/androidTest/assets/ttml/chain_multiple_styles.xml b/library/core/src/androidTest/assets/ttml/chain_multiple_styles.xml similarity index 100% rename from library/src/androidTest/assets/ttml/chain_multiple_styles.xml rename to library/core/src/androidTest/assets/ttml/chain_multiple_styles.xml diff --git a/library/src/androidTest/assets/ttml/font_size.xml b/library/core/src/androidTest/assets/ttml/font_size.xml similarity index 100% rename from library/src/androidTest/assets/ttml/font_size.xml rename to library/core/src/androidTest/assets/ttml/font_size.xml diff --git a/library/src/androidTest/assets/ttml/font_size_empty.xml b/library/core/src/androidTest/assets/ttml/font_size_empty.xml similarity index 100% rename from library/src/androidTest/assets/ttml/font_size_empty.xml rename to library/core/src/androidTest/assets/ttml/font_size_empty.xml diff --git a/library/src/androidTest/assets/ttml/font_size_invalid.xml b/library/core/src/androidTest/assets/ttml/font_size_invalid.xml similarity index 100% rename from library/src/androidTest/assets/ttml/font_size_invalid.xml rename to library/core/src/androidTest/assets/ttml/font_size_invalid.xml diff --git a/library/src/androidTest/assets/ttml/font_size_no_unit.xml b/library/core/src/androidTest/assets/ttml/font_size_no_unit.xml similarity index 100% rename from library/src/androidTest/assets/ttml/font_size_no_unit.xml rename to library/core/src/androidTest/assets/ttml/font_size_no_unit.xml diff --git a/library/src/androidTest/assets/ttml/frame_rate.xml b/library/core/src/androidTest/assets/ttml/frame_rate.xml similarity index 100% rename from library/src/androidTest/assets/ttml/frame_rate.xml rename to library/core/src/androidTest/assets/ttml/frame_rate.xml diff --git a/library/src/androidTest/assets/ttml/inherit_and_override_style.xml b/library/core/src/androidTest/assets/ttml/inherit_and_override_style.xml similarity index 100% rename from library/src/androidTest/assets/ttml/inherit_and_override_style.xml rename to library/core/src/androidTest/assets/ttml/inherit_and_override_style.xml diff --git a/library/src/androidTest/assets/ttml/inherit_global_and_parent.xml b/library/core/src/androidTest/assets/ttml/inherit_global_and_parent.xml similarity index 100% rename from library/src/androidTest/assets/ttml/inherit_global_and_parent.xml rename to library/core/src/androidTest/assets/ttml/inherit_global_and_parent.xml diff --git a/library/src/androidTest/assets/ttml/inherit_multiple_styles.xml b/library/core/src/androidTest/assets/ttml/inherit_multiple_styles.xml similarity index 100% rename from library/src/androidTest/assets/ttml/inherit_multiple_styles.xml rename to library/core/src/androidTest/assets/ttml/inherit_multiple_styles.xml diff --git a/library/src/androidTest/assets/ttml/inherit_style.xml b/library/core/src/androidTest/assets/ttml/inherit_style.xml similarity index 100% rename from library/src/androidTest/assets/ttml/inherit_style.xml rename to library/core/src/androidTest/assets/ttml/inherit_style.xml diff --git a/library/src/androidTest/assets/ttml/inline_style_attributes.xml b/library/core/src/androidTest/assets/ttml/inline_style_attributes.xml similarity index 100% rename from library/src/androidTest/assets/ttml/inline_style_attributes.xml rename to library/core/src/androidTest/assets/ttml/inline_style_attributes.xml diff --git a/library/src/androidTest/assets/ttml/multiple_regions.xml b/library/core/src/androidTest/assets/ttml/multiple_regions.xml similarity index 100% rename from library/src/androidTest/assets/ttml/multiple_regions.xml rename to library/core/src/androidTest/assets/ttml/multiple_regions.xml diff --git a/library/src/androidTest/assets/ttml/no_underline_linethrough.xml b/library/core/src/androidTest/assets/ttml/no_underline_linethrough.xml similarity index 100% rename from library/src/androidTest/assets/ttml/no_underline_linethrough.xml rename to library/core/src/androidTest/assets/ttml/no_underline_linethrough.xml diff --git a/library/src/androidTest/assets/wav/sample.wav b/library/core/src/androidTest/assets/wav/sample.wav similarity index 100% rename from library/src/androidTest/assets/wav/sample.wav rename to library/core/src/androidTest/assets/wav/sample.wav diff --git a/library/src/androidTest/assets/wav/sample.wav.0.dump b/library/core/src/androidTest/assets/wav/sample.wav.0.dump similarity index 100% rename from library/src/androidTest/assets/wav/sample.wav.0.dump rename to library/core/src/androidTest/assets/wav/sample.wav.0.dump diff --git a/library/src/androidTest/assets/wav/sample.wav.1.dump b/library/core/src/androidTest/assets/wav/sample.wav.1.dump similarity index 100% rename from library/src/androidTest/assets/wav/sample.wav.1.dump rename to library/core/src/androidTest/assets/wav/sample.wav.1.dump diff --git a/library/src/androidTest/assets/wav/sample.wav.2.dump b/library/core/src/androidTest/assets/wav/sample.wav.2.dump similarity index 100% rename from library/src/androidTest/assets/wav/sample.wav.2.dump rename to library/core/src/androidTest/assets/wav/sample.wav.2.dump diff --git a/library/src/androidTest/assets/wav/sample.wav.3.dump b/library/core/src/androidTest/assets/wav/sample.wav.3.dump similarity index 100% rename from library/src/androidTest/assets/wav/sample.wav.3.dump rename to library/core/src/androidTest/assets/wav/sample.wav.3.dump diff --git a/library/src/androidTest/assets/webm/vorbis_codec_private b/library/core/src/androidTest/assets/webm/vorbis_codec_private similarity index 100% rename from library/src/androidTest/assets/webm/vorbis_codec_private rename to library/core/src/androidTest/assets/webm/vorbis_codec_private diff --git a/library/src/androidTest/assets/webvtt/empty b/library/core/src/androidTest/assets/webvtt/empty similarity index 100% rename from library/src/androidTest/assets/webvtt/empty rename to library/core/src/androidTest/assets/webvtt/empty diff --git a/library/src/androidTest/assets/webvtt/typical b/library/core/src/androidTest/assets/webvtt/typical similarity index 100% rename from library/src/androidTest/assets/webvtt/typical rename to library/core/src/androidTest/assets/webvtt/typical diff --git a/library/src/androidTest/assets/webvtt/typical_with_comments b/library/core/src/androidTest/assets/webvtt/typical_with_comments similarity index 100% rename from library/src/androidTest/assets/webvtt/typical_with_comments rename to library/core/src/androidTest/assets/webvtt/typical_with_comments diff --git a/library/src/androidTest/assets/webvtt/typical_with_identifiers b/library/core/src/androidTest/assets/webvtt/typical_with_identifiers similarity index 100% rename from library/src/androidTest/assets/webvtt/typical_with_identifiers rename to library/core/src/androidTest/assets/webvtt/typical_with_identifiers diff --git a/library/src/androidTest/assets/webvtt/with_bad_cue_header b/library/core/src/androidTest/assets/webvtt/with_bad_cue_header similarity index 100% rename from library/src/androidTest/assets/webvtt/with_bad_cue_header rename to library/core/src/androidTest/assets/webvtt/with_bad_cue_header diff --git a/library/src/androidTest/assets/webvtt/with_css_complex_selectors b/library/core/src/androidTest/assets/webvtt/with_css_complex_selectors similarity index 100% rename from library/src/androidTest/assets/webvtt/with_css_complex_selectors rename to library/core/src/androidTest/assets/webvtt/with_css_complex_selectors diff --git a/library/src/androidTest/assets/webvtt/with_css_styles b/library/core/src/androidTest/assets/webvtt/with_css_styles similarity index 100% rename from library/src/androidTest/assets/webvtt/with_css_styles rename to library/core/src/androidTest/assets/webvtt/with_css_styles diff --git a/library/src/androidTest/assets/webvtt/with_positioning b/library/core/src/androidTest/assets/webvtt/with_positioning similarity index 100% rename from library/src/androidTest/assets/webvtt/with_positioning rename to library/core/src/androidTest/assets/webvtt/with_positioning diff --git a/library/src/androidTest/assets/webvtt/with_tags b/library/core/src/androidTest/assets/webvtt/with_tags similarity index 100% rename from library/src/androidTest/assets/webvtt/with_tags rename to library/core/src/androidTest/assets/webvtt/with_tags diff --git a/library/src/androidTest/java/com/google/android/exoplayer2/CTest.java b/library/core/src/androidTest/java/com/google/android/exoplayer2/CTest.java similarity index 100% rename from library/src/androidTest/java/com/google/android/exoplayer2/CTest.java rename to library/core/src/androidTest/java/com/google/android/exoplayer2/CTest.java diff --git a/library/src/androidTest/java/com/google/android/exoplayer2/ExoPlayerTest.java b/library/core/src/androidTest/java/com/google/android/exoplayer2/ExoPlayerTest.java similarity index 100% rename from library/src/androidTest/java/com/google/android/exoplayer2/ExoPlayerTest.java rename to library/core/src/androidTest/java/com/google/android/exoplayer2/ExoPlayerTest.java diff --git a/library/src/androidTest/java/com/google/android/exoplayer2/FormatTest.java b/library/core/src/androidTest/java/com/google/android/exoplayer2/FormatTest.java similarity index 100% rename from library/src/androidTest/java/com/google/android/exoplayer2/FormatTest.java rename to library/core/src/androidTest/java/com/google/android/exoplayer2/FormatTest.java diff --git a/library/src/androidTest/java/com/google/android/exoplayer2/drm/DrmInitDataTest.java b/library/core/src/androidTest/java/com/google/android/exoplayer2/drm/DrmInitDataTest.java similarity index 100% rename from library/src/androidTest/java/com/google/android/exoplayer2/drm/DrmInitDataTest.java rename to library/core/src/androidTest/java/com/google/android/exoplayer2/drm/DrmInitDataTest.java diff --git a/library/src/androidTest/java/com/google/android/exoplayer2/drm/OfflineLicenseHelperTest.java b/library/core/src/androidTest/java/com/google/android/exoplayer2/drm/OfflineLicenseHelperTest.java similarity index 100% rename from library/src/androidTest/java/com/google/android/exoplayer2/drm/OfflineLicenseHelperTest.java rename to library/core/src/androidTest/java/com/google/android/exoplayer2/drm/OfflineLicenseHelperTest.java diff --git a/library/src/androidTest/java/com/google/android/exoplayer2/extractor/DefaultExtractorInputTest.java b/library/core/src/androidTest/java/com/google/android/exoplayer2/extractor/DefaultExtractorInputTest.java similarity index 100% rename from library/src/androidTest/java/com/google/android/exoplayer2/extractor/DefaultExtractorInputTest.java rename to library/core/src/androidTest/java/com/google/android/exoplayer2/extractor/DefaultExtractorInputTest.java diff --git a/library/src/androidTest/java/com/google/android/exoplayer2/extractor/ExtractorTest.java b/library/core/src/androidTest/java/com/google/android/exoplayer2/extractor/ExtractorTest.java similarity index 100% rename from library/src/androidTest/java/com/google/android/exoplayer2/extractor/ExtractorTest.java rename to library/core/src/androidTest/java/com/google/android/exoplayer2/extractor/ExtractorTest.java diff --git a/library/src/androidTest/java/com/google/android/exoplayer2/extractor/flv/FlvExtractorTest.java b/library/core/src/androidTest/java/com/google/android/exoplayer2/extractor/flv/FlvExtractorTest.java similarity index 100% rename from library/src/androidTest/java/com/google/android/exoplayer2/extractor/flv/FlvExtractorTest.java rename to library/core/src/androidTest/java/com/google/android/exoplayer2/extractor/flv/FlvExtractorTest.java diff --git a/library/src/androidTest/java/com/google/android/exoplayer2/extractor/mkv/DefaultEbmlReaderTest.java b/library/core/src/androidTest/java/com/google/android/exoplayer2/extractor/mkv/DefaultEbmlReaderTest.java similarity index 100% rename from library/src/androidTest/java/com/google/android/exoplayer2/extractor/mkv/DefaultEbmlReaderTest.java rename to library/core/src/androidTest/java/com/google/android/exoplayer2/extractor/mkv/DefaultEbmlReaderTest.java diff --git a/library/src/androidTest/java/com/google/android/exoplayer2/extractor/mkv/MatroskaExtractorTest.java b/library/core/src/androidTest/java/com/google/android/exoplayer2/extractor/mkv/MatroskaExtractorTest.java similarity index 100% rename from library/src/androidTest/java/com/google/android/exoplayer2/extractor/mkv/MatroskaExtractorTest.java rename to library/core/src/androidTest/java/com/google/android/exoplayer2/extractor/mkv/MatroskaExtractorTest.java diff --git a/library/src/androidTest/java/com/google/android/exoplayer2/extractor/mkv/VarintReaderTest.java b/library/core/src/androidTest/java/com/google/android/exoplayer2/extractor/mkv/VarintReaderTest.java similarity index 100% rename from library/src/androidTest/java/com/google/android/exoplayer2/extractor/mkv/VarintReaderTest.java rename to library/core/src/androidTest/java/com/google/android/exoplayer2/extractor/mkv/VarintReaderTest.java diff --git a/library/src/androidTest/java/com/google/android/exoplayer2/extractor/mp3/Mp3ExtractorTest.java b/library/core/src/androidTest/java/com/google/android/exoplayer2/extractor/mp3/Mp3ExtractorTest.java similarity index 100% rename from library/src/androidTest/java/com/google/android/exoplayer2/extractor/mp3/Mp3ExtractorTest.java rename to library/core/src/androidTest/java/com/google/android/exoplayer2/extractor/mp3/Mp3ExtractorTest.java diff --git a/library/src/androidTest/java/com/google/android/exoplayer2/extractor/mp3/XingSeekerTest.java b/library/core/src/androidTest/java/com/google/android/exoplayer2/extractor/mp3/XingSeekerTest.java similarity index 100% rename from library/src/androidTest/java/com/google/android/exoplayer2/extractor/mp3/XingSeekerTest.java rename to library/core/src/androidTest/java/com/google/android/exoplayer2/extractor/mp3/XingSeekerTest.java diff --git a/library/src/androidTest/java/com/google/android/exoplayer2/extractor/mp4/AtomParsersTest.java b/library/core/src/androidTest/java/com/google/android/exoplayer2/extractor/mp4/AtomParsersTest.java similarity index 100% rename from library/src/androidTest/java/com/google/android/exoplayer2/extractor/mp4/AtomParsersTest.java rename to library/core/src/androidTest/java/com/google/android/exoplayer2/extractor/mp4/AtomParsersTest.java diff --git a/library/src/androidTest/java/com/google/android/exoplayer2/extractor/mp4/FragmentedMp4ExtractorTest.java b/library/core/src/androidTest/java/com/google/android/exoplayer2/extractor/mp4/FragmentedMp4ExtractorTest.java similarity index 100% rename from library/src/androidTest/java/com/google/android/exoplayer2/extractor/mp4/FragmentedMp4ExtractorTest.java rename to library/core/src/androidTest/java/com/google/android/exoplayer2/extractor/mp4/FragmentedMp4ExtractorTest.java diff --git a/library/src/androidTest/java/com/google/android/exoplayer2/extractor/mp4/Mp4ExtractorTest.java b/library/core/src/androidTest/java/com/google/android/exoplayer2/extractor/mp4/Mp4ExtractorTest.java similarity index 100% rename from library/src/androidTest/java/com/google/android/exoplayer2/extractor/mp4/Mp4ExtractorTest.java rename to library/core/src/androidTest/java/com/google/android/exoplayer2/extractor/mp4/Mp4ExtractorTest.java diff --git a/library/src/androidTest/java/com/google/android/exoplayer2/extractor/ogg/DefaultOggSeekerTest.java b/library/core/src/androidTest/java/com/google/android/exoplayer2/extractor/ogg/DefaultOggSeekerTest.java similarity index 100% rename from library/src/androidTest/java/com/google/android/exoplayer2/extractor/ogg/DefaultOggSeekerTest.java rename to library/core/src/androidTest/java/com/google/android/exoplayer2/extractor/ogg/DefaultOggSeekerTest.java diff --git a/library/src/androidTest/java/com/google/android/exoplayer2/extractor/ogg/DefaultOggSeekerUtilMethodsTest.java b/library/core/src/androidTest/java/com/google/android/exoplayer2/extractor/ogg/DefaultOggSeekerUtilMethodsTest.java similarity index 100% rename from library/src/androidTest/java/com/google/android/exoplayer2/extractor/ogg/DefaultOggSeekerUtilMethodsTest.java rename to library/core/src/androidTest/java/com/google/android/exoplayer2/extractor/ogg/DefaultOggSeekerUtilMethodsTest.java diff --git a/library/src/androidTest/java/com/google/android/exoplayer2/extractor/ogg/OggExtractorTest.java b/library/core/src/androidTest/java/com/google/android/exoplayer2/extractor/ogg/OggExtractorTest.java similarity index 100% rename from library/src/androidTest/java/com/google/android/exoplayer2/extractor/ogg/OggExtractorTest.java rename to library/core/src/androidTest/java/com/google/android/exoplayer2/extractor/ogg/OggExtractorTest.java diff --git a/library/src/androidTest/java/com/google/android/exoplayer2/extractor/ogg/OggPacketTest.java b/library/core/src/androidTest/java/com/google/android/exoplayer2/extractor/ogg/OggPacketTest.java similarity index 100% rename from library/src/androidTest/java/com/google/android/exoplayer2/extractor/ogg/OggPacketTest.java rename to library/core/src/androidTest/java/com/google/android/exoplayer2/extractor/ogg/OggPacketTest.java diff --git a/library/src/androidTest/java/com/google/android/exoplayer2/extractor/ogg/OggPageHeaderTest.java b/library/core/src/androidTest/java/com/google/android/exoplayer2/extractor/ogg/OggPageHeaderTest.java similarity index 100% rename from library/src/androidTest/java/com/google/android/exoplayer2/extractor/ogg/OggPageHeaderTest.java rename to library/core/src/androidTest/java/com/google/android/exoplayer2/extractor/ogg/OggPageHeaderTest.java diff --git a/library/src/androidTest/java/com/google/android/exoplayer2/extractor/ogg/OggTestFile.java b/library/core/src/androidTest/java/com/google/android/exoplayer2/extractor/ogg/OggTestFile.java similarity index 100% rename from library/src/androidTest/java/com/google/android/exoplayer2/extractor/ogg/OggTestFile.java rename to library/core/src/androidTest/java/com/google/android/exoplayer2/extractor/ogg/OggTestFile.java diff --git a/library/src/androidTest/java/com/google/android/exoplayer2/extractor/ogg/TestData.java b/library/core/src/androidTest/java/com/google/android/exoplayer2/extractor/ogg/TestData.java similarity index 100% rename from library/src/androidTest/java/com/google/android/exoplayer2/extractor/ogg/TestData.java rename to library/core/src/androidTest/java/com/google/android/exoplayer2/extractor/ogg/TestData.java diff --git a/library/src/androidTest/java/com/google/android/exoplayer2/extractor/ogg/VorbisBitArrayTest.java b/library/core/src/androidTest/java/com/google/android/exoplayer2/extractor/ogg/VorbisBitArrayTest.java similarity index 100% rename from library/src/androidTest/java/com/google/android/exoplayer2/extractor/ogg/VorbisBitArrayTest.java rename to library/core/src/androidTest/java/com/google/android/exoplayer2/extractor/ogg/VorbisBitArrayTest.java diff --git a/library/src/androidTest/java/com/google/android/exoplayer2/extractor/ogg/VorbisReaderTest.java b/library/core/src/androidTest/java/com/google/android/exoplayer2/extractor/ogg/VorbisReaderTest.java similarity index 100% rename from library/src/androidTest/java/com/google/android/exoplayer2/extractor/ogg/VorbisReaderTest.java rename to library/core/src/androidTest/java/com/google/android/exoplayer2/extractor/ogg/VorbisReaderTest.java diff --git a/library/src/androidTest/java/com/google/android/exoplayer2/extractor/ogg/VorbisUtilTest.java b/library/core/src/androidTest/java/com/google/android/exoplayer2/extractor/ogg/VorbisUtilTest.java similarity index 100% rename from library/src/androidTest/java/com/google/android/exoplayer2/extractor/ogg/VorbisUtilTest.java rename to library/core/src/androidTest/java/com/google/android/exoplayer2/extractor/ogg/VorbisUtilTest.java diff --git a/library/src/androidTest/java/com/google/android/exoplayer2/extractor/rawcc/RawCcExtractorTest.java b/library/core/src/androidTest/java/com/google/android/exoplayer2/extractor/rawcc/RawCcExtractorTest.java similarity index 100% rename from library/src/androidTest/java/com/google/android/exoplayer2/extractor/rawcc/RawCcExtractorTest.java rename to library/core/src/androidTest/java/com/google/android/exoplayer2/extractor/rawcc/RawCcExtractorTest.java diff --git a/library/src/androidTest/java/com/google/android/exoplayer2/extractor/ts/Ac3ExtractorTest.java b/library/core/src/androidTest/java/com/google/android/exoplayer2/extractor/ts/Ac3ExtractorTest.java similarity index 100% rename from library/src/androidTest/java/com/google/android/exoplayer2/extractor/ts/Ac3ExtractorTest.java rename to library/core/src/androidTest/java/com/google/android/exoplayer2/extractor/ts/Ac3ExtractorTest.java diff --git a/library/src/androidTest/java/com/google/android/exoplayer2/extractor/ts/AdtsExtractorTest.java b/library/core/src/androidTest/java/com/google/android/exoplayer2/extractor/ts/AdtsExtractorTest.java similarity index 100% rename from library/src/androidTest/java/com/google/android/exoplayer2/extractor/ts/AdtsExtractorTest.java rename to library/core/src/androidTest/java/com/google/android/exoplayer2/extractor/ts/AdtsExtractorTest.java diff --git a/library/src/androidTest/java/com/google/android/exoplayer2/extractor/ts/AdtsReaderTest.java b/library/core/src/androidTest/java/com/google/android/exoplayer2/extractor/ts/AdtsReaderTest.java similarity index 100% rename from library/src/androidTest/java/com/google/android/exoplayer2/extractor/ts/AdtsReaderTest.java rename to library/core/src/androidTest/java/com/google/android/exoplayer2/extractor/ts/AdtsReaderTest.java diff --git a/library/src/androidTest/java/com/google/android/exoplayer2/extractor/ts/PsExtractorTest.java b/library/core/src/androidTest/java/com/google/android/exoplayer2/extractor/ts/PsExtractorTest.java similarity index 100% rename from library/src/androidTest/java/com/google/android/exoplayer2/extractor/ts/PsExtractorTest.java rename to library/core/src/androidTest/java/com/google/android/exoplayer2/extractor/ts/PsExtractorTest.java diff --git a/library/src/androidTest/java/com/google/android/exoplayer2/extractor/ts/SectionReaderTest.java b/library/core/src/androidTest/java/com/google/android/exoplayer2/extractor/ts/SectionReaderTest.java similarity index 100% rename from library/src/androidTest/java/com/google/android/exoplayer2/extractor/ts/SectionReaderTest.java rename to library/core/src/androidTest/java/com/google/android/exoplayer2/extractor/ts/SectionReaderTest.java diff --git a/library/src/androidTest/java/com/google/android/exoplayer2/extractor/ts/TsExtractorTest.java b/library/core/src/androidTest/java/com/google/android/exoplayer2/extractor/ts/TsExtractorTest.java similarity index 100% rename from library/src/androidTest/java/com/google/android/exoplayer2/extractor/ts/TsExtractorTest.java rename to library/core/src/androidTest/java/com/google/android/exoplayer2/extractor/ts/TsExtractorTest.java diff --git a/library/src/androidTest/java/com/google/android/exoplayer2/extractor/wav/WavExtractorTest.java b/library/core/src/androidTest/java/com/google/android/exoplayer2/extractor/wav/WavExtractorTest.java similarity index 100% rename from library/src/androidTest/java/com/google/android/exoplayer2/extractor/wav/WavExtractorTest.java rename to library/core/src/androidTest/java/com/google/android/exoplayer2/extractor/wav/WavExtractorTest.java diff --git a/library/src/androidTest/java/com/google/android/exoplayer2/metadata/emsg/EventMessageDecoderTest.java b/library/core/src/androidTest/java/com/google/android/exoplayer2/metadata/emsg/EventMessageDecoderTest.java similarity index 100% rename from library/src/androidTest/java/com/google/android/exoplayer2/metadata/emsg/EventMessageDecoderTest.java rename to library/core/src/androidTest/java/com/google/android/exoplayer2/metadata/emsg/EventMessageDecoderTest.java diff --git a/library/src/androidTest/java/com/google/android/exoplayer2/metadata/emsg/EventMessageTest.java b/library/core/src/androidTest/java/com/google/android/exoplayer2/metadata/emsg/EventMessageTest.java similarity index 100% rename from library/src/androidTest/java/com/google/android/exoplayer2/metadata/emsg/EventMessageTest.java rename to library/core/src/androidTest/java/com/google/android/exoplayer2/metadata/emsg/EventMessageTest.java diff --git a/library/src/androidTest/java/com/google/android/exoplayer2/metadata/id3/ChapterFrameTest.java b/library/core/src/androidTest/java/com/google/android/exoplayer2/metadata/id3/ChapterFrameTest.java similarity index 100% rename from library/src/androidTest/java/com/google/android/exoplayer2/metadata/id3/ChapterFrameTest.java rename to library/core/src/androidTest/java/com/google/android/exoplayer2/metadata/id3/ChapterFrameTest.java diff --git a/library/src/androidTest/java/com/google/android/exoplayer2/metadata/id3/ChapterTocFrameTest.java b/library/core/src/androidTest/java/com/google/android/exoplayer2/metadata/id3/ChapterTocFrameTest.java similarity index 100% rename from library/src/androidTest/java/com/google/android/exoplayer2/metadata/id3/ChapterTocFrameTest.java rename to library/core/src/androidTest/java/com/google/android/exoplayer2/metadata/id3/ChapterTocFrameTest.java diff --git a/library/src/androidTest/java/com/google/android/exoplayer2/metadata/id3/Id3DecoderTest.java b/library/core/src/androidTest/java/com/google/android/exoplayer2/metadata/id3/Id3DecoderTest.java similarity index 100% rename from library/src/androidTest/java/com/google/android/exoplayer2/metadata/id3/Id3DecoderTest.java rename to library/core/src/androidTest/java/com/google/android/exoplayer2/metadata/id3/Id3DecoderTest.java diff --git a/library/src/androidTest/java/com/google/android/exoplayer2/metadata/scte35/SpliceInfoDecoderTest.java b/library/core/src/androidTest/java/com/google/android/exoplayer2/metadata/scte35/SpliceInfoDecoderTest.java similarity index 100% rename from library/src/androidTest/java/com/google/android/exoplayer2/metadata/scte35/SpliceInfoDecoderTest.java rename to library/core/src/androidTest/java/com/google/android/exoplayer2/metadata/scte35/SpliceInfoDecoderTest.java diff --git a/library/src/androidTest/java/com/google/android/exoplayer2/source/ClippingMediaSourceTest.java b/library/core/src/androidTest/java/com/google/android/exoplayer2/source/ClippingMediaSourceTest.java similarity index 100% rename from library/src/androidTest/java/com/google/android/exoplayer2/source/ClippingMediaSourceTest.java rename to library/core/src/androidTest/java/com/google/android/exoplayer2/source/ClippingMediaSourceTest.java diff --git a/library/src/androidTest/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestParserTest.java b/library/core/src/androidTest/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestParserTest.java similarity index 100% rename from library/src/androidTest/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestParserTest.java rename to library/core/src/androidTest/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestParserTest.java diff --git a/library/src/androidTest/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestTest.java b/library/core/src/androidTest/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestTest.java similarity index 100% rename from library/src/androidTest/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestTest.java rename to library/core/src/androidTest/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestTest.java diff --git a/library/src/androidTest/java/com/google/android/exoplayer2/source/dash/manifest/RangedUriTest.java b/library/core/src/androidTest/java/com/google/android/exoplayer2/source/dash/manifest/RangedUriTest.java similarity index 100% rename from library/src/androidTest/java/com/google/android/exoplayer2/source/dash/manifest/RangedUriTest.java rename to library/core/src/androidTest/java/com/google/android/exoplayer2/source/dash/manifest/RangedUriTest.java diff --git a/library/src/androidTest/java/com/google/android/exoplayer2/source/dash/manifest/RepresentationTest.java b/library/core/src/androidTest/java/com/google/android/exoplayer2/source/dash/manifest/RepresentationTest.java similarity index 100% rename from library/src/androidTest/java/com/google/android/exoplayer2/source/dash/manifest/RepresentationTest.java rename to library/core/src/androidTest/java/com/google/android/exoplayer2/source/dash/manifest/RepresentationTest.java diff --git a/library/src/androidTest/java/com/google/android/exoplayer2/source/dash/manifest/UrlTemplateTest.java b/library/core/src/androidTest/java/com/google/android/exoplayer2/source/dash/manifest/UrlTemplateTest.java similarity index 100% rename from library/src/androidTest/java/com/google/android/exoplayer2/source/dash/manifest/UrlTemplateTest.java rename to library/core/src/androidTest/java/com/google/android/exoplayer2/source/dash/manifest/UrlTemplateTest.java diff --git a/library/src/androidTest/java/com/google/android/exoplayer2/source/hls/playlist/HlsMasterPlaylistParserTest.java b/library/core/src/androidTest/java/com/google/android/exoplayer2/source/hls/playlist/HlsMasterPlaylistParserTest.java similarity index 100% rename from library/src/androidTest/java/com/google/android/exoplayer2/source/hls/playlist/HlsMasterPlaylistParserTest.java rename to library/core/src/androidTest/java/com/google/android/exoplayer2/source/hls/playlist/HlsMasterPlaylistParserTest.java diff --git a/library/src/androidTest/java/com/google/android/exoplayer2/source/hls/playlist/HlsMediaPlaylistParserTest.java b/library/core/src/androidTest/java/com/google/android/exoplayer2/source/hls/playlist/HlsMediaPlaylistParserTest.java similarity index 100% rename from library/src/androidTest/java/com/google/android/exoplayer2/source/hls/playlist/HlsMediaPlaylistParserTest.java rename to library/core/src/androidTest/java/com/google/android/exoplayer2/source/hls/playlist/HlsMediaPlaylistParserTest.java diff --git a/library/src/androidTest/java/com/google/android/exoplayer2/source/smoothstreaming/manifest/SsManifestParserTest.java b/library/core/src/androidTest/java/com/google/android/exoplayer2/source/smoothstreaming/manifest/SsManifestParserTest.java similarity index 100% rename from library/src/androidTest/java/com/google/android/exoplayer2/source/smoothstreaming/manifest/SsManifestParserTest.java rename to library/core/src/androidTest/java/com/google/android/exoplayer2/source/smoothstreaming/manifest/SsManifestParserTest.java diff --git a/library/src/androidTest/java/com/google/android/exoplayer2/text/subrip/SubripDecoderTest.java b/library/core/src/androidTest/java/com/google/android/exoplayer2/text/subrip/SubripDecoderTest.java similarity index 100% rename from library/src/androidTest/java/com/google/android/exoplayer2/text/subrip/SubripDecoderTest.java rename to library/core/src/androidTest/java/com/google/android/exoplayer2/text/subrip/SubripDecoderTest.java diff --git a/library/src/androidTest/java/com/google/android/exoplayer2/text/ttml/TtmlDecoderTest.java b/library/core/src/androidTest/java/com/google/android/exoplayer2/text/ttml/TtmlDecoderTest.java similarity index 100% rename from library/src/androidTest/java/com/google/android/exoplayer2/text/ttml/TtmlDecoderTest.java rename to library/core/src/androidTest/java/com/google/android/exoplayer2/text/ttml/TtmlDecoderTest.java diff --git a/library/src/androidTest/java/com/google/android/exoplayer2/text/ttml/TtmlRenderUtilTest.java b/library/core/src/androidTest/java/com/google/android/exoplayer2/text/ttml/TtmlRenderUtilTest.java similarity index 100% rename from library/src/androidTest/java/com/google/android/exoplayer2/text/ttml/TtmlRenderUtilTest.java rename to library/core/src/androidTest/java/com/google/android/exoplayer2/text/ttml/TtmlRenderUtilTest.java diff --git a/library/src/androidTest/java/com/google/android/exoplayer2/text/ttml/TtmlStyleTest.java b/library/core/src/androidTest/java/com/google/android/exoplayer2/text/ttml/TtmlStyleTest.java similarity index 100% rename from library/src/androidTest/java/com/google/android/exoplayer2/text/ttml/TtmlStyleTest.java rename to library/core/src/androidTest/java/com/google/android/exoplayer2/text/ttml/TtmlStyleTest.java diff --git a/library/src/androidTest/java/com/google/android/exoplayer2/text/webvtt/CssParserTest.java b/library/core/src/androidTest/java/com/google/android/exoplayer2/text/webvtt/CssParserTest.java similarity index 100% rename from library/src/androidTest/java/com/google/android/exoplayer2/text/webvtt/CssParserTest.java rename to library/core/src/androidTest/java/com/google/android/exoplayer2/text/webvtt/CssParserTest.java diff --git a/library/src/androidTest/java/com/google/android/exoplayer2/text/webvtt/Mp4WebvttDecoderTest.java b/library/core/src/androidTest/java/com/google/android/exoplayer2/text/webvtt/Mp4WebvttDecoderTest.java similarity index 100% rename from library/src/androidTest/java/com/google/android/exoplayer2/text/webvtt/Mp4WebvttDecoderTest.java rename to library/core/src/androidTest/java/com/google/android/exoplayer2/text/webvtt/Mp4WebvttDecoderTest.java diff --git a/library/src/androidTest/java/com/google/android/exoplayer2/text/webvtt/WebvttCueParserTest.java b/library/core/src/androidTest/java/com/google/android/exoplayer2/text/webvtt/WebvttCueParserTest.java similarity index 100% rename from library/src/androidTest/java/com/google/android/exoplayer2/text/webvtt/WebvttCueParserTest.java rename to library/core/src/androidTest/java/com/google/android/exoplayer2/text/webvtt/WebvttCueParserTest.java diff --git a/library/src/androidTest/java/com/google/android/exoplayer2/text/webvtt/WebvttDecoderTest.java b/library/core/src/androidTest/java/com/google/android/exoplayer2/text/webvtt/WebvttDecoderTest.java similarity index 100% rename from library/src/androidTest/java/com/google/android/exoplayer2/text/webvtt/WebvttDecoderTest.java rename to library/core/src/androidTest/java/com/google/android/exoplayer2/text/webvtt/WebvttDecoderTest.java diff --git a/library/src/androidTest/java/com/google/android/exoplayer2/text/webvtt/WebvttSubtitleTest.java b/library/core/src/androidTest/java/com/google/android/exoplayer2/text/webvtt/WebvttSubtitleTest.java similarity index 100% rename from library/src/androidTest/java/com/google/android/exoplayer2/text/webvtt/WebvttSubtitleTest.java rename to library/core/src/androidTest/java/com/google/android/exoplayer2/text/webvtt/WebvttSubtitleTest.java diff --git a/library/src/androidTest/java/com/google/android/exoplayer2/upstream/ByteArrayDataSourceTest.java b/library/core/src/androidTest/java/com/google/android/exoplayer2/upstream/ByteArrayDataSourceTest.java similarity index 100% rename from library/src/androidTest/java/com/google/android/exoplayer2/upstream/ByteArrayDataSourceTest.java rename to library/core/src/androidTest/java/com/google/android/exoplayer2/upstream/ByteArrayDataSourceTest.java diff --git a/library/src/androidTest/java/com/google/android/exoplayer2/upstream/DataSourceInputStreamTest.java b/library/core/src/androidTest/java/com/google/android/exoplayer2/upstream/DataSourceInputStreamTest.java similarity index 100% rename from library/src/androidTest/java/com/google/android/exoplayer2/upstream/DataSourceInputStreamTest.java rename to library/core/src/androidTest/java/com/google/android/exoplayer2/upstream/DataSourceInputStreamTest.java diff --git a/library/src/androidTest/java/com/google/android/exoplayer2/upstream/cache/CacheDataSourceTest.java b/library/core/src/androidTest/java/com/google/android/exoplayer2/upstream/cache/CacheDataSourceTest.java similarity index 100% rename from library/src/androidTest/java/com/google/android/exoplayer2/upstream/cache/CacheDataSourceTest.java rename to library/core/src/androidTest/java/com/google/android/exoplayer2/upstream/cache/CacheDataSourceTest.java diff --git a/library/src/androidTest/java/com/google/android/exoplayer2/upstream/cache/CacheDataSourceTest2.java b/library/core/src/androidTest/java/com/google/android/exoplayer2/upstream/cache/CacheDataSourceTest2.java similarity index 100% rename from library/src/androidTest/java/com/google/android/exoplayer2/upstream/cache/CacheDataSourceTest2.java rename to library/core/src/androidTest/java/com/google/android/exoplayer2/upstream/cache/CacheDataSourceTest2.java diff --git a/library/src/androidTest/java/com/google/android/exoplayer2/upstream/cache/CachedContentIndexTest.java b/library/core/src/androidTest/java/com/google/android/exoplayer2/upstream/cache/CachedContentIndexTest.java similarity index 100% rename from library/src/androidTest/java/com/google/android/exoplayer2/upstream/cache/CachedContentIndexTest.java rename to library/core/src/androidTest/java/com/google/android/exoplayer2/upstream/cache/CachedContentIndexTest.java diff --git a/library/src/androidTest/java/com/google/android/exoplayer2/upstream/cache/CachedRegionTrackerTest.java b/library/core/src/androidTest/java/com/google/android/exoplayer2/upstream/cache/CachedRegionTrackerTest.java similarity index 100% rename from library/src/androidTest/java/com/google/android/exoplayer2/upstream/cache/CachedRegionTrackerTest.java rename to library/core/src/androidTest/java/com/google/android/exoplayer2/upstream/cache/CachedRegionTrackerTest.java diff --git a/library/src/androidTest/java/com/google/android/exoplayer2/upstream/cache/SimpleCacheSpanTest.java b/library/core/src/androidTest/java/com/google/android/exoplayer2/upstream/cache/SimpleCacheSpanTest.java similarity index 100% rename from library/src/androidTest/java/com/google/android/exoplayer2/upstream/cache/SimpleCacheSpanTest.java rename to library/core/src/androidTest/java/com/google/android/exoplayer2/upstream/cache/SimpleCacheSpanTest.java diff --git a/library/src/androidTest/java/com/google/android/exoplayer2/upstream/cache/SimpleCacheTest.java b/library/core/src/androidTest/java/com/google/android/exoplayer2/upstream/cache/SimpleCacheTest.java similarity index 100% rename from library/src/androidTest/java/com/google/android/exoplayer2/upstream/cache/SimpleCacheTest.java rename to library/core/src/androidTest/java/com/google/android/exoplayer2/upstream/cache/SimpleCacheTest.java diff --git a/library/src/androidTest/java/com/google/android/exoplayer2/upstream/crypto/AesFlushingCipherTest.java b/library/core/src/androidTest/java/com/google/android/exoplayer2/upstream/crypto/AesFlushingCipherTest.java similarity index 100% rename from library/src/androidTest/java/com/google/android/exoplayer2/upstream/crypto/AesFlushingCipherTest.java rename to library/core/src/androidTest/java/com/google/android/exoplayer2/upstream/crypto/AesFlushingCipherTest.java diff --git a/library/src/androidTest/java/com/google/android/exoplayer2/util/AtomicFileTest.java b/library/core/src/androidTest/java/com/google/android/exoplayer2/util/AtomicFileTest.java similarity index 100% rename from library/src/androidTest/java/com/google/android/exoplayer2/util/AtomicFileTest.java rename to library/core/src/androidTest/java/com/google/android/exoplayer2/util/AtomicFileTest.java diff --git a/library/src/androidTest/java/com/google/android/exoplayer2/util/ColorParserTest.java b/library/core/src/androidTest/java/com/google/android/exoplayer2/util/ColorParserTest.java similarity index 100% rename from library/src/androidTest/java/com/google/android/exoplayer2/util/ColorParserTest.java rename to library/core/src/androidTest/java/com/google/android/exoplayer2/util/ColorParserTest.java diff --git a/library/src/androidTest/java/com/google/android/exoplayer2/util/NalUnitUtilTest.java b/library/core/src/androidTest/java/com/google/android/exoplayer2/util/NalUnitUtilTest.java similarity index 100% rename from library/src/androidTest/java/com/google/android/exoplayer2/util/NalUnitUtilTest.java rename to library/core/src/androidTest/java/com/google/android/exoplayer2/util/NalUnitUtilTest.java diff --git a/library/src/androidTest/java/com/google/android/exoplayer2/util/ParsableByteArrayTest.java b/library/core/src/androidTest/java/com/google/android/exoplayer2/util/ParsableByteArrayTest.java similarity index 100% rename from library/src/androidTest/java/com/google/android/exoplayer2/util/ParsableByteArrayTest.java rename to library/core/src/androidTest/java/com/google/android/exoplayer2/util/ParsableByteArrayTest.java diff --git a/library/src/androidTest/java/com/google/android/exoplayer2/util/ParsableNalUnitBitArrayTest.java b/library/core/src/androidTest/java/com/google/android/exoplayer2/util/ParsableNalUnitBitArrayTest.java similarity index 100% rename from library/src/androidTest/java/com/google/android/exoplayer2/util/ParsableNalUnitBitArrayTest.java rename to library/core/src/androidTest/java/com/google/android/exoplayer2/util/ParsableNalUnitBitArrayTest.java diff --git a/library/src/androidTest/java/com/google/android/exoplayer2/util/ReusableBufferedOutputStreamTest.java b/library/core/src/androidTest/java/com/google/android/exoplayer2/util/ReusableBufferedOutputStreamTest.java similarity index 100% rename from library/src/androidTest/java/com/google/android/exoplayer2/util/ReusableBufferedOutputStreamTest.java rename to library/core/src/androidTest/java/com/google/android/exoplayer2/util/ReusableBufferedOutputStreamTest.java diff --git a/library/src/androidTest/java/com/google/android/exoplayer2/util/UriUtilTest.java b/library/core/src/androidTest/java/com/google/android/exoplayer2/util/UriUtilTest.java similarity index 100% rename from library/src/androidTest/java/com/google/android/exoplayer2/util/UriUtilTest.java rename to library/core/src/androidTest/java/com/google/android/exoplayer2/util/UriUtilTest.java diff --git a/library/src/androidTest/java/com/google/android/exoplayer2/util/UtilTest.java b/library/core/src/androidTest/java/com/google/android/exoplayer2/util/UtilTest.java similarity index 100% rename from library/src/androidTest/java/com/google/android/exoplayer2/util/UtilTest.java rename to library/core/src/androidTest/java/com/google/android/exoplayer2/util/UtilTest.java diff --git a/library/core/src/main/AndroidManifest.xml b/library/core/src/main/AndroidManifest.xml new file mode 100644 index 0000000000..430930a3ca --- /dev/null +++ b/library/core/src/main/AndroidManifest.xml @@ -0,0 +1,17 @@ + + + + diff --git a/library/src/main/java/com/google/android/exoplayer2/BaseRenderer.java b/library/core/src/main/java/com/google/android/exoplayer2/BaseRenderer.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/BaseRenderer.java rename to library/core/src/main/java/com/google/android/exoplayer2/BaseRenderer.java diff --git a/library/src/main/java/com/google/android/exoplayer2/C.java b/library/core/src/main/java/com/google/android/exoplayer2/C.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/C.java rename to library/core/src/main/java/com/google/android/exoplayer2/C.java diff --git a/library/src/main/java/com/google/android/exoplayer2/DefaultLoadControl.java b/library/core/src/main/java/com/google/android/exoplayer2/DefaultLoadControl.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/DefaultLoadControl.java rename to library/core/src/main/java/com/google/android/exoplayer2/DefaultLoadControl.java diff --git a/library/src/main/java/com/google/android/exoplayer2/ExoPlaybackException.java b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlaybackException.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/ExoPlaybackException.java rename to library/core/src/main/java/com/google/android/exoplayer2/ExoPlaybackException.java diff --git a/library/src/main/java/com/google/android/exoplayer2/ExoPlayer.java b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayer.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/ExoPlayer.java rename to library/core/src/main/java/com/google/android/exoplayer2/ExoPlayer.java diff --git a/library/src/main/java/com/google/android/exoplayer2/ExoPlayerFactory.java b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerFactory.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/ExoPlayerFactory.java rename to library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerFactory.java diff --git a/library/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java rename to library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java diff --git a/library/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java rename to library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java diff --git a/library/src/main/java/com/google/android/exoplayer2/ExoPlayerLibraryInfo.java b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerLibraryInfo.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/ExoPlayerLibraryInfo.java rename to library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerLibraryInfo.java diff --git a/library/src/main/java/com/google/android/exoplayer2/Format.java b/library/core/src/main/java/com/google/android/exoplayer2/Format.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/Format.java rename to library/core/src/main/java/com/google/android/exoplayer2/Format.java diff --git a/library/src/main/java/com/google/android/exoplayer2/FormatHolder.java b/library/core/src/main/java/com/google/android/exoplayer2/FormatHolder.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/FormatHolder.java rename to library/core/src/main/java/com/google/android/exoplayer2/FormatHolder.java diff --git a/library/src/main/java/com/google/android/exoplayer2/IllegalSeekPositionException.java b/library/core/src/main/java/com/google/android/exoplayer2/IllegalSeekPositionException.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/IllegalSeekPositionException.java rename to library/core/src/main/java/com/google/android/exoplayer2/IllegalSeekPositionException.java diff --git a/library/src/main/java/com/google/android/exoplayer2/LoadControl.java b/library/core/src/main/java/com/google/android/exoplayer2/LoadControl.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/LoadControl.java rename to library/core/src/main/java/com/google/android/exoplayer2/LoadControl.java diff --git a/library/src/main/java/com/google/android/exoplayer2/ParserException.java b/library/core/src/main/java/com/google/android/exoplayer2/ParserException.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/ParserException.java rename to library/core/src/main/java/com/google/android/exoplayer2/ParserException.java diff --git a/library/src/main/java/com/google/android/exoplayer2/Renderer.java b/library/core/src/main/java/com/google/android/exoplayer2/Renderer.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/Renderer.java rename to library/core/src/main/java/com/google/android/exoplayer2/Renderer.java diff --git a/library/src/main/java/com/google/android/exoplayer2/RendererCapabilities.java b/library/core/src/main/java/com/google/android/exoplayer2/RendererCapabilities.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/RendererCapabilities.java rename to library/core/src/main/java/com/google/android/exoplayer2/RendererCapabilities.java diff --git a/library/src/main/java/com/google/android/exoplayer2/RendererConfiguration.java b/library/core/src/main/java/com/google/android/exoplayer2/RendererConfiguration.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/RendererConfiguration.java rename to library/core/src/main/java/com/google/android/exoplayer2/RendererConfiguration.java diff --git a/library/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java b/library/core/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java rename to library/core/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java diff --git a/library/src/main/java/com/google/android/exoplayer2/Timeline.java b/library/core/src/main/java/com/google/android/exoplayer2/Timeline.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/Timeline.java rename to library/core/src/main/java/com/google/android/exoplayer2/Timeline.java diff --git a/library/src/main/java/com/google/android/exoplayer2/audio/Ac3Util.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/Ac3Util.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/audio/Ac3Util.java rename to library/core/src/main/java/com/google/android/exoplayer2/audio/Ac3Util.java diff --git a/library/src/main/java/com/google/android/exoplayer2/audio/AudioCapabilities.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioCapabilities.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/audio/AudioCapabilities.java rename to library/core/src/main/java/com/google/android/exoplayer2/audio/AudioCapabilities.java diff --git a/library/src/main/java/com/google/android/exoplayer2/audio/AudioCapabilitiesReceiver.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioCapabilitiesReceiver.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/audio/AudioCapabilitiesReceiver.java rename to library/core/src/main/java/com/google/android/exoplayer2/audio/AudioCapabilitiesReceiver.java diff --git a/library/src/main/java/com/google/android/exoplayer2/audio/AudioDecoderException.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioDecoderException.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/audio/AudioDecoderException.java rename to library/core/src/main/java/com/google/android/exoplayer2/audio/AudioDecoderException.java diff --git a/library/src/main/java/com/google/android/exoplayer2/audio/AudioProcessor.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioProcessor.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/audio/AudioProcessor.java rename to library/core/src/main/java/com/google/android/exoplayer2/audio/AudioProcessor.java diff --git a/library/src/main/java/com/google/android/exoplayer2/audio/AudioRendererEventListener.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioRendererEventListener.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/audio/AudioRendererEventListener.java rename to library/core/src/main/java/com/google/android/exoplayer2/audio/AudioRendererEventListener.java diff --git a/library/src/main/java/com/google/android/exoplayer2/audio/AudioTrack.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioTrack.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/audio/AudioTrack.java rename to library/core/src/main/java/com/google/android/exoplayer2/audio/AudioTrack.java diff --git a/library/src/main/java/com/google/android/exoplayer2/audio/ChannelMappingAudioProcessor.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/ChannelMappingAudioProcessor.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/audio/ChannelMappingAudioProcessor.java rename to library/core/src/main/java/com/google/android/exoplayer2/audio/ChannelMappingAudioProcessor.java diff --git a/library/src/main/java/com/google/android/exoplayer2/audio/DtsUtil.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/DtsUtil.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/audio/DtsUtil.java rename to library/core/src/main/java/com/google/android/exoplayer2/audio/DtsUtil.java diff --git a/library/src/main/java/com/google/android/exoplayer2/audio/MediaCodecAudioRenderer.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/MediaCodecAudioRenderer.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/audio/MediaCodecAudioRenderer.java rename to library/core/src/main/java/com/google/android/exoplayer2/audio/MediaCodecAudioRenderer.java diff --git a/library/src/main/java/com/google/android/exoplayer2/audio/ResamplingAudioProcessor.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/ResamplingAudioProcessor.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/audio/ResamplingAudioProcessor.java rename to library/core/src/main/java/com/google/android/exoplayer2/audio/ResamplingAudioProcessor.java diff --git a/library/src/main/java/com/google/android/exoplayer2/audio/SimpleDecoderAudioRenderer.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/SimpleDecoderAudioRenderer.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/audio/SimpleDecoderAudioRenderer.java rename to library/core/src/main/java/com/google/android/exoplayer2/audio/SimpleDecoderAudioRenderer.java diff --git a/library/src/main/java/com/google/android/exoplayer2/decoder/Buffer.java b/library/core/src/main/java/com/google/android/exoplayer2/decoder/Buffer.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/decoder/Buffer.java rename to library/core/src/main/java/com/google/android/exoplayer2/decoder/Buffer.java diff --git a/library/src/main/java/com/google/android/exoplayer2/decoder/CryptoInfo.java b/library/core/src/main/java/com/google/android/exoplayer2/decoder/CryptoInfo.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/decoder/CryptoInfo.java rename to library/core/src/main/java/com/google/android/exoplayer2/decoder/CryptoInfo.java diff --git a/library/src/main/java/com/google/android/exoplayer2/decoder/Decoder.java b/library/core/src/main/java/com/google/android/exoplayer2/decoder/Decoder.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/decoder/Decoder.java rename to library/core/src/main/java/com/google/android/exoplayer2/decoder/Decoder.java diff --git a/library/src/main/java/com/google/android/exoplayer2/decoder/DecoderCounters.java b/library/core/src/main/java/com/google/android/exoplayer2/decoder/DecoderCounters.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/decoder/DecoderCounters.java rename to library/core/src/main/java/com/google/android/exoplayer2/decoder/DecoderCounters.java diff --git a/library/src/main/java/com/google/android/exoplayer2/decoder/DecoderInputBuffer.java b/library/core/src/main/java/com/google/android/exoplayer2/decoder/DecoderInputBuffer.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/decoder/DecoderInputBuffer.java rename to library/core/src/main/java/com/google/android/exoplayer2/decoder/DecoderInputBuffer.java diff --git a/library/src/main/java/com/google/android/exoplayer2/decoder/OutputBuffer.java b/library/core/src/main/java/com/google/android/exoplayer2/decoder/OutputBuffer.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/decoder/OutputBuffer.java rename to library/core/src/main/java/com/google/android/exoplayer2/decoder/OutputBuffer.java diff --git a/library/src/main/java/com/google/android/exoplayer2/decoder/SimpleDecoder.java b/library/core/src/main/java/com/google/android/exoplayer2/decoder/SimpleDecoder.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/decoder/SimpleDecoder.java rename to library/core/src/main/java/com/google/android/exoplayer2/decoder/SimpleDecoder.java diff --git a/library/src/main/java/com/google/android/exoplayer2/decoder/SimpleOutputBuffer.java b/library/core/src/main/java/com/google/android/exoplayer2/decoder/SimpleOutputBuffer.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/decoder/SimpleOutputBuffer.java rename to library/core/src/main/java/com/google/android/exoplayer2/decoder/SimpleOutputBuffer.java diff --git a/library/src/main/java/com/google/android/exoplayer2/drm/DecryptionException.java b/library/core/src/main/java/com/google/android/exoplayer2/drm/DecryptionException.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/drm/DecryptionException.java rename to library/core/src/main/java/com/google/android/exoplayer2/drm/DecryptionException.java diff --git a/library/src/main/java/com/google/android/exoplayer2/drm/DefaultDrmSessionManager.java b/library/core/src/main/java/com/google/android/exoplayer2/drm/DefaultDrmSessionManager.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/drm/DefaultDrmSessionManager.java rename to library/core/src/main/java/com/google/android/exoplayer2/drm/DefaultDrmSessionManager.java diff --git a/library/src/main/java/com/google/android/exoplayer2/drm/DrmInitData.java b/library/core/src/main/java/com/google/android/exoplayer2/drm/DrmInitData.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/drm/DrmInitData.java rename to library/core/src/main/java/com/google/android/exoplayer2/drm/DrmInitData.java diff --git a/library/src/main/java/com/google/android/exoplayer2/drm/DrmSession.java b/library/core/src/main/java/com/google/android/exoplayer2/drm/DrmSession.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/drm/DrmSession.java rename to library/core/src/main/java/com/google/android/exoplayer2/drm/DrmSession.java diff --git a/library/src/main/java/com/google/android/exoplayer2/drm/DrmSessionManager.java b/library/core/src/main/java/com/google/android/exoplayer2/drm/DrmSessionManager.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/drm/DrmSessionManager.java rename to library/core/src/main/java/com/google/android/exoplayer2/drm/DrmSessionManager.java diff --git a/library/src/main/java/com/google/android/exoplayer2/drm/ExoMediaCrypto.java b/library/core/src/main/java/com/google/android/exoplayer2/drm/ExoMediaCrypto.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/drm/ExoMediaCrypto.java rename to library/core/src/main/java/com/google/android/exoplayer2/drm/ExoMediaCrypto.java diff --git a/library/src/main/java/com/google/android/exoplayer2/drm/ExoMediaDrm.java b/library/core/src/main/java/com/google/android/exoplayer2/drm/ExoMediaDrm.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/drm/ExoMediaDrm.java rename to library/core/src/main/java/com/google/android/exoplayer2/drm/ExoMediaDrm.java diff --git a/library/src/main/java/com/google/android/exoplayer2/drm/FrameworkMediaCrypto.java b/library/core/src/main/java/com/google/android/exoplayer2/drm/FrameworkMediaCrypto.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/drm/FrameworkMediaCrypto.java rename to library/core/src/main/java/com/google/android/exoplayer2/drm/FrameworkMediaCrypto.java diff --git a/library/src/main/java/com/google/android/exoplayer2/drm/FrameworkMediaDrm.java b/library/core/src/main/java/com/google/android/exoplayer2/drm/FrameworkMediaDrm.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/drm/FrameworkMediaDrm.java rename to library/core/src/main/java/com/google/android/exoplayer2/drm/FrameworkMediaDrm.java diff --git a/library/src/main/java/com/google/android/exoplayer2/drm/HttpMediaDrmCallback.java b/library/core/src/main/java/com/google/android/exoplayer2/drm/HttpMediaDrmCallback.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/drm/HttpMediaDrmCallback.java rename to library/core/src/main/java/com/google/android/exoplayer2/drm/HttpMediaDrmCallback.java diff --git a/library/src/main/java/com/google/android/exoplayer2/drm/KeysExpiredException.java b/library/core/src/main/java/com/google/android/exoplayer2/drm/KeysExpiredException.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/drm/KeysExpiredException.java rename to library/core/src/main/java/com/google/android/exoplayer2/drm/KeysExpiredException.java diff --git a/library/src/main/java/com/google/android/exoplayer2/drm/MediaDrmCallback.java b/library/core/src/main/java/com/google/android/exoplayer2/drm/MediaDrmCallback.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/drm/MediaDrmCallback.java rename to library/core/src/main/java/com/google/android/exoplayer2/drm/MediaDrmCallback.java diff --git a/library/src/main/java/com/google/android/exoplayer2/drm/OfflineLicenseHelper.java b/library/core/src/main/java/com/google/android/exoplayer2/drm/OfflineLicenseHelper.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/drm/OfflineLicenseHelper.java rename to library/core/src/main/java/com/google/android/exoplayer2/drm/OfflineLicenseHelper.java diff --git a/library/src/main/java/com/google/android/exoplayer2/drm/UnsupportedDrmException.java b/library/core/src/main/java/com/google/android/exoplayer2/drm/UnsupportedDrmException.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/drm/UnsupportedDrmException.java rename to library/core/src/main/java/com/google/android/exoplayer2/drm/UnsupportedDrmException.java diff --git a/library/src/main/java/com/google/android/exoplayer2/drm/WidevineUtil.java b/library/core/src/main/java/com/google/android/exoplayer2/drm/WidevineUtil.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/drm/WidevineUtil.java rename to library/core/src/main/java/com/google/android/exoplayer2/drm/WidevineUtil.java diff --git a/library/src/main/java/com/google/android/exoplayer2/extractor/ChunkIndex.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ChunkIndex.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/extractor/ChunkIndex.java rename to library/core/src/main/java/com/google/android/exoplayer2/extractor/ChunkIndex.java diff --git a/library/src/main/java/com/google/android/exoplayer2/extractor/DefaultExtractorInput.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/DefaultExtractorInput.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/extractor/DefaultExtractorInput.java rename to library/core/src/main/java/com/google/android/exoplayer2/extractor/DefaultExtractorInput.java diff --git a/library/src/main/java/com/google/android/exoplayer2/extractor/DefaultExtractorsFactory.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/DefaultExtractorsFactory.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/extractor/DefaultExtractorsFactory.java rename to library/core/src/main/java/com/google/android/exoplayer2/extractor/DefaultExtractorsFactory.java diff --git a/library/src/main/java/com/google/android/exoplayer2/extractor/DefaultTrackOutput.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/DefaultTrackOutput.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/extractor/DefaultTrackOutput.java rename to library/core/src/main/java/com/google/android/exoplayer2/extractor/DefaultTrackOutput.java diff --git a/library/src/main/java/com/google/android/exoplayer2/extractor/DummyTrackOutput.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/DummyTrackOutput.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/extractor/DummyTrackOutput.java rename to library/core/src/main/java/com/google/android/exoplayer2/extractor/DummyTrackOutput.java diff --git a/library/src/main/java/com/google/android/exoplayer2/extractor/Extractor.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/Extractor.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/extractor/Extractor.java rename to library/core/src/main/java/com/google/android/exoplayer2/extractor/Extractor.java diff --git a/library/src/main/java/com/google/android/exoplayer2/extractor/ExtractorInput.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ExtractorInput.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/extractor/ExtractorInput.java rename to library/core/src/main/java/com/google/android/exoplayer2/extractor/ExtractorInput.java diff --git a/library/src/main/java/com/google/android/exoplayer2/extractor/ExtractorOutput.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ExtractorOutput.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/extractor/ExtractorOutput.java rename to library/core/src/main/java/com/google/android/exoplayer2/extractor/ExtractorOutput.java diff --git a/library/src/main/java/com/google/android/exoplayer2/extractor/ExtractorsFactory.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ExtractorsFactory.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/extractor/ExtractorsFactory.java rename to library/core/src/main/java/com/google/android/exoplayer2/extractor/ExtractorsFactory.java diff --git a/library/src/main/java/com/google/android/exoplayer2/extractor/GaplessInfoHolder.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/GaplessInfoHolder.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/extractor/GaplessInfoHolder.java rename to library/core/src/main/java/com/google/android/exoplayer2/extractor/GaplessInfoHolder.java diff --git a/library/src/main/java/com/google/android/exoplayer2/extractor/MpegAudioHeader.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/MpegAudioHeader.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/extractor/MpegAudioHeader.java rename to library/core/src/main/java/com/google/android/exoplayer2/extractor/MpegAudioHeader.java diff --git a/library/src/main/java/com/google/android/exoplayer2/extractor/PositionHolder.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/PositionHolder.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/extractor/PositionHolder.java rename to library/core/src/main/java/com/google/android/exoplayer2/extractor/PositionHolder.java diff --git a/library/src/main/java/com/google/android/exoplayer2/extractor/SeekMap.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/SeekMap.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/extractor/SeekMap.java rename to library/core/src/main/java/com/google/android/exoplayer2/extractor/SeekMap.java diff --git a/library/src/main/java/com/google/android/exoplayer2/extractor/TrackOutput.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/TrackOutput.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/extractor/TrackOutput.java rename to library/core/src/main/java/com/google/android/exoplayer2/extractor/TrackOutput.java diff --git a/library/src/main/java/com/google/android/exoplayer2/extractor/flv/AudioTagPayloadReader.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/flv/AudioTagPayloadReader.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/extractor/flv/AudioTagPayloadReader.java rename to library/core/src/main/java/com/google/android/exoplayer2/extractor/flv/AudioTagPayloadReader.java diff --git a/library/src/main/java/com/google/android/exoplayer2/extractor/flv/FlvExtractor.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/flv/FlvExtractor.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/extractor/flv/FlvExtractor.java rename to library/core/src/main/java/com/google/android/exoplayer2/extractor/flv/FlvExtractor.java diff --git a/library/src/main/java/com/google/android/exoplayer2/extractor/flv/ScriptTagPayloadReader.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/flv/ScriptTagPayloadReader.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/extractor/flv/ScriptTagPayloadReader.java rename to library/core/src/main/java/com/google/android/exoplayer2/extractor/flv/ScriptTagPayloadReader.java diff --git a/library/src/main/java/com/google/android/exoplayer2/extractor/flv/TagPayloadReader.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/flv/TagPayloadReader.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/extractor/flv/TagPayloadReader.java rename to library/core/src/main/java/com/google/android/exoplayer2/extractor/flv/TagPayloadReader.java diff --git a/library/src/main/java/com/google/android/exoplayer2/extractor/flv/VideoTagPayloadReader.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/flv/VideoTagPayloadReader.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/extractor/flv/VideoTagPayloadReader.java rename to library/core/src/main/java/com/google/android/exoplayer2/extractor/flv/VideoTagPayloadReader.java diff --git a/library/src/main/java/com/google/android/exoplayer2/extractor/mkv/DefaultEbmlReader.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mkv/DefaultEbmlReader.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/extractor/mkv/DefaultEbmlReader.java rename to library/core/src/main/java/com/google/android/exoplayer2/extractor/mkv/DefaultEbmlReader.java diff --git a/library/src/main/java/com/google/android/exoplayer2/extractor/mkv/EbmlReader.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mkv/EbmlReader.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/extractor/mkv/EbmlReader.java rename to library/core/src/main/java/com/google/android/exoplayer2/extractor/mkv/EbmlReader.java diff --git a/library/src/main/java/com/google/android/exoplayer2/extractor/mkv/EbmlReaderOutput.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mkv/EbmlReaderOutput.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/extractor/mkv/EbmlReaderOutput.java rename to library/core/src/main/java/com/google/android/exoplayer2/extractor/mkv/EbmlReaderOutput.java diff --git a/library/src/main/java/com/google/android/exoplayer2/extractor/mkv/MatroskaExtractor.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mkv/MatroskaExtractor.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/extractor/mkv/MatroskaExtractor.java rename to library/core/src/main/java/com/google/android/exoplayer2/extractor/mkv/MatroskaExtractor.java diff --git a/library/src/main/java/com/google/android/exoplayer2/extractor/mkv/Sniffer.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mkv/Sniffer.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/extractor/mkv/Sniffer.java rename to library/core/src/main/java/com/google/android/exoplayer2/extractor/mkv/Sniffer.java diff --git a/library/src/main/java/com/google/android/exoplayer2/extractor/mkv/VarintReader.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mkv/VarintReader.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/extractor/mkv/VarintReader.java rename to library/core/src/main/java/com/google/android/exoplayer2/extractor/mkv/VarintReader.java diff --git a/library/src/main/java/com/google/android/exoplayer2/extractor/mp3/ConstantBitrateSeeker.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp3/ConstantBitrateSeeker.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/extractor/mp3/ConstantBitrateSeeker.java rename to library/core/src/main/java/com/google/android/exoplayer2/extractor/mp3/ConstantBitrateSeeker.java diff --git a/library/src/main/java/com/google/android/exoplayer2/extractor/mp3/Mp3Extractor.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp3/Mp3Extractor.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/extractor/mp3/Mp3Extractor.java rename to library/core/src/main/java/com/google/android/exoplayer2/extractor/mp3/Mp3Extractor.java diff --git a/library/src/main/java/com/google/android/exoplayer2/extractor/mp3/VbriSeeker.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp3/VbriSeeker.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/extractor/mp3/VbriSeeker.java rename to library/core/src/main/java/com/google/android/exoplayer2/extractor/mp3/VbriSeeker.java diff --git a/library/src/main/java/com/google/android/exoplayer2/extractor/mp3/XingSeeker.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp3/XingSeeker.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/extractor/mp3/XingSeeker.java rename to library/core/src/main/java/com/google/android/exoplayer2/extractor/mp3/XingSeeker.java diff --git a/library/src/main/java/com/google/android/exoplayer2/extractor/mp4/Atom.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/Atom.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/extractor/mp4/Atom.java rename to library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/Atom.java diff --git a/library/src/main/java/com/google/android/exoplayer2/extractor/mp4/AtomParsers.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/AtomParsers.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/extractor/mp4/AtomParsers.java rename to library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/AtomParsers.java diff --git a/library/src/main/java/com/google/android/exoplayer2/extractor/mp4/DefaultSampleValues.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/DefaultSampleValues.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/extractor/mp4/DefaultSampleValues.java rename to library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/DefaultSampleValues.java diff --git a/library/src/main/java/com/google/android/exoplayer2/extractor/mp4/FixedSampleSizeRechunker.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/FixedSampleSizeRechunker.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/extractor/mp4/FixedSampleSizeRechunker.java rename to library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/FixedSampleSizeRechunker.java diff --git a/library/src/main/java/com/google/android/exoplayer2/extractor/mp4/FragmentedMp4Extractor.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/FragmentedMp4Extractor.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/extractor/mp4/FragmentedMp4Extractor.java rename to library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/FragmentedMp4Extractor.java diff --git a/library/src/main/java/com/google/android/exoplayer2/extractor/mp4/MetadataUtil.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/MetadataUtil.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/extractor/mp4/MetadataUtil.java rename to library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/MetadataUtil.java diff --git a/library/src/main/java/com/google/android/exoplayer2/extractor/mp4/Mp4Extractor.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/Mp4Extractor.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/extractor/mp4/Mp4Extractor.java rename to library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/Mp4Extractor.java diff --git a/library/src/main/java/com/google/android/exoplayer2/extractor/mp4/PsshAtomUtil.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/PsshAtomUtil.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/extractor/mp4/PsshAtomUtil.java rename to library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/PsshAtomUtil.java diff --git a/library/src/main/java/com/google/android/exoplayer2/extractor/mp4/Sniffer.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/Sniffer.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/extractor/mp4/Sniffer.java rename to library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/Sniffer.java diff --git a/library/src/main/java/com/google/android/exoplayer2/extractor/mp4/Track.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/Track.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/extractor/mp4/Track.java rename to library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/Track.java diff --git a/library/src/main/java/com/google/android/exoplayer2/extractor/mp4/TrackEncryptionBox.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/TrackEncryptionBox.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/extractor/mp4/TrackEncryptionBox.java rename to library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/TrackEncryptionBox.java diff --git a/library/src/main/java/com/google/android/exoplayer2/extractor/mp4/TrackFragment.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/TrackFragment.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/extractor/mp4/TrackFragment.java rename to library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/TrackFragment.java diff --git a/library/src/main/java/com/google/android/exoplayer2/extractor/mp4/TrackSampleTable.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/TrackSampleTable.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/extractor/mp4/TrackSampleTable.java rename to library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/TrackSampleTable.java diff --git a/library/src/main/java/com/google/android/exoplayer2/extractor/ogg/DefaultOggSeeker.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ogg/DefaultOggSeeker.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/extractor/ogg/DefaultOggSeeker.java rename to library/core/src/main/java/com/google/android/exoplayer2/extractor/ogg/DefaultOggSeeker.java diff --git a/library/src/main/java/com/google/android/exoplayer2/extractor/ogg/FlacReader.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ogg/FlacReader.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/extractor/ogg/FlacReader.java rename to library/core/src/main/java/com/google/android/exoplayer2/extractor/ogg/FlacReader.java diff --git a/library/src/main/java/com/google/android/exoplayer2/extractor/ogg/OggExtractor.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ogg/OggExtractor.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/extractor/ogg/OggExtractor.java rename to library/core/src/main/java/com/google/android/exoplayer2/extractor/ogg/OggExtractor.java diff --git a/library/src/main/java/com/google/android/exoplayer2/extractor/ogg/OggPacket.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ogg/OggPacket.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/extractor/ogg/OggPacket.java rename to library/core/src/main/java/com/google/android/exoplayer2/extractor/ogg/OggPacket.java diff --git a/library/src/main/java/com/google/android/exoplayer2/extractor/ogg/OggPageHeader.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ogg/OggPageHeader.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/extractor/ogg/OggPageHeader.java rename to library/core/src/main/java/com/google/android/exoplayer2/extractor/ogg/OggPageHeader.java diff --git a/library/src/main/java/com/google/android/exoplayer2/extractor/ogg/OggSeeker.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ogg/OggSeeker.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/extractor/ogg/OggSeeker.java rename to library/core/src/main/java/com/google/android/exoplayer2/extractor/ogg/OggSeeker.java diff --git a/library/src/main/java/com/google/android/exoplayer2/extractor/ogg/OpusReader.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ogg/OpusReader.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/extractor/ogg/OpusReader.java rename to library/core/src/main/java/com/google/android/exoplayer2/extractor/ogg/OpusReader.java diff --git a/library/src/main/java/com/google/android/exoplayer2/extractor/ogg/StreamReader.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ogg/StreamReader.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/extractor/ogg/StreamReader.java rename to library/core/src/main/java/com/google/android/exoplayer2/extractor/ogg/StreamReader.java diff --git a/library/src/main/java/com/google/android/exoplayer2/extractor/ogg/VorbisBitArray.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ogg/VorbisBitArray.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/extractor/ogg/VorbisBitArray.java rename to library/core/src/main/java/com/google/android/exoplayer2/extractor/ogg/VorbisBitArray.java diff --git a/library/src/main/java/com/google/android/exoplayer2/extractor/ogg/VorbisReader.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ogg/VorbisReader.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/extractor/ogg/VorbisReader.java rename to library/core/src/main/java/com/google/android/exoplayer2/extractor/ogg/VorbisReader.java diff --git a/library/src/main/java/com/google/android/exoplayer2/extractor/ogg/VorbisUtil.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ogg/VorbisUtil.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/extractor/ogg/VorbisUtil.java rename to library/core/src/main/java/com/google/android/exoplayer2/extractor/ogg/VorbisUtil.java diff --git a/library/src/main/java/com/google/android/exoplayer2/extractor/rawcc/RawCcExtractor.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/rawcc/RawCcExtractor.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/extractor/rawcc/RawCcExtractor.java rename to library/core/src/main/java/com/google/android/exoplayer2/extractor/rawcc/RawCcExtractor.java diff --git a/library/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 similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/extractor/ts/Ac3Extractor.java rename to library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/Ac3Extractor.java diff --git a/library/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 similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/extractor/ts/Ac3Reader.java rename to library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/Ac3Reader.java diff --git a/library/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 similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/extractor/ts/AdtsExtractor.java rename to library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/AdtsExtractor.java diff --git a/library/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 similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/extractor/ts/AdtsReader.java rename to library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/AdtsReader.java diff --git a/library/src/main/java/com/google/android/exoplayer2/extractor/ts/DefaultTsPayloadReaderFactory.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/DefaultTsPayloadReaderFactory.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/extractor/ts/DefaultTsPayloadReaderFactory.java rename to library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/DefaultTsPayloadReaderFactory.java diff --git a/library/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 similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/extractor/ts/DtsReader.java rename to library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/DtsReader.java diff --git a/library/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 similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/extractor/ts/ElementaryStreamReader.java rename to library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/ElementaryStreamReader.java diff --git a/library/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 similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/extractor/ts/H262Reader.java rename to library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/H262Reader.java diff --git a/library/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 similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/extractor/ts/H264Reader.java rename to library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/H264Reader.java diff --git a/library/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 similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/extractor/ts/H265Reader.java rename to library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/H265Reader.java diff --git a/library/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 similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/extractor/ts/Id3Reader.java rename to library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/Id3Reader.java diff --git a/library/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 similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/extractor/ts/MpegAudioReader.java rename to library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/MpegAudioReader.java diff --git a/library/src/main/java/com/google/android/exoplayer2/extractor/ts/NalUnitTargetBuffer.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/NalUnitTargetBuffer.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/extractor/ts/NalUnitTargetBuffer.java rename to library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/NalUnitTargetBuffer.java diff --git a/library/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 similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/extractor/ts/PesReader.java rename to library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/PesReader.java diff --git a/library/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 similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/extractor/ts/PsExtractor.java rename to library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/PsExtractor.java diff --git a/library/src/main/java/com/google/android/exoplayer2/extractor/ts/SectionPayloadReader.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/SectionPayloadReader.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/extractor/ts/SectionPayloadReader.java rename to library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/SectionPayloadReader.java diff --git a/library/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 similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/extractor/ts/SectionReader.java rename to library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/SectionReader.java diff --git a/library/src/main/java/com/google/android/exoplayer2/extractor/ts/SeiReader.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/SeiReader.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/extractor/ts/SeiReader.java rename to library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/SeiReader.java diff --git a/library/src/main/java/com/google/android/exoplayer2/extractor/ts/SpliceInfoSectionReader.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/SpliceInfoSectionReader.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/extractor/ts/SpliceInfoSectionReader.java rename to library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/SpliceInfoSectionReader.java diff --git a/library/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 similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/extractor/ts/TsExtractor.java rename to library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/TsExtractor.java diff --git a/library/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 similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/extractor/ts/TsPayloadReader.java rename to library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/TsPayloadReader.java diff --git a/library/src/main/java/com/google/android/exoplayer2/extractor/wav/WavExtractor.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/wav/WavExtractor.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/extractor/wav/WavExtractor.java rename to library/core/src/main/java/com/google/android/exoplayer2/extractor/wav/WavExtractor.java diff --git a/library/src/main/java/com/google/android/exoplayer2/extractor/wav/WavHeader.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/wav/WavHeader.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/extractor/wav/WavHeader.java rename to library/core/src/main/java/com/google/android/exoplayer2/extractor/wav/WavHeader.java diff --git a/library/src/main/java/com/google/android/exoplayer2/extractor/wav/WavHeaderReader.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/wav/WavHeaderReader.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/extractor/wav/WavHeaderReader.java rename to library/core/src/main/java/com/google/android/exoplayer2/extractor/wav/WavHeaderReader.java diff --git a/library/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecInfo.java b/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecInfo.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecInfo.java rename to library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecInfo.java diff --git a/library/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.java b/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.java rename to library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.java diff --git a/library/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecSelector.java b/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecSelector.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecSelector.java rename to library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecSelector.java diff --git a/library/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecUtil.java b/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecUtil.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecUtil.java rename to library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecUtil.java diff --git a/library/src/main/java/com/google/android/exoplayer2/metadata/Metadata.java b/library/core/src/main/java/com/google/android/exoplayer2/metadata/Metadata.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/metadata/Metadata.java rename to library/core/src/main/java/com/google/android/exoplayer2/metadata/Metadata.java diff --git a/library/src/main/java/com/google/android/exoplayer2/metadata/MetadataDecoder.java b/library/core/src/main/java/com/google/android/exoplayer2/metadata/MetadataDecoder.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/metadata/MetadataDecoder.java rename to library/core/src/main/java/com/google/android/exoplayer2/metadata/MetadataDecoder.java diff --git a/library/src/main/java/com/google/android/exoplayer2/metadata/MetadataDecoderException.java b/library/core/src/main/java/com/google/android/exoplayer2/metadata/MetadataDecoderException.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/metadata/MetadataDecoderException.java rename to library/core/src/main/java/com/google/android/exoplayer2/metadata/MetadataDecoderException.java diff --git a/library/src/main/java/com/google/android/exoplayer2/metadata/MetadataDecoderFactory.java b/library/core/src/main/java/com/google/android/exoplayer2/metadata/MetadataDecoderFactory.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/metadata/MetadataDecoderFactory.java rename to library/core/src/main/java/com/google/android/exoplayer2/metadata/MetadataDecoderFactory.java diff --git a/library/src/main/java/com/google/android/exoplayer2/metadata/MetadataInputBuffer.java b/library/core/src/main/java/com/google/android/exoplayer2/metadata/MetadataInputBuffer.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/metadata/MetadataInputBuffer.java rename to library/core/src/main/java/com/google/android/exoplayer2/metadata/MetadataInputBuffer.java diff --git a/library/src/main/java/com/google/android/exoplayer2/metadata/MetadataRenderer.java b/library/core/src/main/java/com/google/android/exoplayer2/metadata/MetadataRenderer.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/metadata/MetadataRenderer.java rename to library/core/src/main/java/com/google/android/exoplayer2/metadata/MetadataRenderer.java diff --git a/library/src/main/java/com/google/android/exoplayer2/metadata/emsg/EventMessage.java b/library/core/src/main/java/com/google/android/exoplayer2/metadata/emsg/EventMessage.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/metadata/emsg/EventMessage.java rename to library/core/src/main/java/com/google/android/exoplayer2/metadata/emsg/EventMessage.java diff --git a/library/src/main/java/com/google/android/exoplayer2/metadata/emsg/EventMessageDecoder.java b/library/core/src/main/java/com/google/android/exoplayer2/metadata/emsg/EventMessageDecoder.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/metadata/emsg/EventMessageDecoder.java rename to library/core/src/main/java/com/google/android/exoplayer2/metadata/emsg/EventMessageDecoder.java diff --git a/library/src/main/java/com/google/android/exoplayer2/metadata/id3/ApicFrame.java b/library/core/src/main/java/com/google/android/exoplayer2/metadata/id3/ApicFrame.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/metadata/id3/ApicFrame.java rename to library/core/src/main/java/com/google/android/exoplayer2/metadata/id3/ApicFrame.java diff --git a/library/src/main/java/com/google/android/exoplayer2/metadata/id3/BinaryFrame.java b/library/core/src/main/java/com/google/android/exoplayer2/metadata/id3/BinaryFrame.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/metadata/id3/BinaryFrame.java rename to library/core/src/main/java/com/google/android/exoplayer2/metadata/id3/BinaryFrame.java diff --git a/library/src/main/java/com/google/android/exoplayer2/metadata/id3/ChapterFrame.java b/library/core/src/main/java/com/google/android/exoplayer2/metadata/id3/ChapterFrame.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/metadata/id3/ChapterFrame.java rename to library/core/src/main/java/com/google/android/exoplayer2/metadata/id3/ChapterFrame.java diff --git a/library/src/main/java/com/google/android/exoplayer2/metadata/id3/ChapterTocFrame.java b/library/core/src/main/java/com/google/android/exoplayer2/metadata/id3/ChapterTocFrame.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/metadata/id3/ChapterTocFrame.java rename to library/core/src/main/java/com/google/android/exoplayer2/metadata/id3/ChapterTocFrame.java diff --git a/library/src/main/java/com/google/android/exoplayer2/metadata/id3/CommentFrame.java b/library/core/src/main/java/com/google/android/exoplayer2/metadata/id3/CommentFrame.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/metadata/id3/CommentFrame.java rename to library/core/src/main/java/com/google/android/exoplayer2/metadata/id3/CommentFrame.java diff --git a/library/src/main/java/com/google/android/exoplayer2/metadata/id3/GeobFrame.java b/library/core/src/main/java/com/google/android/exoplayer2/metadata/id3/GeobFrame.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/metadata/id3/GeobFrame.java rename to library/core/src/main/java/com/google/android/exoplayer2/metadata/id3/GeobFrame.java diff --git a/library/src/main/java/com/google/android/exoplayer2/metadata/id3/Id3Decoder.java b/library/core/src/main/java/com/google/android/exoplayer2/metadata/id3/Id3Decoder.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/metadata/id3/Id3Decoder.java rename to library/core/src/main/java/com/google/android/exoplayer2/metadata/id3/Id3Decoder.java diff --git a/library/src/main/java/com/google/android/exoplayer2/metadata/id3/Id3Frame.java b/library/core/src/main/java/com/google/android/exoplayer2/metadata/id3/Id3Frame.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/metadata/id3/Id3Frame.java rename to library/core/src/main/java/com/google/android/exoplayer2/metadata/id3/Id3Frame.java diff --git a/library/src/main/java/com/google/android/exoplayer2/metadata/id3/PrivFrame.java b/library/core/src/main/java/com/google/android/exoplayer2/metadata/id3/PrivFrame.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/metadata/id3/PrivFrame.java rename to library/core/src/main/java/com/google/android/exoplayer2/metadata/id3/PrivFrame.java diff --git a/library/src/main/java/com/google/android/exoplayer2/metadata/id3/TextInformationFrame.java b/library/core/src/main/java/com/google/android/exoplayer2/metadata/id3/TextInformationFrame.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/metadata/id3/TextInformationFrame.java rename to library/core/src/main/java/com/google/android/exoplayer2/metadata/id3/TextInformationFrame.java diff --git a/library/src/main/java/com/google/android/exoplayer2/metadata/id3/UrlLinkFrame.java b/library/core/src/main/java/com/google/android/exoplayer2/metadata/id3/UrlLinkFrame.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/metadata/id3/UrlLinkFrame.java rename to library/core/src/main/java/com/google/android/exoplayer2/metadata/id3/UrlLinkFrame.java diff --git a/library/src/main/java/com/google/android/exoplayer2/metadata/scte35/PrivateCommand.java b/library/core/src/main/java/com/google/android/exoplayer2/metadata/scte35/PrivateCommand.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/metadata/scte35/PrivateCommand.java rename to library/core/src/main/java/com/google/android/exoplayer2/metadata/scte35/PrivateCommand.java diff --git a/library/src/main/java/com/google/android/exoplayer2/metadata/scte35/SpliceCommand.java b/library/core/src/main/java/com/google/android/exoplayer2/metadata/scte35/SpliceCommand.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/metadata/scte35/SpliceCommand.java rename to library/core/src/main/java/com/google/android/exoplayer2/metadata/scte35/SpliceCommand.java diff --git a/library/src/main/java/com/google/android/exoplayer2/metadata/scte35/SpliceInfoDecoder.java b/library/core/src/main/java/com/google/android/exoplayer2/metadata/scte35/SpliceInfoDecoder.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/metadata/scte35/SpliceInfoDecoder.java rename to library/core/src/main/java/com/google/android/exoplayer2/metadata/scte35/SpliceInfoDecoder.java diff --git a/library/src/main/java/com/google/android/exoplayer2/metadata/scte35/SpliceInsertCommand.java b/library/core/src/main/java/com/google/android/exoplayer2/metadata/scte35/SpliceInsertCommand.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/metadata/scte35/SpliceInsertCommand.java rename to library/core/src/main/java/com/google/android/exoplayer2/metadata/scte35/SpliceInsertCommand.java diff --git a/library/src/main/java/com/google/android/exoplayer2/metadata/scte35/SpliceNullCommand.java b/library/core/src/main/java/com/google/android/exoplayer2/metadata/scte35/SpliceNullCommand.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/metadata/scte35/SpliceNullCommand.java rename to library/core/src/main/java/com/google/android/exoplayer2/metadata/scte35/SpliceNullCommand.java diff --git a/library/src/main/java/com/google/android/exoplayer2/metadata/scte35/SpliceScheduleCommand.java b/library/core/src/main/java/com/google/android/exoplayer2/metadata/scte35/SpliceScheduleCommand.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/metadata/scte35/SpliceScheduleCommand.java rename to library/core/src/main/java/com/google/android/exoplayer2/metadata/scte35/SpliceScheduleCommand.java diff --git a/library/src/main/java/com/google/android/exoplayer2/metadata/scte35/TimeSignalCommand.java b/library/core/src/main/java/com/google/android/exoplayer2/metadata/scte35/TimeSignalCommand.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/metadata/scte35/TimeSignalCommand.java rename to library/core/src/main/java/com/google/android/exoplayer2/metadata/scte35/TimeSignalCommand.java diff --git a/library/src/main/java/com/google/android/exoplayer2/source/AdaptiveMediaSourceEventListener.java b/library/core/src/main/java/com/google/android/exoplayer2/source/AdaptiveMediaSourceEventListener.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/source/AdaptiveMediaSourceEventListener.java rename to library/core/src/main/java/com/google/android/exoplayer2/source/AdaptiveMediaSourceEventListener.java diff --git a/library/src/main/java/com/google/android/exoplayer2/source/BehindLiveWindowException.java b/library/core/src/main/java/com/google/android/exoplayer2/source/BehindLiveWindowException.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/source/BehindLiveWindowException.java rename to library/core/src/main/java/com/google/android/exoplayer2/source/BehindLiveWindowException.java diff --git a/library/src/main/java/com/google/android/exoplayer2/source/ClippingMediaPeriod.java b/library/core/src/main/java/com/google/android/exoplayer2/source/ClippingMediaPeriod.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/source/ClippingMediaPeriod.java rename to library/core/src/main/java/com/google/android/exoplayer2/source/ClippingMediaPeriod.java diff --git a/library/src/main/java/com/google/android/exoplayer2/source/ClippingMediaSource.java b/library/core/src/main/java/com/google/android/exoplayer2/source/ClippingMediaSource.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/source/ClippingMediaSource.java rename to library/core/src/main/java/com/google/android/exoplayer2/source/ClippingMediaSource.java diff --git a/library/src/main/java/com/google/android/exoplayer2/source/CompositeSequenceableLoader.java b/library/core/src/main/java/com/google/android/exoplayer2/source/CompositeSequenceableLoader.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/source/CompositeSequenceableLoader.java rename to library/core/src/main/java/com/google/android/exoplayer2/source/CompositeSequenceableLoader.java diff --git a/library/src/main/java/com/google/android/exoplayer2/source/ConcatenatingMediaSource.java b/library/core/src/main/java/com/google/android/exoplayer2/source/ConcatenatingMediaSource.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/source/ConcatenatingMediaSource.java rename to library/core/src/main/java/com/google/android/exoplayer2/source/ConcatenatingMediaSource.java diff --git a/library/src/main/java/com/google/android/exoplayer2/source/EmptySampleStream.java b/library/core/src/main/java/com/google/android/exoplayer2/source/EmptySampleStream.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/source/EmptySampleStream.java rename to library/core/src/main/java/com/google/android/exoplayer2/source/EmptySampleStream.java diff --git a/library/src/main/java/com/google/android/exoplayer2/source/ExtractorMediaPeriod.java b/library/core/src/main/java/com/google/android/exoplayer2/source/ExtractorMediaPeriod.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/source/ExtractorMediaPeriod.java rename to library/core/src/main/java/com/google/android/exoplayer2/source/ExtractorMediaPeriod.java diff --git a/library/src/main/java/com/google/android/exoplayer2/source/ExtractorMediaSource.java b/library/core/src/main/java/com/google/android/exoplayer2/source/ExtractorMediaSource.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/source/ExtractorMediaSource.java rename to library/core/src/main/java/com/google/android/exoplayer2/source/ExtractorMediaSource.java diff --git a/library/src/main/java/com/google/android/exoplayer2/source/LoopingMediaSource.java b/library/core/src/main/java/com/google/android/exoplayer2/source/LoopingMediaSource.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/source/LoopingMediaSource.java rename to library/core/src/main/java/com/google/android/exoplayer2/source/LoopingMediaSource.java diff --git a/library/src/main/java/com/google/android/exoplayer2/source/MediaPeriod.java b/library/core/src/main/java/com/google/android/exoplayer2/source/MediaPeriod.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/source/MediaPeriod.java rename to library/core/src/main/java/com/google/android/exoplayer2/source/MediaPeriod.java diff --git a/library/src/main/java/com/google/android/exoplayer2/source/MediaSource.java b/library/core/src/main/java/com/google/android/exoplayer2/source/MediaSource.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/source/MediaSource.java rename to library/core/src/main/java/com/google/android/exoplayer2/source/MediaSource.java diff --git a/library/src/main/java/com/google/android/exoplayer2/source/MergingMediaPeriod.java b/library/core/src/main/java/com/google/android/exoplayer2/source/MergingMediaPeriod.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/source/MergingMediaPeriod.java rename to library/core/src/main/java/com/google/android/exoplayer2/source/MergingMediaPeriod.java diff --git a/library/src/main/java/com/google/android/exoplayer2/source/MergingMediaSource.java b/library/core/src/main/java/com/google/android/exoplayer2/source/MergingMediaSource.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/source/MergingMediaSource.java rename to library/core/src/main/java/com/google/android/exoplayer2/source/MergingMediaSource.java diff --git a/library/src/main/java/com/google/android/exoplayer2/source/SampleStream.java b/library/core/src/main/java/com/google/android/exoplayer2/source/SampleStream.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/source/SampleStream.java rename to library/core/src/main/java/com/google/android/exoplayer2/source/SampleStream.java diff --git a/library/src/main/java/com/google/android/exoplayer2/source/SequenceableLoader.java b/library/core/src/main/java/com/google/android/exoplayer2/source/SequenceableLoader.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/source/SequenceableLoader.java rename to library/core/src/main/java/com/google/android/exoplayer2/source/SequenceableLoader.java diff --git a/library/src/main/java/com/google/android/exoplayer2/source/SinglePeriodTimeline.java b/library/core/src/main/java/com/google/android/exoplayer2/source/SinglePeriodTimeline.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/source/SinglePeriodTimeline.java rename to library/core/src/main/java/com/google/android/exoplayer2/source/SinglePeriodTimeline.java diff --git a/library/src/main/java/com/google/android/exoplayer2/source/SingleSampleMediaPeriod.java b/library/core/src/main/java/com/google/android/exoplayer2/source/SingleSampleMediaPeriod.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/source/SingleSampleMediaPeriod.java rename to library/core/src/main/java/com/google/android/exoplayer2/source/SingleSampleMediaPeriod.java diff --git a/library/src/main/java/com/google/android/exoplayer2/source/SingleSampleMediaSource.java b/library/core/src/main/java/com/google/android/exoplayer2/source/SingleSampleMediaSource.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/source/SingleSampleMediaSource.java rename to library/core/src/main/java/com/google/android/exoplayer2/source/SingleSampleMediaSource.java diff --git a/library/src/main/java/com/google/android/exoplayer2/source/TrackGroup.java b/library/core/src/main/java/com/google/android/exoplayer2/source/TrackGroup.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/source/TrackGroup.java rename to library/core/src/main/java/com/google/android/exoplayer2/source/TrackGroup.java diff --git a/library/src/main/java/com/google/android/exoplayer2/source/TrackGroupArray.java b/library/core/src/main/java/com/google/android/exoplayer2/source/TrackGroupArray.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/source/TrackGroupArray.java rename to library/core/src/main/java/com/google/android/exoplayer2/source/TrackGroupArray.java diff --git a/library/src/main/java/com/google/android/exoplayer2/source/UnrecognizedInputFormatException.java b/library/core/src/main/java/com/google/android/exoplayer2/source/UnrecognizedInputFormatException.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/source/UnrecognizedInputFormatException.java rename to library/core/src/main/java/com/google/android/exoplayer2/source/UnrecognizedInputFormatException.java diff --git a/library/src/main/java/com/google/android/exoplayer2/source/chunk/BaseMediaChunk.java b/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/BaseMediaChunk.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/source/chunk/BaseMediaChunk.java rename to library/core/src/main/java/com/google/android/exoplayer2/source/chunk/BaseMediaChunk.java diff --git a/library/src/main/java/com/google/android/exoplayer2/source/chunk/BaseMediaChunkOutput.java b/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/BaseMediaChunkOutput.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/source/chunk/BaseMediaChunkOutput.java rename to library/core/src/main/java/com/google/android/exoplayer2/source/chunk/BaseMediaChunkOutput.java diff --git a/library/src/main/java/com/google/android/exoplayer2/source/chunk/Chunk.java b/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/Chunk.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/source/chunk/Chunk.java rename to library/core/src/main/java/com/google/android/exoplayer2/source/chunk/Chunk.java diff --git a/library/src/main/java/com/google/android/exoplayer2/source/chunk/ChunkExtractorWrapper.java b/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/ChunkExtractorWrapper.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/source/chunk/ChunkExtractorWrapper.java rename to library/core/src/main/java/com/google/android/exoplayer2/source/chunk/ChunkExtractorWrapper.java diff --git a/library/src/main/java/com/google/android/exoplayer2/source/chunk/ChunkHolder.java b/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/ChunkHolder.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/source/chunk/ChunkHolder.java rename to library/core/src/main/java/com/google/android/exoplayer2/source/chunk/ChunkHolder.java diff --git a/library/src/main/java/com/google/android/exoplayer2/source/chunk/ChunkSampleStream.java b/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/ChunkSampleStream.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/source/chunk/ChunkSampleStream.java rename to library/core/src/main/java/com/google/android/exoplayer2/source/chunk/ChunkSampleStream.java diff --git a/library/src/main/java/com/google/android/exoplayer2/source/chunk/ChunkSource.java b/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/ChunkSource.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/source/chunk/ChunkSource.java rename to library/core/src/main/java/com/google/android/exoplayer2/source/chunk/ChunkSource.java diff --git a/library/src/main/java/com/google/android/exoplayer2/source/chunk/ChunkedTrackBlacklistUtil.java b/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/ChunkedTrackBlacklistUtil.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/source/chunk/ChunkedTrackBlacklistUtil.java rename to library/core/src/main/java/com/google/android/exoplayer2/source/chunk/ChunkedTrackBlacklistUtil.java diff --git a/library/src/main/java/com/google/android/exoplayer2/source/chunk/ContainerMediaChunk.java b/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/ContainerMediaChunk.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/source/chunk/ContainerMediaChunk.java rename to library/core/src/main/java/com/google/android/exoplayer2/source/chunk/ContainerMediaChunk.java diff --git a/library/src/main/java/com/google/android/exoplayer2/source/chunk/DataChunk.java b/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/DataChunk.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/source/chunk/DataChunk.java rename to library/core/src/main/java/com/google/android/exoplayer2/source/chunk/DataChunk.java diff --git a/library/src/main/java/com/google/android/exoplayer2/source/chunk/InitializationChunk.java b/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/InitializationChunk.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/source/chunk/InitializationChunk.java rename to library/core/src/main/java/com/google/android/exoplayer2/source/chunk/InitializationChunk.java diff --git a/library/src/main/java/com/google/android/exoplayer2/source/chunk/MediaChunk.java b/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/MediaChunk.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/source/chunk/MediaChunk.java rename to library/core/src/main/java/com/google/android/exoplayer2/source/chunk/MediaChunk.java diff --git a/library/src/main/java/com/google/android/exoplayer2/source/chunk/SingleSampleMediaChunk.java b/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/SingleSampleMediaChunk.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/source/chunk/SingleSampleMediaChunk.java rename to library/core/src/main/java/com/google/android/exoplayer2/source/chunk/SingleSampleMediaChunk.java diff --git a/library/src/main/java/com/google/android/exoplayer2/source/dash/DashChunkSource.java b/library/core/src/main/java/com/google/android/exoplayer2/source/dash/DashChunkSource.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/source/dash/DashChunkSource.java rename to library/core/src/main/java/com/google/android/exoplayer2/source/dash/DashChunkSource.java diff --git a/library/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaPeriod.java b/library/core/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaPeriod.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaPeriod.java rename to library/core/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaPeriod.java diff --git a/library/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaSource.java b/library/core/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaSource.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaSource.java rename to library/core/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaSource.java diff --git a/library/src/main/java/com/google/android/exoplayer2/source/dash/DashSegmentIndex.java b/library/core/src/main/java/com/google/android/exoplayer2/source/dash/DashSegmentIndex.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/source/dash/DashSegmentIndex.java rename to library/core/src/main/java/com/google/android/exoplayer2/source/dash/DashSegmentIndex.java diff --git a/library/src/main/java/com/google/android/exoplayer2/source/dash/DashUtil.java b/library/core/src/main/java/com/google/android/exoplayer2/source/dash/DashUtil.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/source/dash/DashUtil.java rename to library/core/src/main/java/com/google/android/exoplayer2/source/dash/DashUtil.java diff --git a/library/src/main/java/com/google/android/exoplayer2/source/dash/DashWrappingSegmentIndex.java b/library/core/src/main/java/com/google/android/exoplayer2/source/dash/DashWrappingSegmentIndex.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/source/dash/DashWrappingSegmentIndex.java rename to library/core/src/main/java/com/google/android/exoplayer2/source/dash/DashWrappingSegmentIndex.java diff --git a/library/src/main/java/com/google/android/exoplayer2/source/dash/DefaultDashChunkSource.java b/library/core/src/main/java/com/google/android/exoplayer2/source/dash/DefaultDashChunkSource.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/source/dash/DefaultDashChunkSource.java rename to library/core/src/main/java/com/google/android/exoplayer2/source/dash/DefaultDashChunkSource.java diff --git a/library/src/main/java/com/google/android/exoplayer2/source/dash/manifest/AdaptationSet.java b/library/core/src/main/java/com/google/android/exoplayer2/source/dash/manifest/AdaptationSet.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/source/dash/manifest/AdaptationSet.java rename to library/core/src/main/java/com/google/android/exoplayer2/source/dash/manifest/AdaptationSet.java diff --git a/library/src/main/java/com/google/android/exoplayer2/source/dash/manifest/DashManifest.java b/library/core/src/main/java/com/google/android/exoplayer2/source/dash/manifest/DashManifest.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/source/dash/manifest/DashManifest.java rename to library/core/src/main/java/com/google/android/exoplayer2/source/dash/manifest/DashManifest.java diff --git a/library/src/main/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestParser.java b/library/core/src/main/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestParser.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestParser.java rename to library/core/src/main/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestParser.java diff --git a/library/src/main/java/com/google/android/exoplayer2/source/dash/manifest/Period.java b/library/core/src/main/java/com/google/android/exoplayer2/source/dash/manifest/Period.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/source/dash/manifest/Period.java rename to library/core/src/main/java/com/google/android/exoplayer2/source/dash/manifest/Period.java diff --git a/library/src/main/java/com/google/android/exoplayer2/source/dash/manifest/RangedUri.java b/library/core/src/main/java/com/google/android/exoplayer2/source/dash/manifest/RangedUri.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/source/dash/manifest/RangedUri.java rename to library/core/src/main/java/com/google/android/exoplayer2/source/dash/manifest/RangedUri.java diff --git a/library/src/main/java/com/google/android/exoplayer2/source/dash/manifest/Representation.java b/library/core/src/main/java/com/google/android/exoplayer2/source/dash/manifest/Representation.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/source/dash/manifest/Representation.java rename to library/core/src/main/java/com/google/android/exoplayer2/source/dash/manifest/Representation.java diff --git a/library/src/main/java/com/google/android/exoplayer2/source/dash/manifest/RepresentationKey.java b/library/core/src/main/java/com/google/android/exoplayer2/source/dash/manifest/RepresentationKey.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/source/dash/manifest/RepresentationKey.java rename to library/core/src/main/java/com/google/android/exoplayer2/source/dash/manifest/RepresentationKey.java diff --git a/library/src/main/java/com/google/android/exoplayer2/source/dash/manifest/SchemeValuePair.java b/library/core/src/main/java/com/google/android/exoplayer2/source/dash/manifest/SchemeValuePair.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/source/dash/manifest/SchemeValuePair.java rename to library/core/src/main/java/com/google/android/exoplayer2/source/dash/manifest/SchemeValuePair.java diff --git a/library/src/main/java/com/google/android/exoplayer2/source/dash/manifest/SegmentBase.java b/library/core/src/main/java/com/google/android/exoplayer2/source/dash/manifest/SegmentBase.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/source/dash/manifest/SegmentBase.java rename to library/core/src/main/java/com/google/android/exoplayer2/source/dash/manifest/SegmentBase.java diff --git a/library/src/main/java/com/google/android/exoplayer2/source/dash/manifest/SingleSegmentIndex.java b/library/core/src/main/java/com/google/android/exoplayer2/source/dash/manifest/SingleSegmentIndex.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/source/dash/manifest/SingleSegmentIndex.java rename to library/core/src/main/java/com/google/android/exoplayer2/source/dash/manifest/SingleSegmentIndex.java diff --git a/library/src/main/java/com/google/android/exoplayer2/source/dash/manifest/UrlTemplate.java b/library/core/src/main/java/com/google/android/exoplayer2/source/dash/manifest/UrlTemplate.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/source/dash/manifest/UrlTemplate.java rename to library/core/src/main/java/com/google/android/exoplayer2/source/dash/manifest/UrlTemplate.java diff --git a/library/src/main/java/com/google/android/exoplayer2/source/dash/manifest/UtcTimingElement.java b/library/core/src/main/java/com/google/android/exoplayer2/source/dash/manifest/UtcTimingElement.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/source/dash/manifest/UtcTimingElement.java rename to library/core/src/main/java/com/google/android/exoplayer2/source/dash/manifest/UtcTimingElement.java diff --git a/library/src/main/java/com/google/android/exoplayer2/source/hls/Aes128DataSource.java b/library/core/src/main/java/com/google/android/exoplayer2/source/hls/Aes128DataSource.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/source/hls/Aes128DataSource.java rename to library/core/src/main/java/com/google/android/exoplayer2/source/hls/Aes128DataSource.java diff --git a/library/src/main/java/com/google/android/exoplayer2/source/hls/DefaultHlsDataSourceFactory.java b/library/core/src/main/java/com/google/android/exoplayer2/source/hls/DefaultHlsDataSourceFactory.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/source/hls/DefaultHlsDataSourceFactory.java rename to library/core/src/main/java/com/google/android/exoplayer2/source/hls/DefaultHlsDataSourceFactory.java diff --git a/library/src/main/java/com/google/android/exoplayer2/source/hls/HlsChunkSource.java b/library/core/src/main/java/com/google/android/exoplayer2/source/hls/HlsChunkSource.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/source/hls/HlsChunkSource.java rename to library/core/src/main/java/com/google/android/exoplayer2/source/hls/HlsChunkSource.java diff --git a/library/src/main/java/com/google/android/exoplayer2/source/hls/HlsDataSourceFactory.java b/library/core/src/main/java/com/google/android/exoplayer2/source/hls/HlsDataSourceFactory.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/source/hls/HlsDataSourceFactory.java rename to library/core/src/main/java/com/google/android/exoplayer2/source/hls/HlsDataSourceFactory.java diff --git a/library/src/main/java/com/google/android/exoplayer2/source/hls/HlsManifest.java b/library/core/src/main/java/com/google/android/exoplayer2/source/hls/HlsManifest.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/source/hls/HlsManifest.java rename to library/core/src/main/java/com/google/android/exoplayer2/source/hls/HlsManifest.java diff --git a/library/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaChunk.java b/library/core/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaChunk.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaChunk.java rename to library/core/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaChunk.java diff --git a/library/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaPeriod.java b/library/core/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaPeriod.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaPeriod.java rename to library/core/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaPeriod.java diff --git a/library/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaSource.java b/library/core/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaSource.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaSource.java rename to library/core/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaSource.java diff --git a/library/src/main/java/com/google/android/exoplayer2/source/hls/HlsSampleStream.java b/library/core/src/main/java/com/google/android/exoplayer2/source/hls/HlsSampleStream.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/source/hls/HlsSampleStream.java rename to library/core/src/main/java/com/google/android/exoplayer2/source/hls/HlsSampleStream.java diff --git a/library/src/main/java/com/google/android/exoplayer2/source/hls/HlsSampleStreamWrapper.java b/library/core/src/main/java/com/google/android/exoplayer2/source/hls/HlsSampleStreamWrapper.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/source/hls/HlsSampleStreamWrapper.java rename to library/core/src/main/java/com/google/android/exoplayer2/source/hls/HlsSampleStreamWrapper.java diff --git a/library/src/main/java/com/google/android/exoplayer2/source/hls/TimestampAdjusterProvider.java b/library/core/src/main/java/com/google/android/exoplayer2/source/hls/TimestampAdjusterProvider.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/source/hls/TimestampAdjusterProvider.java rename to library/core/src/main/java/com/google/android/exoplayer2/source/hls/TimestampAdjusterProvider.java diff --git a/library/src/main/java/com/google/android/exoplayer2/source/hls/WebvttExtractor.java b/library/core/src/main/java/com/google/android/exoplayer2/source/hls/WebvttExtractor.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/source/hls/WebvttExtractor.java rename to library/core/src/main/java/com/google/android/exoplayer2/source/hls/WebvttExtractor.java diff --git a/library/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsMasterPlaylist.java b/library/core/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsMasterPlaylist.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsMasterPlaylist.java rename to library/core/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsMasterPlaylist.java diff --git a/library/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsMediaPlaylist.java b/library/core/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsMediaPlaylist.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsMediaPlaylist.java rename to library/core/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsMediaPlaylist.java diff --git a/library/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylist.java b/library/core/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylist.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylist.java rename to library/core/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylist.java diff --git a/library/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylistParser.java b/library/core/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylistParser.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylistParser.java rename to library/core/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylistParser.java diff --git a/library/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylistTracker.java b/library/core/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylistTracker.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylistTracker.java rename to library/core/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylistTracker.java diff --git a/library/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/DefaultSsChunkSource.java b/library/core/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/DefaultSsChunkSource.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/DefaultSsChunkSource.java rename to library/core/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/DefaultSsChunkSource.java diff --git a/library/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsChunkSource.java b/library/core/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsChunkSource.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsChunkSource.java rename to library/core/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsChunkSource.java diff --git a/library/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsMediaPeriod.java b/library/core/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsMediaPeriod.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsMediaPeriod.java rename to library/core/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsMediaPeriod.java diff --git a/library/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsMediaSource.java b/library/core/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsMediaSource.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsMediaSource.java rename to library/core/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsMediaSource.java diff --git a/library/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/manifest/SsManifest.java b/library/core/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/manifest/SsManifest.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/manifest/SsManifest.java rename to library/core/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/manifest/SsManifest.java diff --git a/library/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/manifest/SsManifestParser.java b/library/core/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/manifest/SsManifestParser.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/manifest/SsManifestParser.java rename to library/core/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/manifest/SsManifestParser.java diff --git a/library/src/main/java/com/google/android/exoplayer2/text/CaptionStyleCompat.java b/library/core/src/main/java/com/google/android/exoplayer2/text/CaptionStyleCompat.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/text/CaptionStyleCompat.java rename to library/core/src/main/java/com/google/android/exoplayer2/text/CaptionStyleCompat.java diff --git a/library/src/main/java/com/google/android/exoplayer2/text/Cue.java b/library/core/src/main/java/com/google/android/exoplayer2/text/Cue.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/text/Cue.java rename to library/core/src/main/java/com/google/android/exoplayer2/text/Cue.java diff --git a/library/src/main/java/com/google/android/exoplayer2/text/SimpleSubtitleDecoder.java b/library/core/src/main/java/com/google/android/exoplayer2/text/SimpleSubtitleDecoder.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/text/SimpleSubtitleDecoder.java rename to library/core/src/main/java/com/google/android/exoplayer2/text/SimpleSubtitleDecoder.java diff --git a/library/src/main/java/com/google/android/exoplayer2/text/SimpleSubtitleOutputBuffer.java b/library/core/src/main/java/com/google/android/exoplayer2/text/SimpleSubtitleOutputBuffer.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/text/SimpleSubtitleOutputBuffer.java rename to library/core/src/main/java/com/google/android/exoplayer2/text/SimpleSubtitleOutputBuffer.java diff --git a/library/src/main/java/com/google/android/exoplayer2/text/Subtitle.java b/library/core/src/main/java/com/google/android/exoplayer2/text/Subtitle.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/text/Subtitle.java rename to library/core/src/main/java/com/google/android/exoplayer2/text/Subtitle.java diff --git a/library/src/main/java/com/google/android/exoplayer2/text/SubtitleDecoder.java b/library/core/src/main/java/com/google/android/exoplayer2/text/SubtitleDecoder.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/text/SubtitleDecoder.java rename to library/core/src/main/java/com/google/android/exoplayer2/text/SubtitleDecoder.java diff --git a/library/src/main/java/com/google/android/exoplayer2/text/SubtitleDecoderException.java b/library/core/src/main/java/com/google/android/exoplayer2/text/SubtitleDecoderException.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/text/SubtitleDecoderException.java rename to library/core/src/main/java/com/google/android/exoplayer2/text/SubtitleDecoderException.java diff --git a/library/src/main/java/com/google/android/exoplayer2/text/SubtitleDecoderFactory.java b/library/core/src/main/java/com/google/android/exoplayer2/text/SubtitleDecoderFactory.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/text/SubtitleDecoderFactory.java rename to library/core/src/main/java/com/google/android/exoplayer2/text/SubtitleDecoderFactory.java diff --git a/library/src/main/java/com/google/android/exoplayer2/text/SubtitleInputBuffer.java b/library/core/src/main/java/com/google/android/exoplayer2/text/SubtitleInputBuffer.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/text/SubtitleInputBuffer.java rename to library/core/src/main/java/com/google/android/exoplayer2/text/SubtitleInputBuffer.java diff --git a/library/src/main/java/com/google/android/exoplayer2/text/SubtitleOutputBuffer.java b/library/core/src/main/java/com/google/android/exoplayer2/text/SubtitleOutputBuffer.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/text/SubtitleOutputBuffer.java rename to library/core/src/main/java/com/google/android/exoplayer2/text/SubtitleOutputBuffer.java diff --git a/library/src/main/java/com/google/android/exoplayer2/text/TextRenderer.java b/library/core/src/main/java/com/google/android/exoplayer2/text/TextRenderer.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/text/TextRenderer.java rename to library/core/src/main/java/com/google/android/exoplayer2/text/TextRenderer.java diff --git a/library/src/main/java/com/google/android/exoplayer2/text/cea/Cea608Decoder.java b/library/core/src/main/java/com/google/android/exoplayer2/text/cea/Cea608Decoder.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/text/cea/Cea608Decoder.java rename to library/core/src/main/java/com/google/android/exoplayer2/text/cea/Cea608Decoder.java diff --git a/library/src/main/java/com/google/android/exoplayer2/text/cea/Cea708Cue.java b/library/core/src/main/java/com/google/android/exoplayer2/text/cea/Cea708Cue.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/text/cea/Cea708Cue.java rename to library/core/src/main/java/com/google/android/exoplayer2/text/cea/Cea708Cue.java diff --git a/library/src/main/java/com/google/android/exoplayer2/text/cea/Cea708Decoder.java b/library/core/src/main/java/com/google/android/exoplayer2/text/cea/Cea708Decoder.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/text/cea/Cea708Decoder.java rename to library/core/src/main/java/com/google/android/exoplayer2/text/cea/Cea708Decoder.java diff --git a/library/src/main/java/com/google/android/exoplayer2/text/cea/CeaDecoder.java b/library/core/src/main/java/com/google/android/exoplayer2/text/cea/CeaDecoder.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/text/cea/CeaDecoder.java rename to library/core/src/main/java/com/google/android/exoplayer2/text/cea/CeaDecoder.java diff --git a/library/src/main/java/com/google/android/exoplayer2/text/cea/CeaOutputBuffer.java b/library/core/src/main/java/com/google/android/exoplayer2/text/cea/CeaOutputBuffer.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/text/cea/CeaOutputBuffer.java rename to library/core/src/main/java/com/google/android/exoplayer2/text/cea/CeaOutputBuffer.java diff --git a/library/src/main/java/com/google/android/exoplayer2/text/cea/CeaSubtitle.java b/library/core/src/main/java/com/google/android/exoplayer2/text/cea/CeaSubtitle.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/text/cea/CeaSubtitle.java rename to library/core/src/main/java/com/google/android/exoplayer2/text/cea/CeaSubtitle.java diff --git a/library/src/main/java/com/google/android/exoplayer2/text/cea/CeaUtil.java b/library/core/src/main/java/com/google/android/exoplayer2/text/cea/CeaUtil.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/text/cea/CeaUtil.java rename to library/core/src/main/java/com/google/android/exoplayer2/text/cea/CeaUtil.java diff --git a/library/src/main/java/com/google/android/exoplayer2/text/subrip/SubripDecoder.java b/library/core/src/main/java/com/google/android/exoplayer2/text/subrip/SubripDecoder.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/text/subrip/SubripDecoder.java rename to library/core/src/main/java/com/google/android/exoplayer2/text/subrip/SubripDecoder.java diff --git a/library/src/main/java/com/google/android/exoplayer2/text/subrip/SubripSubtitle.java b/library/core/src/main/java/com/google/android/exoplayer2/text/subrip/SubripSubtitle.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/text/subrip/SubripSubtitle.java rename to library/core/src/main/java/com/google/android/exoplayer2/text/subrip/SubripSubtitle.java diff --git a/library/src/main/java/com/google/android/exoplayer2/text/ttml/TtmlDecoder.java b/library/core/src/main/java/com/google/android/exoplayer2/text/ttml/TtmlDecoder.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/text/ttml/TtmlDecoder.java rename to library/core/src/main/java/com/google/android/exoplayer2/text/ttml/TtmlDecoder.java diff --git a/library/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 similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/text/ttml/TtmlNode.java rename to library/core/src/main/java/com/google/android/exoplayer2/text/ttml/TtmlNode.java diff --git a/library/src/main/java/com/google/android/exoplayer2/text/ttml/TtmlRegion.java b/library/core/src/main/java/com/google/android/exoplayer2/text/ttml/TtmlRegion.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/text/ttml/TtmlRegion.java rename to library/core/src/main/java/com/google/android/exoplayer2/text/ttml/TtmlRegion.java diff --git a/library/src/main/java/com/google/android/exoplayer2/text/ttml/TtmlRenderUtil.java b/library/core/src/main/java/com/google/android/exoplayer2/text/ttml/TtmlRenderUtil.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/text/ttml/TtmlRenderUtil.java rename to library/core/src/main/java/com/google/android/exoplayer2/text/ttml/TtmlRenderUtil.java diff --git a/library/src/main/java/com/google/android/exoplayer2/text/ttml/TtmlStyle.java b/library/core/src/main/java/com/google/android/exoplayer2/text/ttml/TtmlStyle.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/text/ttml/TtmlStyle.java rename to library/core/src/main/java/com/google/android/exoplayer2/text/ttml/TtmlStyle.java diff --git a/library/src/main/java/com/google/android/exoplayer2/text/ttml/TtmlSubtitle.java b/library/core/src/main/java/com/google/android/exoplayer2/text/ttml/TtmlSubtitle.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/text/ttml/TtmlSubtitle.java rename to library/core/src/main/java/com/google/android/exoplayer2/text/ttml/TtmlSubtitle.java diff --git a/library/src/main/java/com/google/android/exoplayer2/text/tx3g/Tx3gDecoder.java b/library/core/src/main/java/com/google/android/exoplayer2/text/tx3g/Tx3gDecoder.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/text/tx3g/Tx3gDecoder.java rename to library/core/src/main/java/com/google/android/exoplayer2/text/tx3g/Tx3gDecoder.java diff --git a/library/src/main/java/com/google/android/exoplayer2/text/tx3g/Tx3gSubtitle.java b/library/core/src/main/java/com/google/android/exoplayer2/text/tx3g/Tx3gSubtitle.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/text/tx3g/Tx3gSubtitle.java rename to library/core/src/main/java/com/google/android/exoplayer2/text/tx3g/Tx3gSubtitle.java diff --git a/library/src/main/java/com/google/android/exoplayer2/text/webvtt/CssParser.java b/library/core/src/main/java/com/google/android/exoplayer2/text/webvtt/CssParser.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/text/webvtt/CssParser.java rename to library/core/src/main/java/com/google/android/exoplayer2/text/webvtt/CssParser.java diff --git a/library/src/main/java/com/google/android/exoplayer2/text/webvtt/Mp4WebvttDecoder.java b/library/core/src/main/java/com/google/android/exoplayer2/text/webvtt/Mp4WebvttDecoder.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/text/webvtt/Mp4WebvttDecoder.java rename to library/core/src/main/java/com/google/android/exoplayer2/text/webvtt/Mp4WebvttDecoder.java diff --git a/library/src/main/java/com/google/android/exoplayer2/text/webvtt/Mp4WebvttSubtitle.java b/library/core/src/main/java/com/google/android/exoplayer2/text/webvtt/Mp4WebvttSubtitle.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/text/webvtt/Mp4WebvttSubtitle.java rename to library/core/src/main/java/com/google/android/exoplayer2/text/webvtt/Mp4WebvttSubtitle.java diff --git a/library/src/main/java/com/google/android/exoplayer2/text/webvtt/WebvttCssStyle.java b/library/core/src/main/java/com/google/android/exoplayer2/text/webvtt/WebvttCssStyle.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/text/webvtt/WebvttCssStyle.java rename to library/core/src/main/java/com/google/android/exoplayer2/text/webvtt/WebvttCssStyle.java diff --git a/library/src/main/java/com/google/android/exoplayer2/text/webvtt/WebvttCue.java b/library/core/src/main/java/com/google/android/exoplayer2/text/webvtt/WebvttCue.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/text/webvtt/WebvttCue.java rename to library/core/src/main/java/com/google/android/exoplayer2/text/webvtt/WebvttCue.java diff --git a/library/src/main/java/com/google/android/exoplayer2/text/webvtt/WebvttCueParser.java b/library/core/src/main/java/com/google/android/exoplayer2/text/webvtt/WebvttCueParser.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/text/webvtt/WebvttCueParser.java rename to library/core/src/main/java/com/google/android/exoplayer2/text/webvtt/WebvttCueParser.java diff --git a/library/src/main/java/com/google/android/exoplayer2/text/webvtt/WebvttDecoder.java b/library/core/src/main/java/com/google/android/exoplayer2/text/webvtt/WebvttDecoder.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/text/webvtt/WebvttDecoder.java rename to library/core/src/main/java/com/google/android/exoplayer2/text/webvtt/WebvttDecoder.java diff --git a/library/src/main/java/com/google/android/exoplayer2/text/webvtt/WebvttParserUtil.java b/library/core/src/main/java/com/google/android/exoplayer2/text/webvtt/WebvttParserUtil.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/text/webvtt/WebvttParserUtil.java rename to library/core/src/main/java/com/google/android/exoplayer2/text/webvtt/WebvttParserUtil.java diff --git a/library/src/main/java/com/google/android/exoplayer2/text/webvtt/WebvttSubtitle.java b/library/core/src/main/java/com/google/android/exoplayer2/text/webvtt/WebvttSubtitle.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/text/webvtt/WebvttSubtitle.java rename to library/core/src/main/java/com/google/android/exoplayer2/text/webvtt/WebvttSubtitle.java diff --git a/library/src/main/java/com/google/android/exoplayer2/trackselection/AdaptiveTrackSelection.java b/library/core/src/main/java/com/google/android/exoplayer2/trackselection/AdaptiveTrackSelection.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/trackselection/AdaptiveTrackSelection.java rename to library/core/src/main/java/com/google/android/exoplayer2/trackselection/AdaptiveTrackSelection.java diff --git a/library/src/main/java/com/google/android/exoplayer2/trackselection/BaseTrackSelection.java b/library/core/src/main/java/com/google/android/exoplayer2/trackselection/BaseTrackSelection.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/trackselection/BaseTrackSelection.java rename to library/core/src/main/java/com/google/android/exoplayer2/trackselection/BaseTrackSelection.java diff --git a/library/src/main/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelector.java b/library/core/src/main/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelector.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelector.java rename to library/core/src/main/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelector.java diff --git a/library/src/main/java/com/google/android/exoplayer2/trackselection/FixedTrackSelection.java b/library/core/src/main/java/com/google/android/exoplayer2/trackselection/FixedTrackSelection.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/trackselection/FixedTrackSelection.java rename to library/core/src/main/java/com/google/android/exoplayer2/trackselection/FixedTrackSelection.java diff --git a/library/src/main/java/com/google/android/exoplayer2/trackselection/MappingTrackSelector.java b/library/core/src/main/java/com/google/android/exoplayer2/trackselection/MappingTrackSelector.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/trackselection/MappingTrackSelector.java rename to library/core/src/main/java/com/google/android/exoplayer2/trackselection/MappingTrackSelector.java diff --git a/library/src/main/java/com/google/android/exoplayer2/trackselection/RandomTrackSelection.java b/library/core/src/main/java/com/google/android/exoplayer2/trackselection/RandomTrackSelection.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/trackselection/RandomTrackSelection.java rename to library/core/src/main/java/com/google/android/exoplayer2/trackselection/RandomTrackSelection.java diff --git a/library/src/main/java/com/google/android/exoplayer2/trackselection/TrackSelection.java b/library/core/src/main/java/com/google/android/exoplayer2/trackselection/TrackSelection.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/trackselection/TrackSelection.java rename to library/core/src/main/java/com/google/android/exoplayer2/trackselection/TrackSelection.java diff --git a/library/src/main/java/com/google/android/exoplayer2/trackselection/TrackSelectionArray.java b/library/core/src/main/java/com/google/android/exoplayer2/trackselection/TrackSelectionArray.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/trackselection/TrackSelectionArray.java rename to library/core/src/main/java/com/google/android/exoplayer2/trackselection/TrackSelectionArray.java diff --git a/library/src/main/java/com/google/android/exoplayer2/trackselection/TrackSelector.java b/library/core/src/main/java/com/google/android/exoplayer2/trackselection/TrackSelector.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/trackselection/TrackSelector.java rename to library/core/src/main/java/com/google/android/exoplayer2/trackselection/TrackSelector.java diff --git a/library/src/main/java/com/google/android/exoplayer2/trackselection/TrackSelectorResult.java b/library/core/src/main/java/com/google/android/exoplayer2/trackselection/TrackSelectorResult.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/trackselection/TrackSelectorResult.java rename to library/core/src/main/java/com/google/android/exoplayer2/trackselection/TrackSelectorResult.java diff --git a/library/src/main/java/com/google/android/exoplayer2/ui/AspectRatioFrameLayout.java b/library/core/src/main/java/com/google/android/exoplayer2/ui/AspectRatioFrameLayout.java similarity index 99% rename from library/src/main/java/com/google/android/exoplayer2/ui/AspectRatioFrameLayout.java rename to library/core/src/main/java/com/google/android/exoplayer2/ui/AspectRatioFrameLayout.java index d3034a8bc8..3b17e4ddcf 100644 --- a/library/src/main/java/com/google/android/exoplayer2/ui/AspectRatioFrameLayout.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/ui/AspectRatioFrameLayout.java @@ -21,7 +21,7 @@ import android.support.annotation.IntDef; import android.util.AttributeSet; import android.widget.FrameLayout; -import com.google.android.exoplayer2.R; +import com.google.android.exoplayer2.core.R; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; diff --git a/library/src/main/java/com/google/android/exoplayer2/ui/DebugTextViewHelper.java b/library/core/src/main/java/com/google/android/exoplayer2/ui/DebugTextViewHelper.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/ui/DebugTextViewHelper.java rename to library/core/src/main/java/com/google/android/exoplayer2/ui/DebugTextViewHelper.java diff --git a/library/src/main/java/com/google/android/exoplayer2/ui/PlaybackControlView.java b/library/core/src/main/java/com/google/android/exoplayer2/ui/PlaybackControlView.java similarity index 99% rename from library/src/main/java/com/google/android/exoplayer2/ui/PlaybackControlView.java rename to library/core/src/main/java/com/google/android/exoplayer2/ui/PlaybackControlView.java index dc3c398357..4bde88d972 100644 --- a/library/src/main/java/com/google/android/exoplayer2/ui/PlaybackControlView.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/ui/PlaybackControlView.java @@ -29,8 +29,8 @@ import android.widget.TextView; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.ExoPlaybackException; import com.google.android.exoplayer2.ExoPlayer; -import com.google.android.exoplayer2.R; import com.google.android.exoplayer2.Timeline; +import com.google.android.exoplayer2.core.R; import com.google.android.exoplayer2.source.TrackGroupArray; import com.google.android.exoplayer2.trackselection.TrackSelectionArray; import com.google.android.exoplayer2.util.Util; diff --git a/library/src/main/java/com/google/android/exoplayer2/ui/SimpleExoPlayerView.java b/library/core/src/main/java/com/google/android/exoplayer2/ui/SimpleExoPlayerView.java similarity index 99% rename from library/src/main/java/com/google/android/exoplayer2/ui/SimpleExoPlayerView.java rename to library/core/src/main/java/com/google/android/exoplayer2/ui/SimpleExoPlayerView.java index 3349e05eda..2dfb165960 100644 --- a/library/src/main/java/com/google/android/exoplayer2/ui/SimpleExoPlayerView.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/ui/SimpleExoPlayerView.java @@ -33,9 +33,9 @@ import android.widget.ImageView; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.ExoPlaybackException; import com.google.android.exoplayer2.ExoPlayer; -import com.google.android.exoplayer2.R; import com.google.android.exoplayer2.SimpleExoPlayer; import com.google.android.exoplayer2.Timeline; +import com.google.android.exoplayer2.core.R; import com.google.android.exoplayer2.metadata.Metadata; import com.google.android.exoplayer2.metadata.id3.ApicFrame; import com.google.android.exoplayer2.source.TrackGroupArray; diff --git a/library/src/main/java/com/google/android/exoplayer2/ui/SubtitlePainter.java b/library/core/src/main/java/com/google/android/exoplayer2/ui/SubtitlePainter.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/ui/SubtitlePainter.java rename to library/core/src/main/java/com/google/android/exoplayer2/ui/SubtitlePainter.java diff --git a/library/src/main/java/com/google/android/exoplayer2/ui/SubtitleView.java b/library/core/src/main/java/com/google/android/exoplayer2/ui/SubtitleView.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/ui/SubtitleView.java rename to library/core/src/main/java/com/google/android/exoplayer2/ui/SubtitleView.java diff --git a/library/src/main/java/com/google/android/exoplayer2/upstream/Allocation.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/Allocation.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/upstream/Allocation.java rename to library/core/src/main/java/com/google/android/exoplayer2/upstream/Allocation.java diff --git a/library/src/main/java/com/google/android/exoplayer2/upstream/Allocator.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/Allocator.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/upstream/Allocator.java rename to library/core/src/main/java/com/google/android/exoplayer2/upstream/Allocator.java diff --git a/library/src/main/java/com/google/android/exoplayer2/upstream/AssetDataSource.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/AssetDataSource.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/upstream/AssetDataSource.java rename to library/core/src/main/java/com/google/android/exoplayer2/upstream/AssetDataSource.java diff --git a/library/src/main/java/com/google/android/exoplayer2/upstream/BandwidthMeter.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/BandwidthMeter.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/upstream/BandwidthMeter.java rename to library/core/src/main/java/com/google/android/exoplayer2/upstream/BandwidthMeter.java diff --git a/library/src/main/java/com/google/android/exoplayer2/upstream/ByteArrayDataSink.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/ByteArrayDataSink.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/upstream/ByteArrayDataSink.java rename to library/core/src/main/java/com/google/android/exoplayer2/upstream/ByteArrayDataSink.java diff --git a/library/src/main/java/com/google/android/exoplayer2/upstream/ByteArrayDataSource.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/ByteArrayDataSource.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/upstream/ByteArrayDataSource.java rename to library/core/src/main/java/com/google/android/exoplayer2/upstream/ByteArrayDataSource.java diff --git a/library/src/main/java/com/google/android/exoplayer2/upstream/ContentDataSource.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/ContentDataSource.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/upstream/ContentDataSource.java rename to library/core/src/main/java/com/google/android/exoplayer2/upstream/ContentDataSource.java diff --git a/library/src/main/java/com/google/android/exoplayer2/upstream/DataSink.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/DataSink.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/upstream/DataSink.java rename to library/core/src/main/java/com/google/android/exoplayer2/upstream/DataSink.java diff --git a/library/src/main/java/com/google/android/exoplayer2/upstream/DataSource.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/DataSource.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/upstream/DataSource.java rename to library/core/src/main/java/com/google/android/exoplayer2/upstream/DataSource.java diff --git a/library/src/main/java/com/google/android/exoplayer2/upstream/DataSourceException.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/DataSourceException.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/upstream/DataSourceException.java rename to library/core/src/main/java/com/google/android/exoplayer2/upstream/DataSourceException.java diff --git a/library/src/main/java/com/google/android/exoplayer2/upstream/DataSourceInputStream.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/DataSourceInputStream.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/upstream/DataSourceInputStream.java rename to library/core/src/main/java/com/google/android/exoplayer2/upstream/DataSourceInputStream.java diff --git a/library/src/main/java/com/google/android/exoplayer2/upstream/DataSpec.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/DataSpec.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/upstream/DataSpec.java rename to library/core/src/main/java/com/google/android/exoplayer2/upstream/DataSpec.java diff --git a/library/src/main/java/com/google/android/exoplayer2/upstream/DefaultAllocator.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/DefaultAllocator.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/upstream/DefaultAllocator.java rename to library/core/src/main/java/com/google/android/exoplayer2/upstream/DefaultAllocator.java diff --git a/library/src/main/java/com/google/android/exoplayer2/upstream/DefaultBandwidthMeter.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/DefaultBandwidthMeter.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/upstream/DefaultBandwidthMeter.java rename to library/core/src/main/java/com/google/android/exoplayer2/upstream/DefaultBandwidthMeter.java diff --git a/library/src/main/java/com/google/android/exoplayer2/upstream/DefaultDataSource.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/DefaultDataSource.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/upstream/DefaultDataSource.java rename to library/core/src/main/java/com/google/android/exoplayer2/upstream/DefaultDataSource.java diff --git a/library/src/main/java/com/google/android/exoplayer2/upstream/DefaultDataSourceFactory.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/DefaultDataSourceFactory.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/upstream/DefaultDataSourceFactory.java rename to library/core/src/main/java/com/google/android/exoplayer2/upstream/DefaultDataSourceFactory.java diff --git a/library/src/main/java/com/google/android/exoplayer2/upstream/DefaultHttpDataSource.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/DefaultHttpDataSource.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/upstream/DefaultHttpDataSource.java rename to library/core/src/main/java/com/google/android/exoplayer2/upstream/DefaultHttpDataSource.java diff --git a/library/src/main/java/com/google/android/exoplayer2/upstream/DefaultHttpDataSourceFactory.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/DefaultHttpDataSourceFactory.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/upstream/DefaultHttpDataSourceFactory.java rename to library/core/src/main/java/com/google/android/exoplayer2/upstream/DefaultHttpDataSourceFactory.java diff --git a/library/src/main/java/com/google/android/exoplayer2/upstream/FileDataSource.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/FileDataSource.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/upstream/FileDataSource.java rename to library/core/src/main/java/com/google/android/exoplayer2/upstream/FileDataSource.java diff --git a/library/src/main/java/com/google/android/exoplayer2/upstream/FileDataSourceFactory.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/FileDataSourceFactory.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/upstream/FileDataSourceFactory.java rename to library/core/src/main/java/com/google/android/exoplayer2/upstream/FileDataSourceFactory.java diff --git a/library/src/main/java/com/google/android/exoplayer2/upstream/HttpDataSource.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/HttpDataSource.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/upstream/HttpDataSource.java rename to library/core/src/main/java/com/google/android/exoplayer2/upstream/HttpDataSource.java diff --git a/library/src/main/java/com/google/android/exoplayer2/upstream/Loader.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/Loader.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/upstream/Loader.java rename to library/core/src/main/java/com/google/android/exoplayer2/upstream/Loader.java diff --git a/library/src/main/java/com/google/android/exoplayer2/upstream/LoaderErrorThrower.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/LoaderErrorThrower.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/upstream/LoaderErrorThrower.java rename to library/core/src/main/java/com/google/android/exoplayer2/upstream/LoaderErrorThrower.java diff --git a/library/src/main/java/com/google/android/exoplayer2/upstream/ParsingLoadable.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/ParsingLoadable.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/upstream/ParsingLoadable.java rename to library/core/src/main/java/com/google/android/exoplayer2/upstream/ParsingLoadable.java diff --git a/library/src/main/java/com/google/android/exoplayer2/upstream/PriorityDataSource.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/PriorityDataSource.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/upstream/PriorityDataSource.java rename to library/core/src/main/java/com/google/android/exoplayer2/upstream/PriorityDataSource.java diff --git a/library/src/main/java/com/google/android/exoplayer2/upstream/PriorityDataSourceFactory.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/PriorityDataSourceFactory.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/upstream/PriorityDataSourceFactory.java rename to library/core/src/main/java/com/google/android/exoplayer2/upstream/PriorityDataSourceFactory.java diff --git a/library/src/main/java/com/google/android/exoplayer2/upstream/RawResourceDataSource.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/RawResourceDataSource.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/upstream/RawResourceDataSource.java rename to library/core/src/main/java/com/google/android/exoplayer2/upstream/RawResourceDataSource.java diff --git a/library/src/main/java/com/google/android/exoplayer2/upstream/TeeDataSource.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/TeeDataSource.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/upstream/TeeDataSource.java rename to library/core/src/main/java/com/google/android/exoplayer2/upstream/TeeDataSource.java diff --git a/library/src/main/java/com/google/android/exoplayer2/upstream/TransferListener.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/TransferListener.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/upstream/TransferListener.java rename to library/core/src/main/java/com/google/android/exoplayer2/upstream/TransferListener.java diff --git a/library/src/main/java/com/google/android/exoplayer2/upstream/UdpDataSource.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/UdpDataSource.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/upstream/UdpDataSource.java rename to library/core/src/main/java/com/google/android/exoplayer2/upstream/UdpDataSource.java diff --git a/library/src/main/java/com/google/android/exoplayer2/upstream/cache/Cache.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/Cache.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/upstream/cache/Cache.java rename to library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/Cache.java diff --git a/library/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheDataSink.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheDataSink.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheDataSink.java rename to library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheDataSink.java diff --git a/library/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheDataSinkFactory.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheDataSinkFactory.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheDataSinkFactory.java rename to library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheDataSinkFactory.java diff --git a/library/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheDataSource.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheDataSource.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheDataSource.java rename to library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheDataSource.java diff --git a/library/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheDataSourceFactory.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheDataSourceFactory.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheDataSourceFactory.java rename to library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheDataSourceFactory.java diff --git a/library/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheEvictor.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheEvictor.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheEvictor.java rename to library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheEvictor.java diff --git a/library/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheSpan.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheSpan.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheSpan.java rename to library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheSpan.java diff --git a/library/src/main/java/com/google/android/exoplayer2/upstream/cache/CachedContent.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CachedContent.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/upstream/cache/CachedContent.java rename to library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CachedContent.java diff --git a/library/src/main/java/com/google/android/exoplayer2/upstream/cache/CachedContentIndex.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CachedContentIndex.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/upstream/cache/CachedContentIndex.java rename to library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CachedContentIndex.java diff --git a/library/src/main/java/com/google/android/exoplayer2/upstream/cache/CachedRegionTracker.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CachedRegionTracker.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/upstream/cache/CachedRegionTracker.java rename to library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CachedRegionTracker.java diff --git a/library/src/main/java/com/google/android/exoplayer2/upstream/cache/LeastRecentlyUsedCacheEvictor.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/LeastRecentlyUsedCacheEvictor.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/upstream/cache/LeastRecentlyUsedCacheEvictor.java rename to library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/LeastRecentlyUsedCacheEvictor.java diff --git a/library/src/main/java/com/google/android/exoplayer2/upstream/cache/NoOpCacheEvictor.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/NoOpCacheEvictor.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/upstream/cache/NoOpCacheEvictor.java rename to library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/NoOpCacheEvictor.java diff --git a/library/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 similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/upstream/cache/SimpleCache.java rename to library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/SimpleCache.java diff --git a/library/src/main/java/com/google/android/exoplayer2/upstream/cache/SimpleCacheSpan.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/SimpleCacheSpan.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/upstream/cache/SimpleCacheSpan.java rename to library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/SimpleCacheSpan.java diff --git a/library/src/main/java/com/google/android/exoplayer2/upstream/crypto/AesCipherDataSink.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/crypto/AesCipherDataSink.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/upstream/crypto/AesCipherDataSink.java rename to library/core/src/main/java/com/google/android/exoplayer2/upstream/crypto/AesCipherDataSink.java diff --git a/library/src/main/java/com/google/android/exoplayer2/upstream/crypto/AesCipherDataSource.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/crypto/AesCipherDataSource.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/upstream/crypto/AesCipherDataSource.java rename to library/core/src/main/java/com/google/android/exoplayer2/upstream/crypto/AesCipherDataSource.java diff --git a/library/src/main/java/com/google/android/exoplayer2/upstream/crypto/AesFlushingCipher.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/crypto/AesFlushingCipher.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/upstream/crypto/AesFlushingCipher.java rename to library/core/src/main/java/com/google/android/exoplayer2/upstream/crypto/AesFlushingCipher.java diff --git a/library/src/main/java/com/google/android/exoplayer2/upstream/crypto/CryptoUtil.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/crypto/CryptoUtil.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/upstream/crypto/CryptoUtil.java rename to library/core/src/main/java/com/google/android/exoplayer2/upstream/crypto/CryptoUtil.java diff --git a/library/src/main/java/com/google/android/exoplayer2/util/Assertions.java b/library/core/src/main/java/com/google/android/exoplayer2/util/Assertions.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/util/Assertions.java rename to library/core/src/main/java/com/google/android/exoplayer2/util/Assertions.java diff --git a/library/src/main/java/com/google/android/exoplayer2/util/AtomicFile.java b/library/core/src/main/java/com/google/android/exoplayer2/util/AtomicFile.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/util/AtomicFile.java rename to library/core/src/main/java/com/google/android/exoplayer2/util/AtomicFile.java diff --git a/library/src/main/java/com/google/android/exoplayer2/util/Clock.java b/library/core/src/main/java/com/google/android/exoplayer2/util/Clock.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/util/Clock.java rename to library/core/src/main/java/com/google/android/exoplayer2/util/Clock.java diff --git a/library/core/src/main/java/com/google/android/exoplayer2/util/ClosedSource.java b/library/core/src/main/java/com/google/android/exoplayer2/util/ClosedSource.java new file mode 100644 index 0000000000..ea70920d20 --- /dev/null +++ b/library/core/src/main/java/com/google/android/exoplayer2/util/ClosedSource.java @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.exoplayer2.util; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * An annotation for classes and interfaces that should not be open sourced. + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.SOURCE) +@ClosedSource(reason = "Not required") +public @interface ClosedSource { + String reason(); +} diff --git a/library/src/main/java/com/google/android/exoplayer2/util/CodecSpecificDataUtil.java b/library/core/src/main/java/com/google/android/exoplayer2/util/CodecSpecificDataUtil.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/util/CodecSpecificDataUtil.java rename to library/core/src/main/java/com/google/android/exoplayer2/util/CodecSpecificDataUtil.java diff --git a/library/src/main/java/com/google/android/exoplayer2/util/ColorParser.java b/library/core/src/main/java/com/google/android/exoplayer2/util/ColorParser.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/util/ColorParser.java rename to library/core/src/main/java/com/google/android/exoplayer2/util/ColorParser.java diff --git a/library/src/main/java/com/google/android/exoplayer2/util/ConditionVariable.java b/library/core/src/main/java/com/google/android/exoplayer2/util/ConditionVariable.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/util/ConditionVariable.java rename to library/core/src/main/java/com/google/android/exoplayer2/util/ConditionVariable.java diff --git a/library/src/main/java/com/google/android/exoplayer2/util/FlacStreamInfo.java b/library/core/src/main/java/com/google/android/exoplayer2/util/FlacStreamInfo.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/util/FlacStreamInfo.java rename to library/core/src/main/java/com/google/android/exoplayer2/util/FlacStreamInfo.java diff --git a/library/src/main/java/com/google/android/exoplayer2/util/LibraryLoader.java b/library/core/src/main/java/com/google/android/exoplayer2/util/LibraryLoader.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/util/LibraryLoader.java rename to library/core/src/main/java/com/google/android/exoplayer2/util/LibraryLoader.java diff --git a/library/src/main/java/com/google/android/exoplayer2/util/LongArray.java b/library/core/src/main/java/com/google/android/exoplayer2/util/LongArray.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/util/LongArray.java rename to library/core/src/main/java/com/google/android/exoplayer2/util/LongArray.java diff --git a/library/src/main/java/com/google/android/exoplayer2/util/MediaClock.java b/library/core/src/main/java/com/google/android/exoplayer2/util/MediaClock.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/util/MediaClock.java rename to library/core/src/main/java/com/google/android/exoplayer2/util/MediaClock.java diff --git a/library/src/main/java/com/google/android/exoplayer2/util/MimeTypes.java b/library/core/src/main/java/com/google/android/exoplayer2/util/MimeTypes.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/util/MimeTypes.java rename to library/core/src/main/java/com/google/android/exoplayer2/util/MimeTypes.java diff --git a/library/src/main/java/com/google/android/exoplayer2/util/NalUnitUtil.java b/library/core/src/main/java/com/google/android/exoplayer2/util/NalUnitUtil.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/util/NalUnitUtil.java rename to library/core/src/main/java/com/google/android/exoplayer2/util/NalUnitUtil.java diff --git a/library/src/main/java/com/google/android/exoplayer2/util/ParsableBitArray.java b/library/core/src/main/java/com/google/android/exoplayer2/util/ParsableBitArray.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/util/ParsableBitArray.java rename to library/core/src/main/java/com/google/android/exoplayer2/util/ParsableBitArray.java diff --git a/library/src/main/java/com/google/android/exoplayer2/util/ParsableByteArray.java b/library/core/src/main/java/com/google/android/exoplayer2/util/ParsableByteArray.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/util/ParsableByteArray.java rename to library/core/src/main/java/com/google/android/exoplayer2/util/ParsableByteArray.java diff --git a/library/src/main/java/com/google/android/exoplayer2/util/ParsableNalUnitBitArray.java b/library/core/src/main/java/com/google/android/exoplayer2/util/ParsableNalUnitBitArray.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/util/ParsableNalUnitBitArray.java rename to library/core/src/main/java/com/google/android/exoplayer2/util/ParsableNalUnitBitArray.java diff --git a/library/src/main/java/com/google/android/exoplayer2/util/Predicate.java b/library/core/src/main/java/com/google/android/exoplayer2/util/Predicate.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/util/Predicate.java rename to library/core/src/main/java/com/google/android/exoplayer2/util/Predicate.java diff --git a/library/src/main/java/com/google/android/exoplayer2/util/PriorityHandlerThread.java b/library/core/src/main/java/com/google/android/exoplayer2/util/PriorityHandlerThread.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/util/PriorityHandlerThread.java rename to library/core/src/main/java/com/google/android/exoplayer2/util/PriorityHandlerThread.java diff --git a/library/src/main/java/com/google/android/exoplayer2/util/PriorityTaskManager.java b/library/core/src/main/java/com/google/android/exoplayer2/util/PriorityTaskManager.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/util/PriorityTaskManager.java rename to library/core/src/main/java/com/google/android/exoplayer2/util/PriorityTaskManager.java diff --git a/library/src/main/java/com/google/android/exoplayer2/util/ReusableBufferedOutputStream.java b/library/core/src/main/java/com/google/android/exoplayer2/util/ReusableBufferedOutputStream.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/util/ReusableBufferedOutputStream.java rename to library/core/src/main/java/com/google/android/exoplayer2/util/ReusableBufferedOutputStream.java diff --git a/library/src/main/java/com/google/android/exoplayer2/util/SlidingPercentile.java b/library/core/src/main/java/com/google/android/exoplayer2/util/SlidingPercentile.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/util/SlidingPercentile.java rename to library/core/src/main/java/com/google/android/exoplayer2/util/SlidingPercentile.java diff --git a/library/src/main/java/com/google/android/exoplayer2/util/StandaloneMediaClock.java b/library/core/src/main/java/com/google/android/exoplayer2/util/StandaloneMediaClock.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/util/StandaloneMediaClock.java rename to library/core/src/main/java/com/google/android/exoplayer2/util/StandaloneMediaClock.java diff --git a/library/src/main/java/com/google/android/exoplayer2/util/SystemClock.java b/library/core/src/main/java/com/google/android/exoplayer2/util/SystemClock.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/util/SystemClock.java rename to library/core/src/main/java/com/google/android/exoplayer2/util/SystemClock.java diff --git a/library/src/main/java/com/google/android/exoplayer2/util/TimestampAdjuster.java b/library/core/src/main/java/com/google/android/exoplayer2/util/TimestampAdjuster.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/util/TimestampAdjuster.java rename to library/core/src/main/java/com/google/android/exoplayer2/util/TimestampAdjuster.java diff --git a/library/src/main/java/com/google/android/exoplayer2/util/TraceUtil.java b/library/core/src/main/java/com/google/android/exoplayer2/util/TraceUtil.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/util/TraceUtil.java rename to library/core/src/main/java/com/google/android/exoplayer2/util/TraceUtil.java diff --git a/library/src/main/java/com/google/android/exoplayer2/util/UriUtil.java b/library/core/src/main/java/com/google/android/exoplayer2/util/UriUtil.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/util/UriUtil.java rename to library/core/src/main/java/com/google/android/exoplayer2/util/UriUtil.java diff --git a/library/src/main/java/com/google/android/exoplayer2/util/Util.java b/library/core/src/main/java/com/google/android/exoplayer2/util/Util.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/util/Util.java rename to library/core/src/main/java/com/google/android/exoplayer2/util/Util.java diff --git a/library/src/main/java/com/google/android/exoplayer2/util/XmlPullParserUtil.java b/library/core/src/main/java/com/google/android/exoplayer2/util/XmlPullParserUtil.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/util/XmlPullParserUtil.java rename to library/core/src/main/java/com/google/android/exoplayer2/util/XmlPullParserUtil.java diff --git a/library/src/main/java/com/google/android/exoplayer2/video/AvcConfig.java b/library/core/src/main/java/com/google/android/exoplayer2/video/AvcConfig.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/video/AvcConfig.java rename to library/core/src/main/java/com/google/android/exoplayer2/video/AvcConfig.java diff --git a/library/src/main/java/com/google/android/exoplayer2/video/HevcConfig.java b/library/core/src/main/java/com/google/android/exoplayer2/video/HevcConfig.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/video/HevcConfig.java rename to library/core/src/main/java/com/google/android/exoplayer2/video/HevcConfig.java diff --git a/library/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java b/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java rename to library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java diff --git a/library/src/main/java/com/google/android/exoplayer2/video/VideoFrameReleaseTimeHelper.java b/library/core/src/main/java/com/google/android/exoplayer2/video/VideoFrameReleaseTimeHelper.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/video/VideoFrameReleaseTimeHelper.java rename to library/core/src/main/java/com/google/android/exoplayer2/video/VideoFrameReleaseTimeHelper.java diff --git a/library/src/main/java/com/google/android/exoplayer2/video/VideoRendererEventListener.java b/library/core/src/main/java/com/google/android/exoplayer2/video/VideoRendererEventListener.java similarity index 100% rename from library/src/main/java/com/google/android/exoplayer2/video/VideoRendererEventListener.java rename to library/core/src/main/java/com/google/android/exoplayer2/video/VideoRendererEventListener.java diff --git a/library/src/main/javadoc/com/google/android/exoplayer2/doc-files/exoplayer-threading-model.svg b/library/core/src/main/javadoc/com/google/android/exoplayer2/doc-files/exoplayer-threading-model.svg similarity index 100% rename from library/src/main/javadoc/com/google/android/exoplayer2/doc-files/exoplayer-threading-model.svg rename to library/core/src/main/javadoc/com/google/android/exoplayer2/doc-files/exoplayer-threading-model.svg diff --git a/library/src/main/javadoc/com/google/android/exoplayer2/doc-files/renderer-states.svg b/library/core/src/main/javadoc/com/google/android/exoplayer2/doc-files/renderer-states.svg similarity index 100% rename from library/src/main/javadoc/com/google/android/exoplayer2/doc-files/renderer-states.svg rename to library/core/src/main/javadoc/com/google/android/exoplayer2/doc-files/renderer-states.svg diff --git a/library/src/main/javadoc/com/google/android/exoplayer2/doc-files/timeline-advanced.svg b/library/core/src/main/javadoc/com/google/android/exoplayer2/doc-files/timeline-advanced.svg similarity index 100% rename from library/src/main/javadoc/com/google/android/exoplayer2/doc-files/timeline-advanced.svg rename to library/core/src/main/javadoc/com/google/android/exoplayer2/doc-files/timeline-advanced.svg diff --git a/library/src/main/javadoc/com/google/android/exoplayer2/doc-files/timeline-live-indefinite.svg b/library/core/src/main/javadoc/com/google/android/exoplayer2/doc-files/timeline-live-indefinite.svg similarity index 100% rename from library/src/main/javadoc/com/google/android/exoplayer2/doc-files/timeline-live-indefinite.svg rename to library/core/src/main/javadoc/com/google/android/exoplayer2/doc-files/timeline-live-indefinite.svg diff --git a/library/src/main/javadoc/com/google/android/exoplayer2/doc-files/timeline-live-limited.svg b/library/core/src/main/javadoc/com/google/android/exoplayer2/doc-files/timeline-live-limited.svg similarity index 100% rename from library/src/main/javadoc/com/google/android/exoplayer2/doc-files/timeline-live-limited.svg rename to library/core/src/main/javadoc/com/google/android/exoplayer2/doc-files/timeline-live-limited.svg diff --git a/library/src/main/javadoc/com/google/android/exoplayer2/doc-files/timeline-live-multi-period.svg b/library/core/src/main/javadoc/com/google/android/exoplayer2/doc-files/timeline-live-multi-period.svg similarity index 100% rename from library/src/main/javadoc/com/google/android/exoplayer2/doc-files/timeline-live-multi-period.svg rename to library/core/src/main/javadoc/com/google/android/exoplayer2/doc-files/timeline-live-multi-period.svg diff --git a/library/src/main/javadoc/com/google/android/exoplayer2/doc-files/timeline-period.svg b/library/core/src/main/javadoc/com/google/android/exoplayer2/doc-files/timeline-period.svg similarity index 100% rename from library/src/main/javadoc/com/google/android/exoplayer2/doc-files/timeline-period.svg rename to library/core/src/main/javadoc/com/google/android/exoplayer2/doc-files/timeline-period.svg diff --git a/library/src/main/javadoc/com/google/android/exoplayer2/doc-files/timeline-playlist.svg b/library/core/src/main/javadoc/com/google/android/exoplayer2/doc-files/timeline-playlist.svg similarity index 100% rename from library/src/main/javadoc/com/google/android/exoplayer2/doc-files/timeline-playlist.svg rename to library/core/src/main/javadoc/com/google/android/exoplayer2/doc-files/timeline-playlist.svg diff --git a/library/src/main/javadoc/com/google/android/exoplayer2/doc-files/timeline-single-file.svg b/library/core/src/main/javadoc/com/google/android/exoplayer2/doc-files/timeline-single-file.svg similarity index 100% rename from library/src/main/javadoc/com/google/android/exoplayer2/doc-files/timeline-single-file.svg rename to library/core/src/main/javadoc/com/google/android/exoplayer2/doc-files/timeline-single-file.svg diff --git a/library/src/main/javadoc/com/google/android/exoplayer2/doc-files/timeline-window.svg b/library/core/src/main/javadoc/com/google/android/exoplayer2/doc-files/timeline-window.svg similarity index 100% rename from library/src/main/javadoc/com/google/android/exoplayer2/doc-files/timeline-window.svg rename to library/core/src/main/javadoc/com/google/android/exoplayer2/doc-files/timeline-window.svg diff --git a/library/core/src/main/proguard-rules.txt b/library/core/src/main/proguard-rules.txt new file mode 100644 index 0000000000..75f2d095be --- /dev/null +++ b/library/core/src/main/proguard-rules.txt @@ -0,0 +1,7 @@ +# Accessed via reflection in SubtitleDecoderFactory.DEFAULT +-keepclassmembers class com.google.android.exoplayer2.text.cea.Cea608Decoder { + public (java.lang.String, int); +} +-keepclassmembers class com.google.android.exoplayer2.text.cea.Cea708Decoder { + public (int); +} diff --git a/library/src/main/res/drawable-anydpi-v21/exo_controls_fastforward.xml b/library/core/src/main/res/drawable-anydpi-v21/exo_controls_fastforward.xml similarity index 100% rename from library/src/main/res/drawable-anydpi-v21/exo_controls_fastforward.xml rename to library/core/src/main/res/drawable-anydpi-v21/exo_controls_fastforward.xml diff --git a/library/src/main/res/drawable-anydpi-v21/exo_controls_next.xml b/library/core/src/main/res/drawable-anydpi-v21/exo_controls_next.xml similarity index 100% rename from library/src/main/res/drawable-anydpi-v21/exo_controls_next.xml rename to library/core/src/main/res/drawable-anydpi-v21/exo_controls_next.xml diff --git a/library/src/main/res/drawable-anydpi-v21/exo_controls_pause.xml b/library/core/src/main/res/drawable-anydpi-v21/exo_controls_pause.xml similarity index 100% rename from library/src/main/res/drawable-anydpi-v21/exo_controls_pause.xml rename to library/core/src/main/res/drawable-anydpi-v21/exo_controls_pause.xml diff --git a/library/src/main/res/drawable-anydpi-v21/exo_controls_play.xml b/library/core/src/main/res/drawable-anydpi-v21/exo_controls_play.xml similarity index 100% rename from library/src/main/res/drawable-anydpi-v21/exo_controls_play.xml rename to library/core/src/main/res/drawable-anydpi-v21/exo_controls_play.xml diff --git a/library/src/main/res/drawable-anydpi-v21/exo_controls_previous.xml b/library/core/src/main/res/drawable-anydpi-v21/exo_controls_previous.xml similarity index 100% rename from library/src/main/res/drawable-anydpi-v21/exo_controls_previous.xml rename to library/core/src/main/res/drawable-anydpi-v21/exo_controls_previous.xml diff --git a/library/src/main/res/drawable-anydpi-v21/exo_controls_rewind.xml b/library/core/src/main/res/drawable-anydpi-v21/exo_controls_rewind.xml similarity index 100% rename from library/src/main/res/drawable-anydpi-v21/exo_controls_rewind.xml rename to library/core/src/main/res/drawable-anydpi-v21/exo_controls_rewind.xml diff --git a/library/src/main/res/drawable-hdpi/exo_controls_fastforward.png b/library/core/src/main/res/drawable-hdpi/exo_controls_fastforward.png similarity index 100% rename from library/src/main/res/drawable-hdpi/exo_controls_fastforward.png rename to library/core/src/main/res/drawable-hdpi/exo_controls_fastforward.png diff --git a/library/src/main/res/drawable-hdpi/exo_controls_next.png b/library/core/src/main/res/drawable-hdpi/exo_controls_next.png similarity index 100% rename from library/src/main/res/drawable-hdpi/exo_controls_next.png rename to library/core/src/main/res/drawable-hdpi/exo_controls_next.png diff --git a/library/src/main/res/drawable-hdpi/exo_controls_pause.png b/library/core/src/main/res/drawable-hdpi/exo_controls_pause.png similarity index 100% rename from library/src/main/res/drawable-hdpi/exo_controls_pause.png rename to library/core/src/main/res/drawable-hdpi/exo_controls_pause.png diff --git a/library/src/main/res/drawable-hdpi/exo_controls_play.png b/library/core/src/main/res/drawable-hdpi/exo_controls_play.png similarity index 100% rename from library/src/main/res/drawable-hdpi/exo_controls_play.png rename to library/core/src/main/res/drawable-hdpi/exo_controls_play.png diff --git a/library/src/main/res/drawable-hdpi/exo_controls_previous.png b/library/core/src/main/res/drawable-hdpi/exo_controls_previous.png similarity index 100% rename from library/src/main/res/drawable-hdpi/exo_controls_previous.png rename to library/core/src/main/res/drawable-hdpi/exo_controls_previous.png diff --git a/library/src/main/res/drawable-hdpi/exo_controls_rewind.png b/library/core/src/main/res/drawable-hdpi/exo_controls_rewind.png similarity index 100% rename from library/src/main/res/drawable-hdpi/exo_controls_rewind.png rename to library/core/src/main/res/drawable-hdpi/exo_controls_rewind.png diff --git a/library/src/main/res/drawable-ldpi/exo_controls_fastforward.png b/library/core/src/main/res/drawable-ldpi/exo_controls_fastforward.png similarity index 100% rename from library/src/main/res/drawable-ldpi/exo_controls_fastforward.png rename to library/core/src/main/res/drawable-ldpi/exo_controls_fastforward.png diff --git a/library/src/main/res/drawable-ldpi/exo_controls_next.png b/library/core/src/main/res/drawable-ldpi/exo_controls_next.png similarity index 100% rename from library/src/main/res/drawable-ldpi/exo_controls_next.png rename to library/core/src/main/res/drawable-ldpi/exo_controls_next.png diff --git a/library/src/main/res/drawable-ldpi/exo_controls_pause.png b/library/core/src/main/res/drawable-ldpi/exo_controls_pause.png similarity index 100% rename from library/src/main/res/drawable-ldpi/exo_controls_pause.png rename to library/core/src/main/res/drawable-ldpi/exo_controls_pause.png diff --git a/library/src/main/res/drawable-ldpi/exo_controls_play.png b/library/core/src/main/res/drawable-ldpi/exo_controls_play.png similarity index 100% rename from library/src/main/res/drawable-ldpi/exo_controls_play.png rename to library/core/src/main/res/drawable-ldpi/exo_controls_play.png diff --git a/library/src/main/res/drawable-ldpi/exo_controls_previous.png b/library/core/src/main/res/drawable-ldpi/exo_controls_previous.png similarity index 100% rename from library/src/main/res/drawable-ldpi/exo_controls_previous.png rename to library/core/src/main/res/drawable-ldpi/exo_controls_previous.png diff --git a/library/src/main/res/drawable-ldpi/exo_controls_rewind.png b/library/core/src/main/res/drawable-ldpi/exo_controls_rewind.png similarity index 100% rename from library/src/main/res/drawable-ldpi/exo_controls_rewind.png rename to library/core/src/main/res/drawable-ldpi/exo_controls_rewind.png diff --git a/library/src/main/res/drawable-mdpi/exo_controls_fastforward.png b/library/core/src/main/res/drawable-mdpi/exo_controls_fastforward.png similarity index 100% rename from library/src/main/res/drawable-mdpi/exo_controls_fastforward.png rename to library/core/src/main/res/drawable-mdpi/exo_controls_fastforward.png diff --git a/library/src/main/res/drawable-mdpi/exo_controls_next.png b/library/core/src/main/res/drawable-mdpi/exo_controls_next.png similarity index 100% rename from library/src/main/res/drawable-mdpi/exo_controls_next.png rename to library/core/src/main/res/drawable-mdpi/exo_controls_next.png diff --git a/library/src/main/res/drawable-mdpi/exo_controls_pause.png b/library/core/src/main/res/drawable-mdpi/exo_controls_pause.png similarity index 100% rename from library/src/main/res/drawable-mdpi/exo_controls_pause.png rename to library/core/src/main/res/drawable-mdpi/exo_controls_pause.png diff --git a/library/src/main/res/drawable-mdpi/exo_controls_play.png b/library/core/src/main/res/drawable-mdpi/exo_controls_play.png similarity index 100% rename from library/src/main/res/drawable-mdpi/exo_controls_play.png rename to library/core/src/main/res/drawable-mdpi/exo_controls_play.png diff --git a/library/src/main/res/drawable-mdpi/exo_controls_previous.png b/library/core/src/main/res/drawable-mdpi/exo_controls_previous.png similarity index 100% rename from library/src/main/res/drawable-mdpi/exo_controls_previous.png rename to library/core/src/main/res/drawable-mdpi/exo_controls_previous.png diff --git a/library/src/main/res/drawable-mdpi/exo_controls_rewind.png b/library/core/src/main/res/drawable-mdpi/exo_controls_rewind.png similarity index 100% rename from library/src/main/res/drawable-mdpi/exo_controls_rewind.png rename to library/core/src/main/res/drawable-mdpi/exo_controls_rewind.png diff --git a/library/src/main/res/drawable-xhdpi/exo_controls_fastforward.png b/library/core/src/main/res/drawable-xhdpi/exo_controls_fastforward.png similarity index 100% rename from library/src/main/res/drawable-xhdpi/exo_controls_fastforward.png rename to library/core/src/main/res/drawable-xhdpi/exo_controls_fastforward.png diff --git a/library/src/main/res/drawable-xhdpi/exo_controls_next.png b/library/core/src/main/res/drawable-xhdpi/exo_controls_next.png similarity index 100% rename from library/src/main/res/drawable-xhdpi/exo_controls_next.png rename to library/core/src/main/res/drawable-xhdpi/exo_controls_next.png diff --git a/library/src/main/res/drawable-xhdpi/exo_controls_pause.png b/library/core/src/main/res/drawable-xhdpi/exo_controls_pause.png similarity index 100% rename from library/src/main/res/drawable-xhdpi/exo_controls_pause.png rename to library/core/src/main/res/drawable-xhdpi/exo_controls_pause.png diff --git a/library/src/main/res/drawable-xhdpi/exo_controls_play.png b/library/core/src/main/res/drawable-xhdpi/exo_controls_play.png similarity index 100% rename from library/src/main/res/drawable-xhdpi/exo_controls_play.png rename to library/core/src/main/res/drawable-xhdpi/exo_controls_play.png diff --git a/library/src/main/res/drawable-xhdpi/exo_controls_previous.png b/library/core/src/main/res/drawable-xhdpi/exo_controls_previous.png similarity index 100% rename from library/src/main/res/drawable-xhdpi/exo_controls_previous.png rename to library/core/src/main/res/drawable-xhdpi/exo_controls_previous.png diff --git a/library/src/main/res/drawable-xhdpi/exo_controls_rewind.png b/library/core/src/main/res/drawable-xhdpi/exo_controls_rewind.png similarity index 100% rename from library/src/main/res/drawable-xhdpi/exo_controls_rewind.png rename to library/core/src/main/res/drawable-xhdpi/exo_controls_rewind.png diff --git a/library/src/main/res/drawable-xxhdpi/exo_controls_fastforward.png b/library/core/src/main/res/drawable-xxhdpi/exo_controls_fastforward.png similarity index 100% rename from library/src/main/res/drawable-xxhdpi/exo_controls_fastforward.png rename to library/core/src/main/res/drawable-xxhdpi/exo_controls_fastforward.png diff --git a/library/src/main/res/drawable-xxhdpi/exo_controls_next.png b/library/core/src/main/res/drawable-xxhdpi/exo_controls_next.png similarity index 100% rename from library/src/main/res/drawable-xxhdpi/exo_controls_next.png rename to library/core/src/main/res/drawable-xxhdpi/exo_controls_next.png diff --git a/library/src/main/res/drawable-xxhdpi/exo_controls_pause.png b/library/core/src/main/res/drawable-xxhdpi/exo_controls_pause.png similarity index 100% rename from library/src/main/res/drawable-xxhdpi/exo_controls_pause.png rename to library/core/src/main/res/drawable-xxhdpi/exo_controls_pause.png diff --git a/library/src/main/res/drawable-xxhdpi/exo_controls_play.png b/library/core/src/main/res/drawable-xxhdpi/exo_controls_play.png similarity index 100% rename from library/src/main/res/drawable-xxhdpi/exo_controls_play.png rename to library/core/src/main/res/drawable-xxhdpi/exo_controls_play.png diff --git a/library/src/main/res/drawable-xxhdpi/exo_controls_previous.png b/library/core/src/main/res/drawable-xxhdpi/exo_controls_previous.png similarity index 100% rename from library/src/main/res/drawable-xxhdpi/exo_controls_previous.png rename to library/core/src/main/res/drawable-xxhdpi/exo_controls_previous.png diff --git a/library/src/main/res/drawable-xxhdpi/exo_controls_rewind.png b/library/core/src/main/res/drawable-xxhdpi/exo_controls_rewind.png similarity index 100% rename from library/src/main/res/drawable-xxhdpi/exo_controls_rewind.png rename to library/core/src/main/res/drawable-xxhdpi/exo_controls_rewind.png diff --git a/library/src/main/res/layout/exo_playback_control_view.xml b/library/core/src/main/res/layout/exo_playback_control_view.xml similarity index 100% rename from library/src/main/res/layout/exo_playback_control_view.xml rename to library/core/src/main/res/layout/exo_playback_control_view.xml diff --git a/library/src/main/res/layout/exo_simple_player_view.xml b/library/core/src/main/res/layout/exo_simple_player_view.xml similarity index 100% rename from library/src/main/res/layout/exo_simple_player_view.xml rename to library/core/src/main/res/layout/exo_simple_player_view.xml diff --git a/library/src/main/res/values-af/strings.xml b/library/core/src/main/res/values-af/strings.xml similarity index 100% rename from library/src/main/res/values-af/strings.xml rename to library/core/src/main/res/values-af/strings.xml diff --git a/library/src/main/res/values-am/strings.xml b/library/core/src/main/res/values-am/strings.xml similarity index 100% rename from library/src/main/res/values-am/strings.xml rename to library/core/src/main/res/values-am/strings.xml diff --git a/library/src/main/res/values-ar/strings.xml b/library/core/src/main/res/values-ar/strings.xml similarity index 100% rename from library/src/main/res/values-ar/strings.xml rename to library/core/src/main/res/values-ar/strings.xml diff --git a/library/src/main/res/values-az-rAZ/strings.xml b/library/core/src/main/res/values-az-rAZ/strings.xml similarity index 100% rename from library/src/main/res/values-az-rAZ/strings.xml rename to library/core/src/main/res/values-az-rAZ/strings.xml diff --git a/library/src/main/res/values-b+sr+Latn/strings.xml b/library/core/src/main/res/values-b+sr+Latn/strings.xml similarity index 100% rename from library/src/main/res/values-b+sr+Latn/strings.xml rename to library/core/src/main/res/values-b+sr+Latn/strings.xml diff --git a/library/src/main/res/values-be-rBY/strings.xml b/library/core/src/main/res/values-be-rBY/strings.xml similarity index 100% rename from library/src/main/res/values-be-rBY/strings.xml rename to library/core/src/main/res/values-be-rBY/strings.xml diff --git a/library/src/main/res/values-bg/strings.xml b/library/core/src/main/res/values-bg/strings.xml similarity index 100% rename from library/src/main/res/values-bg/strings.xml rename to library/core/src/main/res/values-bg/strings.xml diff --git a/library/src/main/res/values-bn-rBD/strings.xml b/library/core/src/main/res/values-bn-rBD/strings.xml similarity index 100% rename from library/src/main/res/values-bn-rBD/strings.xml rename to library/core/src/main/res/values-bn-rBD/strings.xml diff --git a/library/src/main/res/values-bs-rBA/strings.xml b/library/core/src/main/res/values-bs-rBA/strings.xml similarity index 100% rename from library/src/main/res/values-bs-rBA/strings.xml rename to library/core/src/main/res/values-bs-rBA/strings.xml diff --git a/library/src/main/res/values-ca/strings.xml b/library/core/src/main/res/values-ca/strings.xml similarity index 100% rename from library/src/main/res/values-ca/strings.xml rename to library/core/src/main/res/values-ca/strings.xml diff --git a/library/src/main/res/values-cs/strings.xml b/library/core/src/main/res/values-cs/strings.xml similarity index 100% rename from library/src/main/res/values-cs/strings.xml rename to library/core/src/main/res/values-cs/strings.xml diff --git a/library/src/main/res/values-da/strings.xml b/library/core/src/main/res/values-da/strings.xml similarity index 100% rename from library/src/main/res/values-da/strings.xml rename to library/core/src/main/res/values-da/strings.xml diff --git a/library/src/main/res/values-de/strings.xml b/library/core/src/main/res/values-de/strings.xml similarity index 100% rename from library/src/main/res/values-de/strings.xml rename to library/core/src/main/res/values-de/strings.xml diff --git a/library/src/main/res/values-el/strings.xml b/library/core/src/main/res/values-el/strings.xml similarity index 100% rename from library/src/main/res/values-el/strings.xml rename to library/core/src/main/res/values-el/strings.xml diff --git a/library/src/main/res/values-en-rAU/strings.xml b/library/core/src/main/res/values-en-rAU/strings.xml similarity index 100% rename from library/src/main/res/values-en-rAU/strings.xml rename to library/core/src/main/res/values-en-rAU/strings.xml diff --git a/library/src/main/res/values-en-rGB/strings.xml b/library/core/src/main/res/values-en-rGB/strings.xml similarity index 100% rename from library/src/main/res/values-en-rGB/strings.xml rename to library/core/src/main/res/values-en-rGB/strings.xml diff --git a/library/src/main/res/values-en-rIN/strings.xml b/library/core/src/main/res/values-en-rIN/strings.xml similarity index 100% rename from library/src/main/res/values-en-rIN/strings.xml rename to library/core/src/main/res/values-en-rIN/strings.xml diff --git a/library/src/main/res/values-es-rUS/strings.xml b/library/core/src/main/res/values-es-rUS/strings.xml similarity index 100% rename from library/src/main/res/values-es-rUS/strings.xml rename to library/core/src/main/res/values-es-rUS/strings.xml diff --git a/library/src/main/res/values-es/strings.xml b/library/core/src/main/res/values-es/strings.xml similarity index 100% rename from library/src/main/res/values-es/strings.xml rename to library/core/src/main/res/values-es/strings.xml diff --git a/library/src/main/res/values-et-rEE/strings.xml b/library/core/src/main/res/values-et-rEE/strings.xml similarity index 100% rename from library/src/main/res/values-et-rEE/strings.xml rename to library/core/src/main/res/values-et-rEE/strings.xml diff --git a/library/src/main/res/values-eu-rES/strings.xml b/library/core/src/main/res/values-eu-rES/strings.xml similarity index 100% rename from library/src/main/res/values-eu-rES/strings.xml rename to library/core/src/main/res/values-eu-rES/strings.xml diff --git a/library/src/main/res/values-fa/strings.xml b/library/core/src/main/res/values-fa/strings.xml similarity index 100% rename from library/src/main/res/values-fa/strings.xml rename to library/core/src/main/res/values-fa/strings.xml diff --git a/library/src/main/res/values-fi/strings.xml b/library/core/src/main/res/values-fi/strings.xml similarity index 100% rename from library/src/main/res/values-fi/strings.xml rename to library/core/src/main/res/values-fi/strings.xml diff --git a/library/src/main/res/values-fr-rCA/strings.xml b/library/core/src/main/res/values-fr-rCA/strings.xml similarity index 100% rename from library/src/main/res/values-fr-rCA/strings.xml rename to library/core/src/main/res/values-fr-rCA/strings.xml diff --git a/library/src/main/res/values-fr/strings.xml b/library/core/src/main/res/values-fr/strings.xml similarity index 100% rename from library/src/main/res/values-fr/strings.xml rename to library/core/src/main/res/values-fr/strings.xml diff --git a/library/src/main/res/values-gl-rES/strings.xml b/library/core/src/main/res/values-gl-rES/strings.xml similarity index 100% rename from library/src/main/res/values-gl-rES/strings.xml rename to library/core/src/main/res/values-gl-rES/strings.xml diff --git a/library/src/main/res/values-gu-rIN/strings.xml b/library/core/src/main/res/values-gu-rIN/strings.xml similarity index 100% rename from library/src/main/res/values-gu-rIN/strings.xml rename to library/core/src/main/res/values-gu-rIN/strings.xml diff --git a/library/src/main/res/values-hi/strings.xml b/library/core/src/main/res/values-hi/strings.xml similarity index 100% rename from library/src/main/res/values-hi/strings.xml rename to library/core/src/main/res/values-hi/strings.xml diff --git a/library/src/main/res/values-hr/strings.xml b/library/core/src/main/res/values-hr/strings.xml similarity index 100% rename from library/src/main/res/values-hr/strings.xml rename to library/core/src/main/res/values-hr/strings.xml diff --git a/library/src/main/res/values-hu/strings.xml b/library/core/src/main/res/values-hu/strings.xml similarity index 100% rename from library/src/main/res/values-hu/strings.xml rename to library/core/src/main/res/values-hu/strings.xml diff --git a/library/src/main/res/values-hy-rAM/strings.xml b/library/core/src/main/res/values-hy-rAM/strings.xml similarity index 100% rename from library/src/main/res/values-hy-rAM/strings.xml rename to library/core/src/main/res/values-hy-rAM/strings.xml diff --git a/library/src/main/res/values-in/strings.xml b/library/core/src/main/res/values-in/strings.xml similarity index 100% rename from library/src/main/res/values-in/strings.xml rename to library/core/src/main/res/values-in/strings.xml diff --git a/library/src/main/res/values-is-rIS/strings.xml b/library/core/src/main/res/values-is-rIS/strings.xml similarity index 100% rename from library/src/main/res/values-is-rIS/strings.xml rename to library/core/src/main/res/values-is-rIS/strings.xml diff --git a/library/src/main/res/values-it/strings.xml b/library/core/src/main/res/values-it/strings.xml similarity index 100% rename from library/src/main/res/values-it/strings.xml rename to library/core/src/main/res/values-it/strings.xml diff --git a/library/src/main/res/values-iw/strings.xml b/library/core/src/main/res/values-iw/strings.xml similarity index 100% rename from library/src/main/res/values-iw/strings.xml rename to library/core/src/main/res/values-iw/strings.xml diff --git a/library/src/main/res/values-ja/strings.xml b/library/core/src/main/res/values-ja/strings.xml similarity index 100% rename from library/src/main/res/values-ja/strings.xml rename to library/core/src/main/res/values-ja/strings.xml diff --git a/library/src/main/res/values-ka-rGE/strings.xml b/library/core/src/main/res/values-ka-rGE/strings.xml similarity index 100% rename from library/src/main/res/values-ka-rGE/strings.xml rename to library/core/src/main/res/values-ka-rGE/strings.xml diff --git a/library/src/main/res/values-kk-rKZ/strings.xml b/library/core/src/main/res/values-kk-rKZ/strings.xml similarity index 100% rename from library/src/main/res/values-kk-rKZ/strings.xml rename to library/core/src/main/res/values-kk-rKZ/strings.xml diff --git a/library/src/main/res/values-km-rKH/strings.xml b/library/core/src/main/res/values-km-rKH/strings.xml similarity index 100% rename from library/src/main/res/values-km-rKH/strings.xml rename to library/core/src/main/res/values-km-rKH/strings.xml diff --git a/library/src/main/res/values-kn-rIN/strings.xml b/library/core/src/main/res/values-kn-rIN/strings.xml similarity index 100% rename from library/src/main/res/values-kn-rIN/strings.xml rename to library/core/src/main/res/values-kn-rIN/strings.xml diff --git a/library/src/main/res/values-ko/strings.xml b/library/core/src/main/res/values-ko/strings.xml similarity index 100% rename from library/src/main/res/values-ko/strings.xml rename to library/core/src/main/res/values-ko/strings.xml diff --git a/library/src/main/res/values-ky-rKG/strings.xml b/library/core/src/main/res/values-ky-rKG/strings.xml similarity index 100% rename from library/src/main/res/values-ky-rKG/strings.xml rename to library/core/src/main/res/values-ky-rKG/strings.xml diff --git a/library/src/main/res/values-lo-rLA/strings.xml b/library/core/src/main/res/values-lo-rLA/strings.xml similarity index 100% rename from library/src/main/res/values-lo-rLA/strings.xml rename to library/core/src/main/res/values-lo-rLA/strings.xml diff --git a/library/src/main/res/values-lt/strings.xml b/library/core/src/main/res/values-lt/strings.xml similarity index 100% rename from library/src/main/res/values-lt/strings.xml rename to library/core/src/main/res/values-lt/strings.xml diff --git a/library/src/main/res/values-lv/strings.xml b/library/core/src/main/res/values-lv/strings.xml similarity index 100% rename from library/src/main/res/values-lv/strings.xml rename to library/core/src/main/res/values-lv/strings.xml diff --git a/library/src/main/res/values-mk-rMK/strings.xml b/library/core/src/main/res/values-mk-rMK/strings.xml similarity index 100% rename from library/src/main/res/values-mk-rMK/strings.xml rename to library/core/src/main/res/values-mk-rMK/strings.xml diff --git a/library/src/main/res/values-ml-rIN/strings.xml b/library/core/src/main/res/values-ml-rIN/strings.xml similarity index 100% rename from library/src/main/res/values-ml-rIN/strings.xml rename to library/core/src/main/res/values-ml-rIN/strings.xml diff --git a/library/src/main/res/values-mn-rMN/strings.xml b/library/core/src/main/res/values-mn-rMN/strings.xml similarity index 100% rename from library/src/main/res/values-mn-rMN/strings.xml rename to library/core/src/main/res/values-mn-rMN/strings.xml diff --git a/library/src/main/res/values-mr-rIN/strings.xml b/library/core/src/main/res/values-mr-rIN/strings.xml similarity index 100% rename from library/src/main/res/values-mr-rIN/strings.xml rename to library/core/src/main/res/values-mr-rIN/strings.xml diff --git a/library/src/main/res/values-ms-rMY/strings.xml b/library/core/src/main/res/values-ms-rMY/strings.xml similarity index 100% rename from library/src/main/res/values-ms-rMY/strings.xml rename to library/core/src/main/res/values-ms-rMY/strings.xml diff --git a/library/src/main/res/values-my-rMM/strings.xml b/library/core/src/main/res/values-my-rMM/strings.xml similarity index 100% rename from library/src/main/res/values-my-rMM/strings.xml rename to library/core/src/main/res/values-my-rMM/strings.xml diff --git a/library/src/main/res/values-nb/strings.xml b/library/core/src/main/res/values-nb/strings.xml similarity index 100% rename from library/src/main/res/values-nb/strings.xml rename to library/core/src/main/res/values-nb/strings.xml diff --git a/library/src/main/res/values-ne-rNP/strings.xml b/library/core/src/main/res/values-ne-rNP/strings.xml similarity index 100% rename from library/src/main/res/values-ne-rNP/strings.xml rename to library/core/src/main/res/values-ne-rNP/strings.xml diff --git a/library/src/main/res/values-nl/strings.xml b/library/core/src/main/res/values-nl/strings.xml similarity index 100% rename from library/src/main/res/values-nl/strings.xml rename to library/core/src/main/res/values-nl/strings.xml diff --git a/library/src/main/res/values-pa-rIN/strings.xml b/library/core/src/main/res/values-pa-rIN/strings.xml similarity index 100% rename from library/src/main/res/values-pa-rIN/strings.xml rename to library/core/src/main/res/values-pa-rIN/strings.xml diff --git a/library/src/main/res/values-pl/strings.xml b/library/core/src/main/res/values-pl/strings.xml similarity index 100% rename from library/src/main/res/values-pl/strings.xml rename to library/core/src/main/res/values-pl/strings.xml diff --git a/library/src/main/res/values-pt-rBR/strings.xml b/library/core/src/main/res/values-pt-rBR/strings.xml similarity index 100% rename from library/src/main/res/values-pt-rBR/strings.xml rename to library/core/src/main/res/values-pt-rBR/strings.xml diff --git a/library/src/main/res/values-pt-rPT/strings.xml b/library/core/src/main/res/values-pt-rPT/strings.xml similarity index 100% rename from library/src/main/res/values-pt-rPT/strings.xml rename to library/core/src/main/res/values-pt-rPT/strings.xml diff --git a/library/src/main/res/values-pt/strings.xml b/library/core/src/main/res/values-pt/strings.xml similarity index 100% rename from library/src/main/res/values-pt/strings.xml rename to library/core/src/main/res/values-pt/strings.xml diff --git a/library/src/main/res/values-ro/strings.xml b/library/core/src/main/res/values-ro/strings.xml similarity index 100% rename from library/src/main/res/values-ro/strings.xml rename to library/core/src/main/res/values-ro/strings.xml diff --git a/library/src/main/res/values-ru/strings.xml b/library/core/src/main/res/values-ru/strings.xml similarity index 100% rename from library/src/main/res/values-ru/strings.xml rename to library/core/src/main/res/values-ru/strings.xml diff --git a/library/src/main/res/values-si-rLK/strings.xml b/library/core/src/main/res/values-si-rLK/strings.xml similarity index 100% rename from library/src/main/res/values-si-rLK/strings.xml rename to library/core/src/main/res/values-si-rLK/strings.xml diff --git a/library/src/main/res/values-sk/strings.xml b/library/core/src/main/res/values-sk/strings.xml similarity index 100% rename from library/src/main/res/values-sk/strings.xml rename to library/core/src/main/res/values-sk/strings.xml diff --git a/library/src/main/res/values-sl/strings.xml b/library/core/src/main/res/values-sl/strings.xml similarity index 100% rename from library/src/main/res/values-sl/strings.xml rename to library/core/src/main/res/values-sl/strings.xml diff --git a/library/src/main/res/values-sq-rAL/strings.xml b/library/core/src/main/res/values-sq-rAL/strings.xml similarity index 100% rename from library/src/main/res/values-sq-rAL/strings.xml rename to library/core/src/main/res/values-sq-rAL/strings.xml diff --git a/library/src/main/res/values-sr/strings.xml b/library/core/src/main/res/values-sr/strings.xml similarity index 100% rename from library/src/main/res/values-sr/strings.xml rename to library/core/src/main/res/values-sr/strings.xml diff --git a/library/src/main/res/values-sv/strings.xml b/library/core/src/main/res/values-sv/strings.xml similarity index 100% rename from library/src/main/res/values-sv/strings.xml rename to library/core/src/main/res/values-sv/strings.xml diff --git a/library/src/main/res/values-sw/strings.xml b/library/core/src/main/res/values-sw/strings.xml similarity index 100% rename from library/src/main/res/values-sw/strings.xml rename to library/core/src/main/res/values-sw/strings.xml diff --git a/library/src/main/res/values-ta-rIN/strings.xml b/library/core/src/main/res/values-ta-rIN/strings.xml similarity index 100% rename from library/src/main/res/values-ta-rIN/strings.xml rename to library/core/src/main/res/values-ta-rIN/strings.xml diff --git a/library/src/main/res/values-te-rIN/strings.xml b/library/core/src/main/res/values-te-rIN/strings.xml similarity index 100% rename from library/src/main/res/values-te-rIN/strings.xml rename to library/core/src/main/res/values-te-rIN/strings.xml diff --git a/library/src/main/res/values-th/strings.xml b/library/core/src/main/res/values-th/strings.xml similarity index 100% rename from library/src/main/res/values-th/strings.xml rename to library/core/src/main/res/values-th/strings.xml diff --git a/library/src/main/res/values-tl/strings.xml b/library/core/src/main/res/values-tl/strings.xml similarity index 100% rename from library/src/main/res/values-tl/strings.xml rename to library/core/src/main/res/values-tl/strings.xml diff --git a/library/src/main/res/values-tr/strings.xml b/library/core/src/main/res/values-tr/strings.xml similarity index 100% rename from library/src/main/res/values-tr/strings.xml rename to library/core/src/main/res/values-tr/strings.xml diff --git a/library/src/main/res/values-uk/strings.xml b/library/core/src/main/res/values-uk/strings.xml similarity index 100% rename from library/src/main/res/values-uk/strings.xml rename to library/core/src/main/res/values-uk/strings.xml diff --git a/library/src/main/res/values-ur-rPK/strings.xml b/library/core/src/main/res/values-ur-rPK/strings.xml similarity index 100% rename from library/src/main/res/values-ur-rPK/strings.xml rename to library/core/src/main/res/values-ur-rPK/strings.xml diff --git a/library/src/main/res/values-uz-rUZ/strings.xml b/library/core/src/main/res/values-uz-rUZ/strings.xml similarity index 100% rename from library/src/main/res/values-uz-rUZ/strings.xml rename to library/core/src/main/res/values-uz-rUZ/strings.xml diff --git a/library/src/main/res/values-v11/styles.xml b/library/core/src/main/res/values-v11/styles.xml similarity index 100% rename from library/src/main/res/values-v11/styles.xml rename to library/core/src/main/res/values-v11/styles.xml diff --git a/library/src/main/res/values-vi/strings.xml b/library/core/src/main/res/values-vi/strings.xml similarity index 100% rename from library/src/main/res/values-vi/strings.xml rename to library/core/src/main/res/values-vi/strings.xml diff --git a/library/src/main/res/values-zh-rCN/strings.xml b/library/core/src/main/res/values-zh-rCN/strings.xml similarity index 100% rename from library/src/main/res/values-zh-rCN/strings.xml rename to library/core/src/main/res/values-zh-rCN/strings.xml diff --git a/library/src/main/res/values-zh-rHK/strings.xml b/library/core/src/main/res/values-zh-rHK/strings.xml similarity index 100% rename from library/src/main/res/values-zh-rHK/strings.xml rename to library/core/src/main/res/values-zh-rHK/strings.xml diff --git a/library/src/main/res/values-zh-rTW/strings.xml b/library/core/src/main/res/values-zh-rTW/strings.xml similarity index 100% rename from library/src/main/res/values-zh-rTW/strings.xml rename to library/core/src/main/res/values-zh-rTW/strings.xml diff --git a/library/src/main/res/values-zu/strings.xml b/library/core/src/main/res/values-zu/strings.xml similarity index 100% rename from library/src/main/res/values-zu/strings.xml rename to library/core/src/main/res/values-zu/strings.xml diff --git a/library/src/main/res/values/attrs.xml b/library/core/src/main/res/values/attrs.xml similarity index 100% rename from library/src/main/res/values/attrs.xml rename to library/core/src/main/res/values/attrs.xml diff --git a/library/src/main/res/values/constants.xml b/library/core/src/main/res/values/constants.xml similarity index 100% rename from library/src/main/res/values/constants.xml rename to library/core/src/main/res/values/constants.xml diff --git a/library/src/main/res/values/ids.xml b/library/core/src/main/res/values/ids.xml similarity index 100% rename from library/src/main/res/values/ids.xml rename to library/core/src/main/res/values/ids.xml diff --git a/library/src/main/res/values/strings.xml b/library/core/src/main/res/values/strings.xml similarity index 100% rename from library/src/main/res/values/strings.xml rename to library/core/src/main/res/values/strings.xml diff --git a/library/src/main/res/values/styles.xml b/library/core/src/main/res/values/styles.xml similarity index 100% rename from library/src/main/res/values/styles.xml rename to library/core/src/main/res/values/styles.xml diff --git a/settings.gradle b/settings.gradle index b69c134fc4..9578420959 100644 --- a/settings.gradle +++ b/settings.gradle @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. include ':library' +include ':library-core' include ':testutils' include ':demo' include ':playbacktests' @@ -24,6 +25,8 @@ include ':extension-vp9' // Uncomment the following line to use the Cronet Extension. // include ':extension-cronet' +project(':library').projectDir = new File(settingsDir, 'library/all') +project(':library-core').projectDir = new File(settingsDir, 'library/core') project(':extension-ffmpeg').projectDir = new File(settingsDir, 'extensions/ffmpeg') project(':extension-flac').projectDir = new File(settingsDir, 'extensions/flac') project(':extension-gvr').projectDir = new File(settingsDir, 'extensions/gvr') From f9767940cc982c8ce43609a13bd9e5cf17db4a80 Mon Sep 17 00:00:00 2001 From: olly Date: Mon, 20 Mar 2017 03:56:53 -0700 Subject: [PATCH 004/119] Fix misc issues ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=150610549 --- RELEASENOTES.md | 194 +++++++++--------- .../source/dash/DefaultDashChunkSource.java | 3 +- 2 files changed, 98 insertions(+), 99 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index f45cb9aff6..b85c65e769 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -6,30 +6,30 @@ rendering. You can read more about the GVR extension [here](https://medium.com/google-exoplayer/spatial-audio-with-exoplayer-and-gvr-cecb00e9da5f#.xdjebjd7g). * DASH improvements: - * Support embedded CEA-608 closed captions - ([#2362](https://github.com/google/ExoPlayer/issues/2362)). - * Support embedded EMSG events - ([#2176](https://github.com/google/ExoPlayer/issues/2176)). - * Support mspr:pro manifest element - ([#2386](https://github.com/google/ExoPlayer/issues/2386)). - * Correct handling of empty segment indices at the start of live events - ([#1865](https://github.com/google/ExoPlayer/issues/1865)). + * Support embedded CEA-608 closed captions + ([#2362](https://github.com/google/ExoPlayer/issues/2362)). + * Support embedded EMSG events + ([#2176](https://github.com/google/ExoPlayer/issues/2176)). + * Support mspr:pro manifest element + ([#2386](https://github.com/google/ExoPlayer/issues/2386)). + * Correct handling of empty segment indices at the start of live events + ([#1865](https://github.com/google/ExoPlayer/issues/1865)). * HLS improvements: - * Respect initial track selection - ([#2353](https://github.com/google/ExoPlayer/issues/2353)). - * Reduced frequency of media playlist requests when playback position is close - to the live edge ([#2548](https://github.com/google/ExoPlayer/issues/2548)). - * Exposed the master playlist through ExoPlayer.getCurrentManifest() - ([#2537](https://github.com/google/ExoPlayer/issues/2537)). - * Support CLOSED-CAPTIONS #EXT-X-MEDIA type - ([#341](https://github.com/google/ExoPlayer/issues/341)). - * Fixed handling of negative values in #EXT-X-SUPPORT - ([#2495](https://github.com/google/ExoPlayer/issues/2495)). - * Fixed potential endless buffering state for streams with WebVTT subtitles - ([#2424](https://github.com/google/ExoPlayer/issues/2424)). + * Respect initial track selection + ([#2353](https://github.com/google/ExoPlayer/issues/2353)). + * Reduced frequency of media playlist requests when playback position is close + to the live edge ([#2548](https://github.com/google/ExoPlayer/issues/2548)). + * Exposed the master playlist through ExoPlayer.getCurrentManifest() + ([#2537](https://github.com/google/ExoPlayer/issues/2537)). + * Support CLOSED-CAPTIONS #EXT-X-MEDIA type + ([#341](https://github.com/google/ExoPlayer/issues/341)). + * Fixed handling of negative values in #EXT-X-SUPPORT + ([#2495](https://github.com/google/ExoPlayer/issues/2495)). + * Fixed potential endless buffering state for streams with WebVTT subtitles + ([#2424](https://github.com/google/ExoPlayer/issues/2424)). * MPEG-TS improvements: - * Support for multiple programs. - * Support for multiple closed captions and caption service descriptors + * Support for multiple programs. + * Support for multiple closed captions and caption service descriptors ([#2161](https://github.com/google/ExoPlayer/issues/2161)). * MP3: Add `FLAG_ENABLE_CONSTANT_BITRATE_SEEKING` extractor option to enable constant bitrate seeking in MP3 files that would otherwise be unseekable @@ -125,15 +125,15 @@ * HLS: Support for seeking in live streams ([#87](https://github.com/google/ExoPlayer/issues/87)). * HLS: Improved support: - * Support for EXT-X-PROGRAM-DATE-TIME - ([#747](https://github.com/google/ExoPlayer/issues/747)). - * Improved handling of sample timestamps and their alignment across variants - and renditions. - * Fix issue that could cause playbacks to get stuck in an endless initial - buffering state. - * Correctly propagate BehindLiveWindowException instead of - IndexOutOfBoundsException exception - ([#1695](https://github.com/google/ExoPlayer/issues/1695)). + * Support for EXT-X-PROGRAM-DATE-TIME + ([#747](https://github.com/google/ExoPlayer/issues/747)). + * Improved handling of sample timestamps and their alignment across variants + and renditions. + * Fix issue that could cause playbacks to get stuck in an endless initial + buffering state. + * Correctly propagate BehindLiveWindowException instead of + IndexOutOfBoundsException exception + ([#1695](https://github.com/google/ExoPlayer/issues/1695)). * MP3/MP4: Support for ID3 metadata, including embedded album art ([#979](https://github.com/google/ExoPlayer/issues/979)). * Improved customization of UI components. You can read about customization of @@ -143,19 +143,19 @@ MediaPeriod transitions. * EIA608: Support for caption styling and positioning. * MPEG-TS: Improved support: - * Support injection of custom TS payload readers. - * Support injection of custom section payload readers. - * Support SCTE-35 splice information messages. - * Support multiple table sections in a single PSI section. - * Fix NullPointerException when an unsupported stream type is encountered - ([#2149](https://github.com/google/ExoPlayer/issues/2149)). - * Avoid failure when expected ID3 header not found - ([#1966](https://github.com/google/ExoPlayer/issues/1966)). + * Support injection of custom TS payload readers. + * Support injection of custom section payload readers. + * Support SCTE-35 splice information messages. + * Support multiple table sections in a single PSI section. + * Fix NullPointerException when an unsupported stream type is encountered + ([#2149](https://github.com/google/ExoPlayer/issues/2149)). + * Avoid failure when expected ID3 header not found + ([#1966](https://github.com/google/ExoPlayer/issues/1966)). * Improvements to the upstream cache package. - * Support caching of media segments for DASH, HLS and SmoothStreaming. Note - that caching of manifest and playlist files is still not supported in the - (normal) case where the corresponding responses are compressed. - * Support caching for ExtractorMediaSource based playbacks. + * Support caching of media segments for DASH, HLS and SmoothStreaming. Note + that caching of manifest and playlist files is still not supported in the + (normal) case where the corresponding responses are compressed. + * Support caching for ExtractorMediaSource based playbacks. * Improved flexibility of SimpleExoPlayer ([#2102](https://github.com/google/ExoPlayer/issues/2102)). * Fix issue where only the audio of a video would play due to capability @@ -227,62 +227,62 @@ some of the motivations behind ExoPlayer 2.x structure and class names have also been sanitized. Read more [here](https://medium.com/google-exoplayer/exoplayer-2-x-new-package-and-class-names-ef8e1d9ba96f#.lv8sd4nez). * Key architectural changes: - * Late binding between rendering and media source components. Allows the same - rendering components to be re-used from one playback to another. Enables - features such as gapless playback through playlists and DASH multi-period - support. - * Improved track selection design. More details can be found - [here](https://medium.com/google-exoplayer/exoplayer-2-x-track-selection-2b62ff712cc9#.n00zo76b6). - * LoadControl now used to control buffering and loading across all playback - types. - * Media source components given additional structure. A new MediaSource class - has been introduced. MediaSources expose Timelines that describe the media - they expose, and can consist of multiple MediaPeriods. This enables features - such as seeking in live playbacks and DASH multi-period support. - * Responsibility for loading the initial DASH/SmoothStreaming/HLS manifest is - promoted to the corresponding MediaSource components and is no longer the - application's responsibility. - * Higher level abstractions such as SimpleExoPlayer have been added to the - library. These make the library easier to use for common use cases. The demo - app is halved in size as a result, whilst at the same time gaining more - functionality. Read more - [here](https://medium.com/google-exoplayer/exoplayer-2-x-improved-demo-app-d97171aaaaa1). - * Enhanced library support for implementing audio extensions. Read more - [here](https://medium.com/google-exoplayer/exoplayer-2-x-new-audio-features-cfb26c2883a#.ua75vu4s3). - * Format and MediaFormat are replaced by a single Format class. + * Late binding between rendering and media source components. Allows the same + rendering components to be re-used from one playback to another. Enables + features such as gapless playback through playlists and DASH multi-period + support. + * Improved track selection design. More details can be found + [here](https://medium.com/google-exoplayer/exoplayer-2-x-track-selection-2b62ff712cc9#.n00zo76b6). + * LoadControl now used to control buffering and loading across all playback + types. + * Media source components given additional structure. A new MediaSource class + has been introduced. MediaSources expose Timelines that describe the media + they expose, and can consist of multiple MediaPeriods. This enables features + such as seeking in live playbacks and DASH multi-period support. + * Responsibility for loading the initial DASH/SmoothStreaming/HLS manifest is + promoted to the corresponding MediaSource components and is no longer the + application's responsibility. + * Higher level abstractions such as SimpleExoPlayer have been added to the + library. These make the library easier to use for common use cases. The demo + app is halved in size as a result, whilst at the same time gaining more + functionality. Read more + [here](https://medium.com/google-exoplayer/exoplayer-2-x-improved-demo-app-d97171aaaaa1). + * Enhanced library support for implementing audio extensions. Read more + [here](https://medium.com/google-exoplayer/exoplayer-2-x-new-audio-features-cfb26c2883a#.ua75vu4s3). + * Format and MediaFormat are replaced by a single Format class. * Key new features: - * Playlist support. Includes support for gapless playback between playlist - items and consistent application of LoadControl and TrackSelector policies - when transitioning between items - ([#1270](https://github.com/google/ExoPlayer/issues/1270)). - * Seeking in live playbacks for DASH and SmoothStreaming - ([#291](https://github.com/google/ExoPlayer/issues/291)). - * DASH multi-period support - ([#557](https://github.com/google/ExoPlayer/issues/557)). - * MediaSource composition allows MediaSources to be concatenated into a - playlist, merged and looped. Read more - [here](https://medium.com/google-exoplayer/exoplayer-2-x-mediasource-composition-6c285fcbca1f#.zfha8qupz). - * Looping support (see above) - ([#490](https://github.com/google/ExoPlayer/issues/490)). - * Ability to query information about all tracks in a piece of media (including - those not supported by the device) - ([#1121](https://github.com/google/ExoPlayer/issues/1121)). - * Improved player controls. - * Support for PSSH in fMP4 moof atoms - ([#1143](https://github.com/google/ExoPlayer/issues/1143)). - * Support for Opus in Ogg - ([#1447](https://github.com/google/ExoPlayer/issues/1447)). - * CacheDataSource support for standalone media file playbacks (mp3, mp4 etc). - * FFMPEG extension (for audio only). + * Playlist support. Includes support for gapless playback between playlist + items and consistent application of LoadControl and TrackSelector policies + when transitioning between items + ([#1270](https://github.com/google/ExoPlayer/issues/1270)). + * Seeking in live playbacks for DASH and SmoothStreaming + ([#291](https://github.com/google/ExoPlayer/issues/291)). + * DASH multi-period support + ([#557](https://github.com/google/ExoPlayer/issues/557)). + * MediaSource composition allows MediaSources to be concatenated into a + playlist, merged and looped. Read more + [here](https://medium.com/google-exoplayer/exoplayer-2-x-mediasource-composition-6c285fcbca1f#.zfha8qupz). + * Looping support (see above) + ([#490](https://github.com/google/ExoPlayer/issues/490)). + * Ability to query information about all tracks in a piece of media (including + those not supported by the device) + ([#1121](https://github.com/google/ExoPlayer/issues/1121)). + * Improved player controls. + * Support for PSSH in fMP4 moof atoms + ([#1143](https://github.com/google/ExoPlayer/issues/1143)). + * Support for Opus in Ogg + ([#1447](https://github.com/google/ExoPlayer/issues/1447)). + * CacheDataSource support for standalone media file playbacks (mp3, mp4 etc). + * FFMPEG extension (for audio only). * Key bug fixes: - * Removed unnecessary secondary requests when playing standalone media files - ([#1041](https://github.com/google/ExoPlayer/issues/1041)). - * Fixed playback of video only (i.e. no audio) live streams - ([#758](https://github.com/google/ExoPlayer/issues/758)). - * Fixed silent failure when media buffer is too small - ([#583](https://github.com/google/ExoPlayer/issues/583)). - * Suppressed "Sending message to a Handler on a dead thread" warnings - ([#426](https://github.com/google/ExoPlayer/issues/426)). + * Removed unnecessary secondary requests when playing standalone media files + ([#1041](https://github.com/google/ExoPlayer/issues/1041)). + * Fixed playback of video only (i.e. no audio) live streams + ([#758](https://github.com/google/ExoPlayer/issues/758)). + * Fixed silent failure when media buffer is too small + ([#583](https://github.com/google/ExoPlayer/issues/583)). + * Suppressed "Sending message to a Handler on a dead thread" warnings + ([#426](https://github.com/google/ExoPlayer/issues/426)). # Legacy release notes # diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/dash/DefaultDashChunkSource.java b/library/core/src/main/java/com/google/android/exoplayer2/source/dash/DefaultDashChunkSource.java index 7ccea8a2a6..3c928f3ccf 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/dash/DefaultDashChunkSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/dash/DefaultDashChunkSource.java @@ -109,8 +109,7 @@ public class DefaultDashChunkSource implements DashChunkSource { * data ranges are adjacent. * @param enableEventMessageTrack Whether the chunks generated by the source may output an event * message track. - * @param enableEventMessageTrack Whether the chunks generated by the source may output a CEA-608 - * track. + * @param enableCea608Track Whether the chunks generated by the source may output a CEA-608 track. */ public DefaultDashChunkSource(LoaderErrorThrower manifestLoaderErrorThrower, DashManifest manifest, int periodIndex, int adaptationSetIndex, TrackSelection trackSelection, From de6e47f7e65fb4f7b59a14d3d934da119271d484 Mon Sep 17 00:00:00 2001 From: olly Date: Mon, 20 Mar 2017 07:20:39 -0700 Subject: [PATCH 005/119] Fix skipping to keyframe to use correct position Issue: #2575 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=150622487 --- .../main/java/com/google/android/exoplayer2/BaseRenderer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/BaseRenderer.java b/library/core/src/main/java/com/google/android/exoplayer2/BaseRenderer.java index f65be3afcd..f6aae200dd 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/BaseRenderer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/BaseRenderer.java @@ -311,7 +311,7 @@ public abstract class BaseRenderer implements Renderer, RendererCapabilities { * @param timeUs The specified time. */ protected void skipToKeyframeBefore(long timeUs) { - stream.skipToKeyframeBefore(timeUs); + stream.skipToKeyframeBefore(timeUs - streamOffsetUs); } } From 7b08e972e4de657b4b26cafe5bc6296403b36fbd Mon Sep 17 00:00:00 2001 From: olly Date: Mon, 20 Mar 2017 07:30:14 -0700 Subject: [PATCH 006/119] Split UI components into separate module Issue: #2139 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=150623215 --- library/all/build.gradle | 1 + .../exoplayer2/ui/AspectRatioFrameLayout.java | 149 ---- .../exoplayer2/ui/DebugTextViewHelper.java | 181 ---- .../exoplayer2/ui/PlaybackControlView.java | 813 ------------------ .../exoplayer2/ui/SimpleExoPlayerView.java | 718 ---------------- .../exoplayer2/ui/SubtitlePainter.java | 405 --------- .../android/exoplayer2/ui/SubtitleView.java | 265 ------ .../exo_controls_fastforward.xml | 25 - .../drawable-anydpi-v21/exo_controls_next.xml | 25 - .../exo_controls_pause.xml | 25 - .../drawable-anydpi-v21/exo_controls_play.xml | 25 - .../exo_controls_previous.xml | 25 - .../exo_controls_rewind.xml | 25 - .../exo_controls_fastforward.png | Bin 354 -> 0 bytes .../res/drawable-hdpi/exo_controls_next.png | Bin 323 -> 0 bytes .../res/drawable-hdpi/exo_controls_pause.png | Bin 108 -> 0 bytes .../res/drawable-hdpi/exo_controls_play.png | Bin 286 -> 0 bytes .../drawable-hdpi/exo_controls_previous.png | Bin 292 -> 0 bytes .../res/drawable-hdpi/exo_controls_rewind.png | Bin 347 -> 0 bytes .../exo_controls_fastforward.png | Bin 192 -> 0 bytes .../res/drawable-ldpi/exo_controls_next.png | Bin 167 -> 0 bytes .../res/drawable-ldpi/exo_controls_pause.png | Bin 91 -> 0 bytes .../res/drawable-ldpi/exo_controls_play.png | Bin 182 -> 0 bytes .../drawable-ldpi/exo_controls_previous.png | Bin 187 -> 0 bytes .../res/drawable-ldpi/exo_controls_rewind.png | Bin 214 -> 0 bytes .../exo_controls_fastforward.png | Bin 255 -> 0 bytes .../res/drawable-mdpi/exo_controls_next.png | Bin 276 -> 0 bytes .../res/drawable-mdpi/exo_controls_pause.png | Bin 153 -> 0 bytes .../res/drawable-mdpi/exo_controls_play.png | Bin 228 -> 0 bytes .../drawable-mdpi/exo_controls_previous.png | Bin 227 -> 0 bytes .../res/drawable-mdpi/exo_controls_rewind.png | Bin 273 -> 0 bytes .../exo_controls_fastforward.png | Bin 392 -> 0 bytes .../res/drawable-xhdpi/exo_controls_next.png | Bin 334 -> 0 bytes .../res/drawable-xhdpi/exo_controls_pause.png | Bin 164 -> 0 bytes .../res/drawable-xhdpi/exo_controls_play.png | Bin 343 -> 0 bytes .../drawable-xhdpi/exo_controls_previous.png | Bin 339 -> 0 bytes .../drawable-xhdpi/exo_controls_rewind.png | Bin 400 -> 0 bytes .../exo_controls_fastforward.png | Bin 584 -> 0 bytes .../res/drawable-xxhdpi/exo_controls_next.png | Bin 391 -> 0 bytes .../drawable-xxhdpi/exo_controls_pause.png | Bin 113 -> 0 bytes .../res/drawable-xxhdpi/exo_controls_play.png | Bin 384 -> 0 bytes .../drawable-xxhdpi/exo_controls_previous.png | Bin 464 -> 0 bytes .../drawable-xxhdpi/exo_controls_rewind.png | Bin 571 -> 0 bytes .../res/layout/exo_playback_control_view.xml | 87 -- .../res/layout/exo_simple_player_view.xml | 49 -- .../core/src/main/res/values-af/strings.xml | 25 - .../core/src/main/res/values-am/strings.xml | 25 - .../core/src/main/res/values-ar/strings.xml | 25 - .../src/main/res/values-az-rAZ/strings.xml | 25 - .../src/main/res/values-b+sr+Latn/strings.xml | 25 - .../src/main/res/values-be-rBY/strings.xml | 25 - .../core/src/main/res/values-bg/strings.xml | 25 - .../src/main/res/values-bn-rBD/strings.xml | 25 - .../src/main/res/values-bs-rBA/strings.xml | 25 - .../core/src/main/res/values-ca/strings.xml | 25 - .../core/src/main/res/values-cs/strings.xml | 25 - .../core/src/main/res/values-da/strings.xml | 25 - .../core/src/main/res/values-de/strings.xml | 25 - .../core/src/main/res/values-el/strings.xml | 25 - .../src/main/res/values-en-rAU/strings.xml | 25 - .../src/main/res/values-en-rGB/strings.xml | 25 - .../src/main/res/values-en-rIN/strings.xml | 25 - .../src/main/res/values-es-rUS/strings.xml | 25 - .../core/src/main/res/values-es/strings.xml | 25 - .../src/main/res/values-et-rEE/strings.xml | 25 - .../src/main/res/values-eu-rES/strings.xml | 25 - .../core/src/main/res/values-fa/strings.xml | 25 - .../core/src/main/res/values-fi/strings.xml | 25 - .../src/main/res/values-fr-rCA/strings.xml | 25 - .../core/src/main/res/values-fr/strings.xml | 25 - .../src/main/res/values-gl-rES/strings.xml | 25 - .../src/main/res/values-gu-rIN/strings.xml | 25 - .../core/src/main/res/values-hi/strings.xml | 25 - .../core/src/main/res/values-hr/strings.xml | 25 - .../core/src/main/res/values-hu/strings.xml | 25 - .../src/main/res/values-hy-rAM/strings.xml | 25 - .../core/src/main/res/values-in/strings.xml | 25 - .../src/main/res/values-is-rIS/strings.xml | 25 - .../core/src/main/res/values-it/strings.xml | 25 - .../core/src/main/res/values-iw/strings.xml | 25 - .../core/src/main/res/values-ja/strings.xml | 25 - .../src/main/res/values-ka-rGE/strings.xml | 25 - .../src/main/res/values-kk-rKZ/strings.xml | 25 - .../src/main/res/values-km-rKH/strings.xml | 25 - .../src/main/res/values-kn-rIN/strings.xml | 25 - .../core/src/main/res/values-ko/strings.xml | 25 - .../src/main/res/values-ky-rKG/strings.xml | 25 - .../src/main/res/values-lo-rLA/strings.xml | 25 - .../core/src/main/res/values-lt/strings.xml | 25 - .../core/src/main/res/values-lv/strings.xml | 25 - .../src/main/res/values-mk-rMK/strings.xml | 25 - .../src/main/res/values-ml-rIN/strings.xml | 25 - .../src/main/res/values-mn-rMN/strings.xml | 25 - .../src/main/res/values-mr-rIN/strings.xml | 25 - .../src/main/res/values-ms-rMY/strings.xml | 25 - .../src/main/res/values-my-rMM/strings.xml | 25 - .../core/src/main/res/values-nb/strings.xml | 25 - .../src/main/res/values-ne-rNP/strings.xml | 25 - .../core/src/main/res/values-nl/strings.xml | 25 - .../src/main/res/values-pa-rIN/strings.xml | 25 - .../core/src/main/res/values-pl/strings.xml | 25 - .../src/main/res/values-pt-rBR/strings.xml | 25 - .../src/main/res/values-pt-rPT/strings.xml | 25 - .../core/src/main/res/values-pt/strings.xml | 25 - .../core/src/main/res/values-ro/strings.xml | 25 - .../core/src/main/res/values-ru/strings.xml | 25 - .../src/main/res/values-si-rLK/strings.xml | 25 - .../core/src/main/res/values-sk/strings.xml | 25 - .../core/src/main/res/values-sl/strings.xml | 25 - .../src/main/res/values-sq-rAL/strings.xml | 25 - .../core/src/main/res/values-sr/strings.xml | 25 - .../core/src/main/res/values-sv/strings.xml | 25 - .../core/src/main/res/values-sw/strings.xml | 25 - .../src/main/res/values-ta-rIN/strings.xml | 25 - .../src/main/res/values-te-rIN/strings.xml | 25 - .../core/src/main/res/values-th/strings.xml | 25 - .../core/src/main/res/values-tl/strings.xml | 25 - .../core/src/main/res/values-tr/strings.xml | 25 - .../core/src/main/res/values-uk/strings.xml | 25 - .../src/main/res/values-ur-rPK/strings.xml | 25 - .../src/main/res/values-uz-rUZ/strings.xml | 25 - .../core/src/main/res/values-v11/styles.xml | 24 - .../core/src/main/res/values-vi/strings.xml | 25 - .../src/main/res/values-zh-rCN/strings.xml | 25 - .../src/main/res/values-zh-rHK/strings.xml | 25 - .../src/main/res/values-zh-rTW/strings.xml | 25 - .../core/src/main/res/values-zu/strings.xml | 25 - library/core/src/main/res/values/attrs.xml | 62 -- .../core/src/main/res/values/constants.xml | 21 - library/core/src/main/res/values/ids.xml | 34 - library/core/src/main/res/values/strings.xml | 24 - library/core/src/main/res/values/styles.xml | 54 -- library/ui/build.gradle | 35 + 133 files changed, 36 insertions(+), 5061 deletions(-) delete mode 100644 library/core/src/main/java/com/google/android/exoplayer2/ui/AspectRatioFrameLayout.java delete mode 100644 library/core/src/main/java/com/google/android/exoplayer2/ui/DebugTextViewHelper.java delete mode 100644 library/core/src/main/java/com/google/android/exoplayer2/ui/PlaybackControlView.java delete mode 100644 library/core/src/main/java/com/google/android/exoplayer2/ui/SimpleExoPlayerView.java delete mode 100644 library/core/src/main/java/com/google/android/exoplayer2/ui/SubtitlePainter.java delete mode 100644 library/core/src/main/java/com/google/android/exoplayer2/ui/SubtitleView.java delete mode 100644 library/core/src/main/res/drawable-anydpi-v21/exo_controls_fastforward.xml delete mode 100644 library/core/src/main/res/drawable-anydpi-v21/exo_controls_next.xml delete mode 100644 library/core/src/main/res/drawable-anydpi-v21/exo_controls_pause.xml delete mode 100644 library/core/src/main/res/drawable-anydpi-v21/exo_controls_play.xml delete mode 100644 library/core/src/main/res/drawable-anydpi-v21/exo_controls_previous.xml delete mode 100644 library/core/src/main/res/drawable-anydpi-v21/exo_controls_rewind.xml delete mode 100644 library/core/src/main/res/drawable-hdpi/exo_controls_fastforward.png delete mode 100644 library/core/src/main/res/drawable-hdpi/exo_controls_next.png delete mode 100644 library/core/src/main/res/drawable-hdpi/exo_controls_pause.png delete mode 100644 library/core/src/main/res/drawable-hdpi/exo_controls_play.png delete mode 100644 library/core/src/main/res/drawable-hdpi/exo_controls_previous.png delete mode 100644 library/core/src/main/res/drawable-hdpi/exo_controls_rewind.png delete mode 100644 library/core/src/main/res/drawable-ldpi/exo_controls_fastforward.png delete mode 100644 library/core/src/main/res/drawable-ldpi/exo_controls_next.png delete mode 100644 library/core/src/main/res/drawable-ldpi/exo_controls_pause.png delete mode 100644 library/core/src/main/res/drawable-ldpi/exo_controls_play.png delete mode 100644 library/core/src/main/res/drawable-ldpi/exo_controls_previous.png delete mode 100644 library/core/src/main/res/drawable-ldpi/exo_controls_rewind.png delete mode 100644 library/core/src/main/res/drawable-mdpi/exo_controls_fastforward.png delete mode 100644 library/core/src/main/res/drawable-mdpi/exo_controls_next.png delete mode 100644 library/core/src/main/res/drawable-mdpi/exo_controls_pause.png delete mode 100644 library/core/src/main/res/drawable-mdpi/exo_controls_play.png delete mode 100644 library/core/src/main/res/drawable-mdpi/exo_controls_previous.png delete mode 100644 library/core/src/main/res/drawable-mdpi/exo_controls_rewind.png delete mode 100644 library/core/src/main/res/drawable-xhdpi/exo_controls_fastforward.png delete mode 100644 library/core/src/main/res/drawable-xhdpi/exo_controls_next.png delete mode 100644 library/core/src/main/res/drawable-xhdpi/exo_controls_pause.png delete mode 100644 library/core/src/main/res/drawable-xhdpi/exo_controls_play.png delete mode 100644 library/core/src/main/res/drawable-xhdpi/exo_controls_previous.png delete mode 100644 library/core/src/main/res/drawable-xhdpi/exo_controls_rewind.png delete mode 100644 library/core/src/main/res/drawable-xxhdpi/exo_controls_fastforward.png delete mode 100644 library/core/src/main/res/drawable-xxhdpi/exo_controls_next.png delete mode 100644 library/core/src/main/res/drawable-xxhdpi/exo_controls_pause.png delete mode 100644 library/core/src/main/res/drawable-xxhdpi/exo_controls_play.png delete mode 100644 library/core/src/main/res/drawable-xxhdpi/exo_controls_previous.png delete mode 100644 library/core/src/main/res/drawable-xxhdpi/exo_controls_rewind.png delete mode 100644 library/core/src/main/res/layout/exo_playback_control_view.xml delete mode 100644 library/core/src/main/res/layout/exo_simple_player_view.xml delete mode 100644 library/core/src/main/res/values-af/strings.xml delete mode 100644 library/core/src/main/res/values-am/strings.xml delete mode 100644 library/core/src/main/res/values-ar/strings.xml delete mode 100644 library/core/src/main/res/values-az-rAZ/strings.xml delete mode 100644 library/core/src/main/res/values-b+sr+Latn/strings.xml delete mode 100644 library/core/src/main/res/values-be-rBY/strings.xml delete mode 100644 library/core/src/main/res/values-bg/strings.xml delete mode 100644 library/core/src/main/res/values-bn-rBD/strings.xml delete mode 100644 library/core/src/main/res/values-bs-rBA/strings.xml delete mode 100644 library/core/src/main/res/values-ca/strings.xml delete mode 100644 library/core/src/main/res/values-cs/strings.xml delete mode 100644 library/core/src/main/res/values-da/strings.xml delete mode 100644 library/core/src/main/res/values-de/strings.xml delete mode 100644 library/core/src/main/res/values-el/strings.xml delete mode 100644 library/core/src/main/res/values-en-rAU/strings.xml delete mode 100644 library/core/src/main/res/values-en-rGB/strings.xml delete mode 100644 library/core/src/main/res/values-en-rIN/strings.xml delete mode 100644 library/core/src/main/res/values-es-rUS/strings.xml delete mode 100644 library/core/src/main/res/values-es/strings.xml delete mode 100644 library/core/src/main/res/values-et-rEE/strings.xml delete mode 100644 library/core/src/main/res/values-eu-rES/strings.xml delete mode 100644 library/core/src/main/res/values-fa/strings.xml delete mode 100644 library/core/src/main/res/values-fi/strings.xml delete mode 100644 library/core/src/main/res/values-fr-rCA/strings.xml delete mode 100644 library/core/src/main/res/values-fr/strings.xml delete mode 100644 library/core/src/main/res/values-gl-rES/strings.xml delete mode 100644 library/core/src/main/res/values-gu-rIN/strings.xml delete mode 100644 library/core/src/main/res/values-hi/strings.xml delete mode 100644 library/core/src/main/res/values-hr/strings.xml delete mode 100644 library/core/src/main/res/values-hu/strings.xml delete mode 100644 library/core/src/main/res/values-hy-rAM/strings.xml delete mode 100644 library/core/src/main/res/values-in/strings.xml delete mode 100644 library/core/src/main/res/values-is-rIS/strings.xml delete mode 100644 library/core/src/main/res/values-it/strings.xml delete mode 100644 library/core/src/main/res/values-iw/strings.xml delete mode 100644 library/core/src/main/res/values-ja/strings.xml delete mode 100644 library/core/src/main/res/values-ka-rGE/strings.xml delete mode 100644 library/core/src/main/res/values-kk-rKZ/strings.xml delete mode 100644 library/core/src/main/res/values-km-rKH/strings.xml delete mode 100644 library/core/src/main/res/values-kn-rIN/strings.xml delete mode 100644 library/core/src/main/res/values-ko/strings.xml delete mode 100644 library/core/src/main/res/values-ky-rKG/strings.xml delete mode 100644 library/core/src/main/res/values-lo-rLA/strings.xml delete mode 100644 library/core/src/main/res/values-lt/strings.xml delete mode 100644 library/core/src/main/res/values-lv/strings.xml delete mode 100644 library/core/src/main/res/values-mk-rMK/strings.xml delete mode 100644 library/core/src/main/res/values-ml-rIN/strings.xml delete mode 100644 library/core/src/main/res/values-mn-rMN/strings.xml delete mode 100644 library/core/src/main/res/values-mr-rIN/strings.xml delete mode 100644 library/core/src/main/res/values-ms-rMY/strings.xml delete mode 100644 library/core/src/main/res/values-my-rMM/strings.xml delete mode 100644 library/core/src/main/res/values-nb/strings.xml delete mode 100644 library/core/src/main/res/values-ne-rNP/strings.xml delete mode 100644 library/core/src/main/res/values-nl/strings.xml delete mode 100644 library/core/src/main/res/values-pa-rIN/strings.xml delete mode 100644 library/core/src/main/res/values-pl/strings.xml delete mode 100644 library/core/src/main/res/values-pt-rBR/strings.xml delete mode 100644 library/core/src/main/res/values-pt-rPT/strings.xml delete mode 100644 library/core/src/main/res/values-pt/strings.xml delete mode 100644 library/core/src/main/res/values-ro/strings.xml delete mode 100644 library/core/src/main/res/values-ru/strings.xml delete mode 100644 library/core/src/main/res/values-si-rLK/strings.xml delete mode 100644 library/core/src/main/res/values-sk/strings.xml delete mode 100644 library/core/src/main/res/values-sl/strings.xml delete mode 100644 library/core/src/main/res/values-sq-rAL/strings.xml delete mode 100644 library/core/src/main/res/values-sr/strings.xml delete mode 100644 library/core/src/main/res/values-sv/strings.xml delete mode 100644 library/core/src/main/res/values-sw/strings.xml delete mode 100644 library/core/src/main/res/values-ta-rIN/strings.xml delete mode 100644 library/core/src/main/res/values-te-rIN/strings.xml delete mode 100644 library/core/src/main/res/values-th/strings.xml delete mode 100644 library/core/src/main/res/values-tl/strings.xml delete mode 100644 library/core/src/main/res/values-tr/strings.xml delete mode 100644 library/core/src/main/res/values-uk/strings.xml delete mode 100644 library/core/src/main/res/values-ur-rPK/strings.xml delete mode 100644 library/core/src/main/res/values-uz-rUZ/strings.xml delete mode 100644 library/core/src/main/res/values-v11/styles.xml delete mode 100644 library/core/src/main/res/values-vi/strings.xml delete mode 100644 library/core/src/main/res/values-zh-rCN/strings.xml delete mode 100644 library/core/src/main/res/values-zh-rHK/strings.xml delete mode 100644 library/core/src/main/res/values-zh-rTW/strings.xml delete mode 100644 library/core/src/main/res/values-zu/strings.xml delete mode 100644 library/core/src/main/res/values/attrs.xml delete mode 100644 library/core/src/main/res/values/constants.xml delete mode 100644 library/core/src/main/res/values/ids.xml delete mode 100644 library/core/src/main/res/values/strings.xml delete mode 100644 library/core/src/main/res/values/styles.xml create mode 100644 library/ui/build.gradle diff --git a/library/all/build.gradle b/library/all/build.gradle index 0799a184f3..0b7753a9be 100644 --- a/library/all/build.gradle +++ b/library/all/build.gradle @@ -27,6 +27,7 @@ android { dependencies { compile project(':library-core') + compile project(':library-ui') } android.libraryVariants.all { variant -> diff --git a/library/core/src/main/java/com/google/android/exoplayer2/ui/AspectRatioFrameLayout.java b/library/core/src/main/java/com/google/android/exoplayer2/ui/AspectRatioFrameLayout.java deleted file mode 100644 index 3b17e4ddcf..0000000000 --- a/library/core/src/main/java/com/google/android/exoplayer2/ui/AspectRatioFrameLayout.java +++ /dev/null @@ -1,149 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.google.android.exoplayer2.ui; - -import android.content.Context; -import android.content.res.TypedArray; -import android.support.annotation.IntDef; -import android.util.AttributeSet; -import android.widget.FrameLayout; - -import com.google.android.exoplayer2.core.R; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; - -/** - * A {@link FrameLayout} that resizes itself to match a specified aspect ratio. - */ -public final class AspectRatioFrameLayout extends FrameLayout { - - /** - * Resize modes for {@link AspectRatioFrameLayout}. - */ - @Retention(RetentionPolicy.SOURCE) - @IntDef({RESIZE_MODE_FIT, RESIZE_MODE_FIXED_WIDTH, RESIZE_MODE_FIXED_HEIGHT, RESIZE_MODE_FILL}) - public @interface ResizeMode {} - - /** - * Either the width or height is decreased to obtain the desired aspect ratio. - */ - public static final int RESIZE_MODE_FIT = 0; - /** - * The width is fixed and the height is increased or decreased to obtain the desired aspect ratio. - */ - public static final int RESIZE_MODE_FIXED_WIDTH = 1; - /** - * The height is fixed and the width is increased or decreased to obtain the desired aspect ratio. - */ - public static final int RESIZE_MODE_FIXED_HEIGHT = 2; - /** - * The specified aspect ratio is ignored. - */ - public static final int RESIZE_MODE_FILL = 3; - - /** - * The {@link FrameLayout} will not resize itself if the fractional difference between its natural - * aspect ratio and the requested aspect ratio falls below this threshold. - *

- * This tolerance allows the view to occupy the whole of the screen when the requested aspect - * ratio is very close, but not exactly equal to, the aspect ratio of the screen. This may reduce - * the number of view layers that need to be composited by the underlying system, which can help - * to reduce power consumption. - */ - private static final float MAX_ASPECT_RATIO_DEFORMATION_FRACTION = 0.01f; - - private float videoAspectRatio; - private int resizeMode; - - public AspectRatioFrameLayout(Context context) { - this(context, null); - } - - public AspectRatioFrameLayout(Context context, AttributeSet attrs) { - super(context, attrs); - resizeMode = RESIZE_MODE_FIT; - if (attrs != null) { - TypedArray a = context.getTheme().obtainStyledAttributes(attrs, - R.styleable.AspectRatioFrameLayout, 0, 0); - try { - resizeMode = a.getInt(R.styleable.AspectRatioFrameLayout_resize_mode, RESIZE_MODE_FIT); - } finally { - a.recycle(); - } - } - } - - /** - * Set the aspect ratio that this view should satisfy. - * - * @param widthHeightRatio The width to height ratio. - */ - public void setAspectRatio(float widthHeightRatio) { - if (this.videoAspectRatio != widthHeightRatio) { - this.videoAspectRatio = widthHeightRatio; - requestLayout(); - } - } - - /** - * Sets the resize mode. - * - * @param resizeMode The resize mode. - */ - public void setResizeMode(@ResizeMode int resizeMode) { - if (this.resizeMode != resizeMode) { - this.resizeMode = resizeMode; - requestLayout(); - } - } - - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - super.onMeasure(widthMeasureSpec, heightMeasureSpec); - if (resizeMode == RESIZE_MODE_FILL || videoAspectRatio <= 0) { - // Aspect ratio not set. - return; - } - - int width = getMeasuredWidth(); - int height = getMeasuredHeight(); - float viewAspectRatio = (float) width / height; - float aspectDeformation = videoAspectRatio / viewAspectRatio - 1; - if (Math.abs(aspectDeformation) <= MAX_ASPECT_RATIO_DEFORMATION_FRACTION) { - // We're within the allowed tolerance. - return; - } - - switch (resizeMode) { - case RESIZE_MODE_FIXED_WIDTH: - height = (int) (width / videoAspectRatio); - break; - case RESIZE_MODE_FIXED_HEIGHT: - width = (int) (height * videoAspectRatio); - break; - default: - if (aspectDeformation > 0) { - height = (int) (width / videoAspectRatio); - } else { - width = (int) (height * videoAspectRatio); - } - break; - } - super.onMeasure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), - MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY)); - } - -} diff --git a/library/core/src/main/java/com/google/android/exoplayer2/ui/DebugTextViewHelper.java b/library/core/src/main/java/com/google/android/exoplayer2/ui/DebugTextViewHelper.java deleted file mode 100644 index 1bf5b59a4a..0000000000 --- a/library/core/src/main/java/com/google/android/exoplayer2/ui/DebugTextViewHelper.java +++ /dev/null @@ -1,181 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.google.android.exoplayer2.ui; - -import android.widget.TextView; -import com.google.android.exoplayer2.ExoPlaybackException; -import com.google.android.exoplayer2.ExoPlayer; -import com.google.android.exoplayer2.Format; -import com.google.android.exoplayer2.SimpleExoPlayer; -import com.google.android.exoplayer2.Timeline; -import com.google.android.exoplayer2.decoder.DecoderCounters; -import com.google.android.exoplayer2.source.TrackGroupArray; -import com.google.android.exoplayer2.trackselection.TrackSelectionArray; - -/** - * A helper class for periodically updating a {@link TextView} with debug information obtained from - * a {@link SimpleExoPlayer}. - */ -public final class DebugTextViewHelper implements Runnable, ExoPlayer.EventListener { - - private static final int REFRESH_INTERVAL_MS = 1000; - - private final SimpleExoPlayer player; - private final TextView textView; - - private boolean started; - - /** - * @param player The {@link SimpleExoPlayer} from which debug information should be obtained. - * @param textView The {@link TextView} that should be updated to display the information. - */ - public DebugTextViewHelper(SimpleExoPlayer player, TextView textView) { - this.player = player; - this.textView = textView; - } - - /** - * Starts periodic updates of the {@link TextView}. Must be called from the application's main - * thread. - */ - public void start() { - if (started) { - return; - } - started = true; - player.addListener(this); - updateAndPost(); - } - - /** - * Stops periodic updates of the {@link TextView}. Must be called from the application's main - * thread. - */ - public void stop() { - if (!started) { - return; - } - started = false; - player.removeListener(this); - textView.removeCallbacks(this); - } - - // ExoPlayer.EventListener implementation. - - @Override - public void onLoadingChanged(boolean isLoading) { - // Do nothing. - } - - @Override - public void onPlayerStateChanged(boolean playWhenReady, int playbackState) { - updateAndPost(); - } - - @Override - public void onPositionDiscontinuity() { - updateAndPost(); - } - - @Override - public void onTimelineChanged(Timeline timeline, Object manifest) { - // Do nothing. - } - - @Override - public void onPlayerError(ExoPlaybackException error) { - // Do nothing. - } - - @Override - public void onTracksChanged(TrackGroupArray tracks, TrackSelectionArray selections) { - // Do nothing. - } - - // Runnable implementation. - - @Override - public void run() { - updateAndPost(); - } - - // Private methods. - - private void updateAndPost() { - textView.setText(getPlayerStateString() + getPlayerWindowIndexString() + getVideoString() - + getAudioString()); - textView.removeCallbacks(this); - textView.postDelayed(this, REFRESH_INTERVAL_MS); - } - - private String getPlayerStateString() { - String text = "playWhenReady:" + player.getPlayWhenReady() + " playbackState:"; - switch (player.getPlaybackState()) { - case ExoPlayer.STATE_BUFFERING: - text += "buffering"; - break; - case ExoPlayer.STATE_ENDED: - text += "ended"; - break; - case ExoPlayer.STATE_IDLE: - text += "idle"; - break; - case ExoPlayer.STATE_READY: - text += "ready"; - break; - default: - text += "unknown"; - break; - } - return text; - } - - private String getPlayerWindowIndexString() { - return " window:" + player.getCurrentWindowIndex(); - } - - private String getVideoString() { - Format format = player.getVideoFormat(); - if (format == null) { - return ""; - } - return "\n" + format.sampleMimeType + "(id:" + format.id + " r:" + format.width + "x" - + format.height + getDecoderCountersBufferCountString(player.getVideoDecoderCounters()) - + ")"; - } - - private String getAudioString() { - Format format = player.getAudioFormat(); - if (format == null) { - return ""; - } - return "\n" + format.sampleMimeType + "(id:" + format.id + " hz:" + format.sampleRate + " ch:" - + format.channelCount - + getDecoderCountersBufferCountString(player.getAudioDecoderCounters()) + ")"; - } - - private static String getDecoderCountersBufferCountString(DecoderCounters counters) { - if (counters == null) { - return ""; - } - counters.ensureUpdated(); - return " rb:" + counters.renderedOutputBufferCount - + " sb:" + counters.skippedOutputBufferCount - + " db:" + counters.droppedOutputBufferCount - + " mcdb:" + counters.maxConsecutiveDroppedOutputBufferCount; - } - -} diff --git a/library/core/src/main/java/com/google/android/exoplayer2/ui/PlaybackControlView.java b/library/core/src/main/java/com/google/android/exoplayer2/ui/PlaybackControlView.java deleted file mode 100644 index 4bde88d972..0000000000 --- a/library/core/src/main/java/com/google/android/exoplayer2/ui/PlaybackControlView.java +++ /dev/null @@ -1,813 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.google.android.exoplayer2.ui; - -import android.annotation.TargetApi; -import android.content.Context; -import android.content.res.TypedArray; -import android.os.SystemClock; -import android.util.AttributeSet; -import android.view.KeyEvent; -import android.view.LayoutInflater; -import android.view.View; -import android.widget.FrameLayout; -import android.widget.SeekBar; -import android.widget.TextView; -import com.google.android.exoplayer2.C; -import com.google.android.exoplayer2.ExoPlaybackException; -import com.google.android.exoplayer2.ExoPlayer; -import com.google.android.exoplayer2.Timeline; -import com.google.android.exoplayer2.core.R; -import com.google.android.exoplayer2.source.TrackGroupArray; -import com.google.android.exoplayer2.trackselection.TrackSelectionArray; -import com.google.android.exoplayer2.util.Util; -import java.util.Formatter; -import java.util.Locale; - -/** - * A view for controlling {@link ExoPlayer} instances. - *

- * A PlaybackControlView can be customized by setting attributes (or calling corresponding methods), - * overriding the view's layout file or by specifying a custom view layout file, as outlined below. - * - *

Attributes

- * The following attributes can be set on a PlaybackControlView when used in a layout XML file: - *

- *

    - *
  • {@code show_timeout} - The time between the last user interaction and the controls - * being automatically hidden, in milliseconds. Use zero if the controls should not - * automatically timeout. - *
      - *
    • Corresponding method: {@link #setShowTimeoutMs(int)}
    • - *
    • Default: {@link #DEFAULT_SHOW_TIMEOUT_MS}
    • - *
    - *
  • - *
  • {@code rewind_increment} - The duration of the rewind applied when the user taps the - * rewind button, in milliseconds. Use zero to disable the rewind button. - *
      - *
    • Corresponding method: {@link #setRewindIncrementMs(int)}
    • - *
    • Default: {@link #DEFAULT_REWIND_MS}
    • - *
    - *
  • - *
  • {@code fastforward_increment} - Like {@code rewind_increment}, but for fast forward. - *
      - *
    • Corresponding method: {@link #setFastForwardIncrementMs(int)}
    • - *
    • Default: {@link #DEFAULT_FAST_FORWARD_MS}
    • - *
    - *
  • - *
  • {@code controller_layout_id} - Specifies the id of the layout to be inflated. See - * below for more details. - *
      - *
    • Corresponding method: None
    • - *
    • Default: {@code R.id.exo_playback_control_view}
    • - *
    - *
  • - *
- * - *

Overriding the layout file

- * To customize the layout of PlaybackControlView throughout your app, or just for certain - * configurations, you can define {@code exo_playback_control_view.xml} layout files in your - * application {@code res/layout*} directories. These layouts will override the one provided by the - * ExoPlayer library, and will be inflated for use by PlaybackControlView. The view identifies and - * binds its children by looking for the following ids: - *

- *

    - *
  • {@code exo_play} - The play button. - *
      - *
    • Type: {@link View}
    • - *
    - *
  • - *
  • {@code exo_pause} - The pause button. - *
      - *
    • Type: {@link View}
    • - *
    - *
  • - *
  • {@code exo_ffwd} - The fast forward button. - *
      - *
    • Type: {@link View}
    • - *
    - *
  • - *
  • {@code exo_rew} - The rewind button. - *
      - *
    • Type: {@link View}
    • - *
    - *
  • - *
  • {@code exo_prev} - The previous track button. - *
      - *
    • Type: {@link View}
    • - *
    - *
  • - *
  • {@code exo_next} - The next track button. - *
      - *
    • Type: {@link View}
    • - *
    - *
  • - *
  • {@code exo_position} - Text view displaying the current playback position. - *
      - *
    • Type: {@link TextView}
    • - *
    - *
  • - *
  • {@code exo_duration} - Text view displaying the current media duration. - *
      - *
    • Type: {@link TextView}
    • - *
    - *
  • - *
  • {@code exo_progress} - Seek bar that's updated during playback and allows seeking. - *
      - *
    • Type: {@link SeekBar}
    • - *
    - *
  • - *
- *

- * All child views are optional and so can be omitted if not required, however where defined they - * must be of the expected type. - * - *

Specifying a custom layout file

- * Defining your own {@code exo_playback_control_view.xml} is useful to customize the layout of - * PlaybackControlView throughout your application. It's also possible to customize the layout for a - * single instance in a layout file. This is achieved by setting the {@code controller_layout_id} - * attribute on a PlaybackControlView. This will cause the specified layout to be inflated instead - * of {@code exo_playback_control_view.xml} for only the instance on which the attribute is set. - */ -public class PlaybackControlView extends FrameLayout { - - /** - * Listener to be notified about changes of the visibility of the UI control. - */ - public interface VisibilityListener { - - /** - * Called when the visibility changes. - * - * @param visibility The new visibility. Either {@link View#VISIBLE} or {@link View#GONE}. - */ - void onVisibilityChange(int visibility); - - } - - /** - * Dispatches seek operations to the player. - */ - public interface SeekDispatcher { - - /** - * @param player The player to seek. - * @param windowIndex The index of the window. - * @param positionMs The seek position in the specified window, or {@link C#TIME_UNSET} to seek - * to the window's default position. - * @return True if the seek was dispatched. False otherwise. - */ - boolean dispatchSeek(ExoPlayer player, int windowIndex, long positionMs); - - } - - /** - * Default {@link SeekDispatcher} that dispatches seeks to the player without modification. - */ - public static final SeekDispatcher DEFAULT_SEEK_DISPATCHER = new SeekDispatcher() { - - @Override - public boolean dispatchSeek(ExoPlayer player, int windowIndex, long positionMs) { - player.seekTo(windowIndex, positionMs); - return true; - } - - }; - - public static final int DEFAULT_FAST_FORWARD_MS = 15000; - public static final int DEFAULT_REWIND_MS = 5000; - public static final int DEFAULT_SHOW_TIMEOUT_MS = 5000; - - private static final int PROGRESS_BAR_MAX = 1000; - private static final long MAX_POSITION_FOR_SEEK_TO_PREVIOUS = 3000; - - private final ComponentListener componentListener; - private final View previousButton; - private final View nextButton; - private final View playButton; - private final View pauseButton; - private final View fastForwardButton; - private final View rewindButton; - private final TextView durationView; - private final TextView positionView; - private final SeekBar progressBar; - private final StringBuilder formatBuilder; - private final Formatter formatter; - private final Timeline.Window currentWindow; - - private ExoPlayer player; - private SeekDispatcher seekDispatcher; - private VisibilityListener visibilityListener; - - private boolean isAttachedToWindow; - private boolean dragging; - private int rewindMs; - private int fastForwardMs; - private int showTimeoutMs; - private long hideAtMs; - - private final Runnable updateProgressAction = new Runnable() { - @Override - public void run() { - updateProgress(); - } - }; - - private final Runnable hideAction = new Runnable() { - @Override - public void run() { - hide(); - } - }; - - public PlaybackControlView(Context context) { - this(context, null); - } - - public PlaybackControlView(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } - - public PlaybackControlView(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - - int controllerLayoutId = R.layout.exo_playback_control_view; - rewindMs = DEFAULT_REWIND_MS; - fastForwardMs = DEFAULT_FAST_FORWARD_MS; - showTimeoutMs = DEFAULT_SHOW_TIMEOUT_MS; - if (attrs != null) { - TypedArray a = context.getTheme().obtainStyledAttributes(attrs, - R.styleable.PlaybackControlView, 0, 0); - try { - rewindMs = a.getInt(R.styleable.PlaybackControlView_rewind_increment, rewindMs); - fastForwardMs = a.getInt(R.styleable.PlaybackControlView_fastforward_increment, - fastForwardMs); - showTimeoutMs = a.getInt(R.styleable.PlaybackControlView_show_timeout, showTimeoutMs); - controllerLayoutId = a.getResourceId(R.styleable.PlaybackControlView_controller_layout_id, - controllerLayoutId); - } finally { - a.recycle(); - } - } - currentWindow = new Timeline.Window(); - formatBuilder = new StringBuilder(); - formatter = new Formatter(formatBuilder, Locale.getDefault()); - componentListener = new ComponentListener(); - seekDispatcher = DEFAULT_SEEK_DISPATCHER; - - LayoutInflater.from(context).inflate(controllerLayoutId, this); - setDescendantFocusability(FOCUS_AFTER_DESCENDANTS); - - durationView = (TextView) findViewById(R.id.exo_duration); - positionView = (TextView) findViewById(R.id.exo_position); - progressBar = (SeekBar) findViewById(R.id.exo_progress); - if (progressBar != null) { - progressBar.setOnSeekBarChangeListener(componentListener); - progressBar.setMax(PROGRESS_BAR_MAX); - } - playButton = findViewById(R.id.exo_play); - if (playButton != null) { - playButton.setOnClickListener(componentListener); - } - pauseButton = findViewById(R.id.exo_pause); - if (pauseButton != null) { - pauseButton.setOnClickListener(componentListener); - } - previousButton = findViewById(R.id.exo_prev); - if (previousButton != null) { - previousButton.setOnClickListener(componentListener); - } - nextButton = findViewById(R.id.exo_next); - if (nextButton != null) { - nextButton.setOnClickListener(componentListener); - } - rewindButton = findViewById(R.id.exo_rew); - if (rewindButton != null) { - rewindButton.setOnClickListener(componentListener); - } - fastForwardButton = findViewById(R.id.exo_ffwd); - if (fastForwardButton != null) { - fastForwardButton.setOnClickListener(componentListener); - } - } - - /** - * Returns the player currently being controlled by this view, or null if no player is set. - */ - public ExoPlayer getPlayer() { - return player; - } - - /** - * Sets the {@link ExoPlayer} to control. - * - * @param player the {@code ExoPlayer} to control. - */ - public void setPlayer(ExoPlayer player) { - if (this.player == player) { - return; - } - if (this.player != null) { - this.player.removeListener(componentListener); - } - this.player = player; - if (player != null) { - player.addListener(componentListener); - } - updateAll(); - } - - /** - * Sets the {@link VisibilityListener}. - * - * @param listener The listener to be notified about visibility changes. - */ - public void setVisibilityListener(VisibilityListener listener) { - this.visibilityListener = listener; - } - - /** - * Sets the {@link SeekDispatcher}. - * - * @param seekDispatcher The {@link SeekDispatcher}, or null to use - * {@link #DEFAULT_SEEK_DISPATCHER}. - */ - public void setSeekDispatcher(SeekDispatcher seekDispatcher) { - this.seekDispatcher = seekDispatcher == null ? DEFAULT_SEEK_DISPATCHER : seekDispatcher; - } - - /** - * Sets the rewind increment in milliseconds. - * - * @param rewindMs The rewind increment in milliseconds. A non-positive value will cause the - * rewind button to be disabled. - */ - public void setRewindIncrementMs(int rewindMs) { - this.rewindMs = rewindMs; - updateNavigation(); - } - - /** - * Sets the fast forward increment in milliseconds. - * - * @param fastForwardMs The fast forward increment in milliseconds. A non-positive value will - * cause the fast forward button to be disabled. - */ - public void setFastForwardIncrementMs(int fastForwardMs) { - this.fastForwardMs = fastForwardMs; - updateNavigation(); - } - - /** - * Returns the playback controls timeout. The playback controls are automatically hidden after - * this duration of time has elapsed without user input. - * - * @return The duration in milliseconds. A non-positive value indicates that the controls will - * remain visible indefinitely. - */ - public int getShowTimeoutMs() { - return showTimeoutMs; - } - - /** - * Sets the playback controls timeout. The playback controls are automatically hidden after this - * duration of time has elapsed without user input. - * - * @param showTimeoutMs The duration in milliseconds. A non-positive value will cause the controls - * to remain visible indefinitely. - */ - public void setShowTimeoutMs(int showTimeoutMs) { - this.showTimeoutMs = showTimeoutMs; - } - - /** - * Shows the playback controls. If {@link #getShowTimeoutMs()} is positive then the controls will - * be automatically hidden after this duration of time has elapsed without user input. - */ - public void show() { - if (!isVisible()) { - setVisibility(VISIBLE); - if (visibilityListener != null) { - visibilityListener.onVisibilityChange(getVisibility()); - } - updateAll(); - requestPlayPauseFocus(); - } - // Call hideAfterTimeout even if already visible to reset the timeout. - hideAfterTimeout(); - } - - /** - * Hides the controller. - */ - public void hide() { - if (isVisible()) { - setVisibility(GONE); - if (visibilityListener != null) { - visibilityListener.onVisibilityChange(getVisibility()); - } - removeCallbacks(updateProgressAction); - removeCallbacks(hideAction); - hideAtMs = C.TIME_UNSET; - } - } - - /** - * Returns whether the controller is currently visible. - */ - public boolean isVisible() { - return getVisibility() == VISIBLE; - } - - private void hideAfterTimeout() { - removeCallbacks(hideAction); - if (showTimeoutMs > 0) { - hideAtMs = SystemClock.uptimeMillis() + showTimeoutMs; - if (isAttachedToWindow) { - postDelayed(hideAction, showTimeoutMs); - } - } else { - hideAtMs = C.TIME_UNSET; - } - } - - private void updateAll() { - updatePlayPauseButton(); - updateNavigation(); - updateProgress(); - } - - private void updatePlayPauseButton() { - if (!isVisible() || !isAttachedToWindow) { - return; - } - boolean requestPlayPauseFocus = false; - boolean playing = player != null && player.getPlayWhenReady(); - if (playButton != null) { - requestPlayPauseFocus |= playing && playButton.isFocused(); - playButton.setVisibility(playing ? View.GONE : View.VISIBLE); - } - if (pauseButton != null) { - requestPlayPauseFocus |= !playing && pauseButton.isFocused(); - pauseButton.setVisibility(!playing ? View.GONE : View.VISIBLE); - } - if (requestPlayPauseFocus) { - requestPlayPauseFocus(); - } - } - - private void updateNavigation() { - if (!isVisible() || !isAttachedToWindow) { - return; - } - Timeline currentTimeline = player != null ? player.getCurrentTimeline() : null; - boolean haveNonEmptyTimeline = currentTimeline != null && !currentTimeline.isEmpty(); - boolean isSeekable = false; - boolean enablePrevious = false; - boolean enableNext = false; - if (haveNonEmptyTimeline) { - int currentWindowIndex = player.getCurrentWindowIndex(); - currentTimeline.getWindow(currentWindowIndex, currentWindow); - isSeekable = currentWindow.isSeekable; - enablePrevious = currentWindowIndex > 0 || isSeekable || !currentWindow.isDynamic; - enableNext = (currentWindowIndex < currentTimeline.getWindowCount() - 1) - || currentWindow.isDynamic; - } - setButtonEnabled(enablePrevious , previousButton); - setButtonEnabled(enableNext, nextButton); - setButtonEnabled(fastForwardMs > 0 && isSeekable, fastForwardButton); - setButtonEnabled(rewindMs > 0 && isSeekable, rewindButton); - if (progressBar != null) { - progressBar.setEnabled(isSeekable); - } - } - - private void updateProgress() { - if (!isVisible() || !isAttachedToWindow) { - return; - } - long duration = player == null ? 0 : player.getDuration(); - long position = player == null ? 0 : player.getCurrentPosition(); - if (durationView != null) { - durationView.setText(stringForTime(duration)); - } - if (positionView != null && !dragging) { - positionView.setText(stringForTime(position)); - } - - if (progressBar != null) { - if (!dragging) { - progressBar.setProgress(progressBarValue(position)); - } - long bufferedPosition = player == null ? 0 : player.getBufferedPosition(); - progressBar.setSecondaryProgress(progressBarValue(bufferedPosition)); - // Remove scheduled updates. - } - removeCallbacks(updateProgressAction); - // Schedule an update if necessary. - int playbackState = player == null ? ExoPlayer.STATE_IDLE : player.getPlaybackState(); - if (playbackState != ExoPlayer.STATE_IDLE && playbackState != ExoPlayer.STATE_ENDED) { - long delayMs; - if (player.getPlayWhenReady() && playbackState == ExoPlayer.STATE_READY) { - delayMs = 1000 - (position % 1000); - if (delayMs < 200) { - delayMs += 1000; - } - } else { - delayMs = 1000; - } - postDelayed(updateProgressAction, delayMs); - } - } - - private void requestPlayPauseFocus() { - boolean playing = player != null && player.getPlayWhenReady(); - if (!playing && playButton != null) { - playButton.requestFocus(); - } else if (playing && pauseButton != null) { - pauseButton.requestFocus(); - } - } - - private void setButtonEnabled(boolean enabled, View view) { - if (view == null) { - return; - } - view.setEnabled(enabled); - if (Util.SDK_INT >= 11) { - setViewAlphaV11(view, enabled ? 1f : 0.3f); - view.setVisibility(VISIBLE); - } else { - view.setVisibility(enabled ? VISIBLE : INVISIBLE); - } - } - - @TargetApi(11) - private void setViewAlphaV11(View view, float alpha) { - view.setAlpha(alpha); - } - - private String stringForTime(long timeMs) { - if (timeMs == C.TIME_UNSET) { - timeMs = 0; - } - long totalSeconds = (timeMs + 500) / 1000; - long seconds = totalSeconds % 60; - long minutes = (totalSeconds / 60) % 60; - long hours = totalSeconds / 3600; - formatBuilder.setLength(0); - return hours > 0 ? formatter.format("%d:%02d:%02d", hours, minutes, seconds).toString() - : formatter.format("%02d:%02d", minutes, seconds).toString(); - } - - private int progressBarValue(long position) { - long duration = player == null ? C.TIME_UNSET : player.getDuration(); - return duration == C.TIME_UNSET || duration == 0 ? 0 - : (int) ((position * PROGRESS_BAR_MAX) / duration); - } - - private long positionValue(int progress) { - long duration = player == null ? C.TIME_UNSET : player.getDuration(); - return duration == C.TIME_UNSET ? 0 : ((duration * progress) / PROGRESS_BAR_MAX); - } - - private void previous() { - Timeline currentTimeline = player.getCurrentTimeline(); - if (currentTimeline.isEmpty()) { - return; - } - int currentWindowIndex = player.getCurrentWindowIndex(); - currentTimeline.getWindow(currentWindowIndex, currentWindow); - if (currentWindowIndex > 0 && (player.getCurrentPosition() <= MAX_POSITION_FOR_SEEK_TO_PREVIOUS - || (currentWindow.isDynamic && !currentWindow.isSeekable))) { - seekTo(currentWindowIndex - 1, C.TIME_UNSET); - } else { - seekTo(0); - } - } - - private void next() { - Timeline currentTimeline = player.getCurrentTimeline(); - if (currentTimeline.isEmpty()) { - return; - } - int currentWindowIndex = player.getCurrentWindowIndex(); - if (currentWindowIndex < currentTimeline.getWindowCount() - 1) { - seekTo(currentWindowIndex + 1, C.TIME_UNSET); - } else if (currentTimeline.getWindow(currentWindowIndex, currentWindow, false).isDynamic) { - seekTo(currentWindowIndex, C.TIME_UNSET); - } - } - - private void rewind() { - if (rewindMs <= 0) { - return; - } - seekTo(Math.max(player.getCurrentPosition() - rewindMs, 0)); - } - - private void fastForward() { - if (fastForwardMs <= 0) { - return; - } - seekTo(Math.min(player.getCurrentPosition() + fastForwardMs, player.getDuration())); - } - - private void seekTo(long positionMs) { - seekTo(player.getCurrentWindowIndex(), positionMs); - } - - private void seekTo(int windowIndex, long positionMs) { - boolean dispatched = seekDispatcher.dispatchSeek(player, windowIndex, positionMs); - if (!dispatched) { - // The seek wasn't dispatched. If the progress bar was dragged by the user to perform the - // seek then it'll now be in the wrong position. Trigger a progress update to snap it back. - updateProgress(); - } - } - - @Override - public void onAttachedToWindow() { - super.onAttachedToWindow(); - isAttachedToWindow = true; - if (hideAtMs != C.TIME_UNSET) { - long delayMs = hideAtMs - SystemClock.uptimeMillis(); - if (delayMs <= 0) { - hide(); - } else { - postDelayed(hideAction, delayMs); - } - } - updateAll(); - } - - @Override - public void onDetachedFromWindow() { - super.onDetachedFromWindow(); - isAttachedToWindow = false; - removeCallbacks(updateProgressAction); - removeCallbacks(hideAction); - } - - @Override - public boolean dispatchKeyEvent(KeyEvent event) { - boolean handled = dispatchMediaKeyEvent(event) || super.dispatchKeyEvent(event); - if (handled) { - show(); - } - return handled; - } - - /** - * Called to process media key events. Any {@link KeyEvent} can be passed but only media key - * events will be handled. - * - * @param event A key event. - * @return Whether the key event was handled. - */ - public boolean dispatchMediaKeyEvent(KeyEvent event) { - int keyCode = event.getKeyCode(); - if (player == null || !isHandledMediaKey(keyCode)) { - return false; - } - if (event.getAction() == KeyEvent.ACTION_DOWN) { - switch (keyCode) { - case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD: - fastForward(); - break; - case KeyEvent.KEYCODE_MEDIA_REWIND: - rewind(); - break; - case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE: - player.setPlayWhenReady(!player.getPlayWhenReady()); - break; - case KeyEvent.KEYCODE_MEDIA_PLAY: - player.setPlayWhenReady(true); - break; - case KeyEvent.KEYCODE_MEDIA_PAUSE: - player.setPlayWhenReady(false); - break; - case KeyEvent.KEYCODE_MEDIA_NEXT: - next(); - break; - case KeyEvent.KEYCODE_MEDIA_PREVIOUS: - previous(); - break; - default: - break; - } - } - show(); - return true; - } - - private static boolean isHandledMediaKey(int keyCode) { - return keyCode == KeyEvent.KEYCODE_MEDIA_FAST_FORWARD - || keyCode == KeyEvent.KEYCODE_MEDIA_REWIND - || keyCode == KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE - || keyCode == KeyEvent.KEYCODE_MEDIA_PLAY - || keyCode == KeyEvent.KEYCODE_MEDIA_PAUSE - || keyCode == KeyEvent.KEYCODE_MEDIA_NEXT - || keyCode == KeyEvent.KEYCODE_MEDIA_PREVIOUS; - } - - private final class ComponentListener implements ExoPlayer.EventListener, - SeekBar.OnSeekBarChangeListener, OnClickListener { - - @Override - public void onStartTrackingTouch(SeekBar seekBar) { - removeCallbacks(hideAction); - dragging = true; - } - - @Override - public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { - if (fromUser) { - long position = positionValue(progress); - if (positionView != null) { - positionView.setText(stringForTime(position)); - } - if (player != null && !dragging) { - seekTo(position); - } - } - } - - @Override - public void onStopTrackingTouch(SeekBar seekBar) { - dragging = false; - if (player != null) { - seekTo(positionValue(seekBar.getProgress())); - } - hideAfterTimeout(); - } - - @Override - public void onPlayerStateChanged(boolean playWhenReady, int playbackState) { - updatePlayPauseButton(); - updateProgress(); - } - - @Override - public void onPositionDiscontinuity() { - updateNavigation(); - updateProgress(); - } - - @Override - public void onTimelineChanged(Timeline timeline, Object manifest) { - updateNavigation(); - updateProgress(); - } - - @Override - public void onLoadingChanged(boolean isLoading) { - // Do nothing. - } - - @Override - public void onTracksChanged(TrackGroupArray tracks, TrackSelectionArray selections) { - // Do nothing. - } - - @Override - public void onPlayerError(ExoPlaybackException error) { - // Do nothing. - } - - @Override - public void onClick(View view) { - if (player != null) { - if (nextButton == view) { - next(); - } else if (previousButton == view) { - previous(); - } else if (fastForwardButton == view) { - fastForward(); - } else if (rewindButton == view) { - rewind(); - } else if (playButton == view) { - player.setPlayWhenReady(true); - } else if (pauseButton == view) { - player.setPlayWhenReady(false); - } - } - hideAfterTimeout(); - } - - } - -} diff --git a/library/core/src/main/java/com/google/android/exoplayer2/ui/SimpleExoPlayerView.java b/library/core/src/main/java/com/google/android/exoplayer2/ui/SimpleExoPlayerView.java deleted file mode 100644 index 2dfb165960..0000000000 --- a/library/core/src/main/java/com/google/android/exoplayer2/ui/SimpleExoPlayerView.java +++ /dev/null @@ -1,718 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.google.android.exoplayer2.ui; - -import android.annotation.TargetApi; -import android.content.Context; -import android.content.res.TypedArray; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.util.AttributeSet; -import android.view.KeyEvent; -import android.view.LayoutInflater; -import android.view.MotionEvent; -import android.view.SurfaceView; -import android.view.TextureView; -import android.view.View; -import android.view.ViewGroup; -import android.widget.FrameLayout; -import android.widget.ImageView; -import com.google.android.exoplayer2.C; -import com.google.android.exoplayer2.ExoPlaybackException; -import com.google.android.exoplayer2.ExoPlayer; -import com.google.android.exoplayer2.SimpleExoPlayer; -import com.google.android.exoplayer2.Timeline; -import com.google.android.exoplayer2.core.R; -import com.google.android.exoplayer2.metadata.Metadata; -import com.google.android.exoplayer2.metadata.id3.ApicFrame; -import com.google.android.exoplayer2.source.TrackGroupArray; -import com.google.android.exoplayer2.text.Cue; -import com.google.android.exoplayer2.text.TextRenderer; -import com.google.android.exoplayer2.trackselection.TrackSelection; -import com.google.android.exoplayer2.trackselection.TrackSelectionArray; -import com.google.android.exoplayer2.ui.AspectRatioFrameLayout.ResizeMode; -import com.google.android.exoplayer2.ui.PlaybackControlView.SeekDispatcher; -import com.google.android.exoplayer2.util.Assertions; -import java.util.List; - -/** - * A high level view for {@link SimpleExoPlayer} media playbacks. It displays video, subtitles and - * album art during playback, and displays playback controls using a {@link PlaybackControlView}. - *

- * A SimpleExoPlayerView can be customized by setting attributes (or calling corresponding methods), - * overriding the view's layout file or by specifying a custom view layout file, as outlined below. - * - *

Attributes

- * The following attributes can be set on a SimpleExoPlayerView when used in a layout XML file: - *

- *

    - *
  • {@code use_artwork} - Whether artwork is used if available in audio streams. - *
      - *
    • Corresponding method: {@link #setUseArtwork(boolean)}
    • - *
    • Default: {@code true}
    • - *
    - *
  • - *
  • {@code default_artwork} - Default artwork to use if no artwork available in audio - * streams. - *
      - *
    • Corresponding method: {@link #setDefaultArtwork(Bitmap)}
    • - *
    • Default: {@code null}
    • - *
    - *
  • - *
  • {@code use_controller} - Whether playback controls are displayed. - *
      - *
    • Corresponding method: {@link #setUseController(boolean)}
    • - *
    • Default: {@code true}
    • - *
    - *
  • - *
  • {@code resize_mode} - Controls how video and album art is resized within the view. - * Valid values are {@code fit}, {@code fixed_width}, {@code fixed_height} and {@code fill}. - *
      - *
    • Corresponding method: {@link #setResizeMode(int)}
    • - *
    • Default: {@code fit}
    • - *
    - *
  • - *
  • {@code surface_type} - The type of surface view used for video playbacks. Valid - * values are {@code surface_view}, {@code texture_view} and {@code none}. Using {@code none} - * is recommended for audio only applications, since creating the surface can be expensive. - * Using {@code surface_view} is recommended for video applications. - *
      - *
    • Corresponding method: None
    • - *
    • Default: {@code surface_view}
    • - *
    - *
  • - *
  • {@code player_layout_id} - Specifies the id of the layout to be inflated. See below - * for more details. - *
      - *
    • Corresponding method: None
    • - *
    • Default: {@code R.id.exo_simple_player_view}
    • - *
    - *
  • {@code controller_layout_id} - Specifies the id of the layout resource to be - * inflated by the child {@link PlaybackControlView}. See below for more details. - *
      - *
    • Corresponding method: None
    • - *
    • Default: {@code R.id.exo_playback_control_view}
    • - *
    - *
  • All attributes that can be set on a {@link PlaybackControlView} can also be set on a - * SimpleExoPlayerView, and will be propagated to the inflated {@link PlaybackControlView}. - *
  • - *
- * - *

Overriding the layout file

- * To customize the layout of SimpleExoPlayerView throughout your app, or just for certain - * configurations, you can define {@code exo_simple_player_view.xml} layout files in your - * application {@code res/layout*} directories. These layouts will override the one provided by the - * ExoPlayer library, and will be inflated for use by SimpleExoPlayerView. The view identifies and - * binds its children by looking for the following ids: - *

- *

    - *
  • {@code exo_content_frame} - A frame whose aspect ratio is resized based on the video - * or album art of the media being played, and the configured {@code resize_mode}. The video - * surface view is inflated into this frame as its first child. - *
      - *
    • Type: {@link AspectRatioFrameLayout}
    • - *
    - *
  • - *
  • {@code exo_shutter} - A view that's made visible when video should be hidden. This - * view is typically an opaque view that covers the video surface view, thereby obscuring it - * when visible. - *
      - *
    • Type: {@link View}
    • - *
    - *
  • - *
  • {@code exo_subtitles} - Displays subtitles. - *
      - *
    • Type: {@link SubtitleView}
    • - *
    - *
  • - *
  • {@code exo_artwork} - Displays album art. - *
      - *
    • Type: {@link ImageView}
    • - *
    - *
  • - *
  • {@code exo_controller_placeholder} - A placeholder that's replaced with the inflated - * {@link PlaybackControlView}. - *
      - *
    • Type: {@link View}
    • - *
    - *
  • - *
  • {@code exo_overlay} - A {@link FrameLayout} positioned on top of the player which - * the app can access via {@link #getOverlayFrameLayout()}, provided for convenience. - *
      - *
    • Type: {@link FrameLayout}
    • - *
    - *
  • - *
- *

- * All child views are optional and so can be omitted if not required, however where defined they - * must be of the expected type. - * - *

Specifying a custom layout file

- * Defining your own {@code exo_simple_player_view.xml} is useful to customize the layout of - * SimpleExoPlayerView throughout your application. It's also possible to customize the layout for a - * single instance in a layout file. This is achieved by setting the {@code player_layout_id} - * attribute on a SimpleExoPlayerView. This will cause the specified layout to be inflated instead - * of {@code exo_simple_player_view.xml} for only the instance on which the attribute is set. - */ -@TargetApi(16) -public final class SimpleExoPlayerView extends FrameLayout { - - private static final int SURFACE_TYPE_NONE = 0; - private static final int SURFACE_TYPE_SURFACE_VIEW = 1; - private static final int SURFACE_TYPE_TEXTURE_VIEW = 2; - - private final AspectRatioFrameLayout contentFrame; - private final View shutterView; - private final View surfaceView; - private final ImageView artworkView; - private final SubtitleView subtitleView; - private final PlaybackControlView controller; - private final ComponentListener componentListener; - private final FrameLayout overlayFrameLayout; - - private SimpleExoPlayer player; - private boolean useController; - private boolean useArtwork; - private Bitmap defaultArtwork; - private int controllerShowTimeoutMs; - - public SimpleExoPlayerView(Context context) { - this(context, null); - } - - public SimpleExoPlayerView(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } - - public SimpleExoPlayerView(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - - int playerLayoutId = R.layout.exo_simple_player_view; - boolean useArtwork = true; - int defaultArtworkId = 0; - boolean useController = true; - int surfaceType = SURFACE_TYPE_SURFACE_VIEW; - int resizeMode = AspectRatioFrameLayout.RESIZE_MODE_FIT; - int controllerShowTimeoutMs = PlaybackControlView.DEFAULT_SHOW_TIMEOUT_MS; - if (attrs != null) { - TypedArray a = context.getTheme().obtainStyledAttributes(attrs, - R.styleable.SimpleExoPlayerView, 0, 0); - try { - playerLayoutId = a.getResourceId(R.styleable.SimpleExoPlayerView_player_layout_id, - playerLayoutId); - useArtwork = a.getBoolean(R.styleable.SimpleExoPlayerView_use_artwork, useArtwork); - defaultArtworkId = a.getResourceId(R.styleable.SimpleExoPlayerView_default_artwork, - defaultArtworkId); - useController = a.getBoolean(R.styleable.SimpleExoPlayerView_use_controller, useController); - surfaceType = a.getInt(R.styleable.SimpleExoPlayerView_surface_type, surfaceType); - resizeMode = a.getInt(R.styleable.SimpleExoPlayerView_resize_mode, resizeMode); - controllerShowTimeoutMs = a.getInt(R.styleable.SimpleExoPlayerView_show_timeout, - controllerShowTimeoutMs); - } finally { - a.recycle(); - } - } - - LayoutInflater.from(context).inflate(playerLayoutId, this); - componentListener = new ComponentListener(); - setDescendantFocusability(FOCUS_AFTER_DESCENDANTS); - - // Content frame. - contentFrame = (AspectRatioFrameLayout) findViewById(R.id.exo_content_frame); - if (contentFrame != null) { - setResizeModeRaw(contentFrame, resizeMode); - } - - // Shutter view. - shutterView = findViewById(R.id.exo_shutter); - - // Create a surface view and insert it into the content frame, if there is one. - if (contentFrame != null && surfaceType != SURFACE_TYPE_NONE) { - ViewGroup.LayoutParams params = new ViewGroup.LayoutParams( - ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); - surfaceView = surfaceType == SURFACE_TYPE_TEXTURE_VIEW ? new TextureView(context) - : new SurfaceView(context); - surfaceView.setLayoutParams(params); - contentFrame.addView(surfaceView, 0); - } else { - surfaceView = null; - } - - // Overlay frame layout. - overlayFrameLayout = (FrameLayout) findViewById(R.id.exo_overlay); - - // Artwork view. - artworkView = (ImageView) findViewById(R.id.exo_artwork); - this.useArtwork = useArtwork && artworkView != null; - if (defaultArtworkId != 0) { - defaultArtwork = BitmapFactory.decodeResource(context.getResources(), defaultArtworkId); - } - - // Subtitle view. - subtitleView = (SubtitleView) findViewById(R.id.exo_subtitles); - if (subtitleView != null) { - subtitleView.setUserDefaultStyle(); - subtitleView.setUserDefaultTextSize(); - } - - // Playback control view. - View controllerPlaceholder = findViewById(R.id.exo_controller_placeholder); - if (controllerPlaceholder != null) { - // Note: rewindMs and fastForwardMs are passed via attrs, so we don't need to make explicit - // calls to set them. - this.controller = new PlaybackControlView(context, attrs); - controller.setLayoutParams(controllerPlaceholder.getLayoutParams()); - ViewGroup parent = ((ViewGroup) controllerPlaceholder.getParent()); - int controllerIndex = parent.indexOfChild(controllerPlaceholder); - parent.removeView(controllerPlaceholder); - parent.addView(controller, controllerIndex); - } else { - this.controller = null; - } - this.controllerShowTimeoutMs = controller != null ? controllerShowTimeoutMs : 0; - this.useController = useController && controller != null; - hideController(); - } - - /** - * Returns the player currently set on this view, or null if no player is set. - */ - public SimpleExoPlayer getPlayer() { - return player; - } - - /** - * Set the {@link SimpleExoPlayer} to use. The {@link SimpleExoPlayer#setTextOutput} and - * {@link SimpleExoPlayer#setVideoListener} method of the player will be called and previous - * assignments are overridden. - * - * @param player The {@link SimpleExoPlayer} to use. - */ - public void setPlayer(SimpleExoPlayer player) { - if (this.player == player) { - return; - } - if (this.player != null) { - this.player.setTextOutput(null); - this.player.setVideoListener(null); - this.player.removeListener(componentListener); - this.player.setVideoSurface(null); - } - this.player = player; - if (useController) { - controller.setPlayer(player); - } - if (shutterView != null) { - shutterView.setVisibility(VISIBLE); - } - if (player != null) { - if (surfaceView instanceof TextureView) { - player.setVideoTextureView((TextureView) surfaceView); - } else if (surfaceView instanceof SurfaceView) { - player.setVideoSurfaceView((SurfaceView) surfaceView); - } - player.setVideoListener(componentListener); - player.addListener(componentListener); - player.setTextOutput(componentListener); - maybeShowController(false); - updateForCurrentTrackSelections(); - } else { - hideController(); - hideArtwork(); - } - } - - /** - * Sets the resize mode. - * - * @param resizeMode The resize mode. - */ - public void setResizeMode(@ResizeMode int resizeMode) { - Assertions.checkState(contentFrame != null); - contentFrame.setResizeMode(resizeMode); - } - - /** - * Returns whether artwork is displayed if present in the media. - */ - public boolean getUseArtwork() { - return useArtwork; - } - - /** - * Sets whether artwork is displayed if present in the media. - * - * @param useArtwork Whether artwork is displayed. - */ - public void setUseArtwork(boolean useArtwork) { - Assertions.checkState(!useArtwork || artworkView != null); - if (this.useArtwork != useArtwork) { - this.useArtwork = useArtwork; - updateForCurrentTrackSelections(); - } - } - - /** - * Returns the default artwork to display. - */ - public Bitmap getDefaultArtwork() { - return defaultArtwork; - } - - /** - * Sets the default artwork to display if {@code useArtwork} is {@code true} and no artwork is - * present in the media. - * - * @param defaultArtwork the default artwork to display. - */ - public void setDefaultArtwork(Bitmap defaultArtwork) { - if (this.defaultArtwork != defaultArtwork) { - this.defaultArtwork = defaultArtwork; - updateForCurrentTrackSelections(); - } - } - - /** - * Returns whether the playback controls are enabled. - */ - public boolean getUseController() { - return useController; - } - - /** - * Sets whether playback controls are enabled. If set to {@code false} the playback controls are - * never visible and are disconnected from the player. - * - * @param useController Whether playback controls should be enabled. - */ - public void setUseController(boolean useController) { - Assertions.checkState(!useController || controller != null); - if (this.useController == useController) { - return; - } - this.useController = useController; - if (useController) { - controller.setPlayer(player); - } else if (controller != null) { - controller.hide(); - controller.setPlayer(null); - } - } - - /** - * Called to process media key events. Any {@link KeyEvent} can be passed but only media key - * events will be handled. Does nothing if playback controls are disabled. - * - * @param event A key event. - * @return Whether the key event was handled. - */ - public boolean dispatchMediaKeyEvent(KeyEvent event) { - return useController && controller.dispatchMediaKeyEvent(event); - } - - /** - * Shows the playback controls. Does nothing if playback controls are disabled. - */ - public void showController() { - if (useController) { - maybeShowController(true); - } - } - - /** - * Hides the playback controls. Does nothing if playback controls are disabled. - */ - public void hideController() { - if (controller != null) { - controller.hide(); - } - } - - /** - * Returns the playback controls timeout. The playback controls are automatically hidden after - * this duration of time has elapsed without user input and with playback or buffering in - * progress. - * - * @return The timeout in milliseconds. A non-positive value will cause the controller to remain - * visible indefinitely. - */ - public int getControllerShowTimeoutMs() { - return controllerShowTimeoutMs; - } - - /** - * Sets the playback controls timeout. The playback controls are automatically hidden after this - * duration of time has elapsed without user input and with playback or buffering in progress. - * - * @param controllerShowTimeoutMs The timeout in milliseconds. A non-positive value will cause - * the controller to remain visible indefinitely. - */ - public void setControllerShowTimeoutMs(int controllerShowTimeoutMs) { - Assertions.checkState(controller != null); - this.controllerShowTimeoutMs = controllerShowTimeoutMs; - } - - /** - * Set the {@link PlaybackControlView.VisibilityListener}. - * - * @param listener The listener to be notified about visibility changes. - */ - public void setControllerVisibilityListener(PlaybackControlView.VisibilityListener listener) { - Assertions.checkState(controller != null); - controller.setVisibilityListener(listener); - } - - /** - * Sets the {@link SeekDispatcher}. - * - * @param seekDispatcher The {@link SeekDispatcher}, or null to use - * {@link PlaybackControlView#DEFAULT_SEEK_DISPATCHER}. - */ - public void setSeekDispatcher(SeekDispatcher seekDispatcher) { - Assertions.checkState(controller != null); - controller.setSeekDispatcher(seekDispatcher); - } - - /** - * Sets the rewind increment in milliseconds. - * - * @param rewindMs The rewind increment in milliseconds. - */ - public void setRewindIncrementMs(int rewindMs) { - Assertions.checkState(controller != null); - controller.setRewindIncrementMs(rewindMs); - } - - /** - * Sets the fast forward increment in milliseconds. - * - * @param fastForwardMs The fast forward increment in milliseconds. - */ - public void setFastForwardIncrementMs(int fastForwardMs) { - Assertions.checkState(controller != null); - controller.setFastForwardIncrementMs(fastForwardMs); - } - - /** - * Gets the view onto which video is rendered. This is either a {@link SurfaceView} (default) - * or a {@link TextureView} if the {@code use_texture_view} view attribute has been set to true. - * - * @return Either a {@link SurfaceView} or a {@link TextureView}. - */ - public View getVideoSurfaceView() { - return surfaceView; - } - - /** - * Gets the overlay {@link FrameLayout}, which can be populated with UI elements to show on top of - * the player. - * - * @return The overlay {@link FrameLayout}, or {@code null} if the layout has been customized and - * the overlay is not present. - */ - public FrameLayout getOverlayFrameLayout() { - return overlayFrameLayout; - } - - /** - * Gets the {@link SubtitleView}. - * - * @return The {@link SubtitleView}, or {@code null} if the layout has been customized and the - * subtitle view is not present. - */ - public SubtitleView getSubtitleView() { - return subtitleView; - } - - @Override - public boolean onTouchEvent(MotionEvent ev) { - if (!useController || player == null || ev.getActionMasked() != MotionEvent.ACTION_DOWN) { - return false; - } - if (controller.isVisible()) { - controller.hide(); - } else { - maybeShowController(true); - } - return true; - } - - @Override - public boolean onTrackballEvent(MotionEvent ev) { - if (!useController || player == null) { - return false; - } - maybeShowController(true); - return true; - } - - private void maybeShowController(boolean isForced) { - if (!useController || player == null) { - return; - } - int playbackState = player.getPlaybackState(); - boolean showIndefinitely = playbackState == ExoPlayer.STATE_IDLE - || playbackState == ExoPlayer.STATE_ENDED || !player.getPlayWhenReady(); - boolean wasShowingIndefinitely = controller.isVisible() && controller.getShowTimeoutMs() <= 0; - controller.setShowTimeoutMs(showIndefinitely ? 0 : controllerShowTimeoutMs); - if (isForced || showIndefinitely || wasShowingIndefinitely) { - controller.show(); - } - } - - private void updateForCurrentTrackSelections() { - if (player == null) { - return; - } - TrackSelectionArray selections = player.getCurrentTrackSelections(); - for (int i = 0; i < selections.length; i++) { - if (player.getRendererType(i) == C.TRACK_TYPE_VIDEO && selections.get(i) != null) { - // Video enabled so artwork must be hidden. If the shutter is closed, it will be opened in - // onRenderedFirstFrame(). - hideArtwork(); - return; - } - } - // Video disabled so the shutter must be closed. - if (shutterView != null) { - shutterView.setVisibility(VISIBLE); - } - // Display artwork if enabled and available, else hide it. - if (useArtwork) { - for (int i = 0; i < selections.length; i++) { - TrackSelection selection = selections.get(i); - if (selection != null) { - for (int j = 0; j < selection.length(); j++) { - Metadata metadata = selection.getFormat(j).metadata; - if (metadata != null && setArtworkFromMetadata(metadata)) { - return; - } - } - } - } - if (setArtworkFromBitmap(defaultArtwork)) { - return; - } - } - // Artwork disabled or unavailable. - hideArtwork(); - } - - private boolean setArtworkFromMetadata(Metadata metadata) { - for (int i = 0; i < metadata.length(); i++) { - Metadata.Entry metadataEntry = metadata.get(i); - if (metadataEntry instanceof ApicFrame) { - byte[] bitmapData = ((ApicFrame) metadataEntry).pictureData; - Bitmap bitmap = BitmapFactory.decodeByteArray(bitmapData, 0, bitmapData.length); - return setArtworkFromBitmap(bitmap); - } - } - return false; - } - - private boolean setArtworkFromBitmap(Bitmap bitmap) { - if (bitmap != null) { - int bitmapWidth = bitmap.getWidth(); - int bitmapHeight = bitmap.getHeight(); - if (bitmapWidth > 0 && bitmapHeight > 0) { - if (contentFrame != null) { - contentFrame.setAspectRatio((float) bitmapWidth / bitmapHeight); - } - artworkView.setImageBitmap(bitmap); - artworkView.setVisibility(VISIBLE); - return true; - } - } - return false; - } - - private void hideArtwork() { - if (artworkView != null) { - artworkView.setImageResource(android.R.color.transparent); // Clears any bitmap reference. - artworkView.setVisibility(INVISIBLE); - } - } - - @SuppressWarnings("ResourceType") - private static void setResizeModeRaw(AspectRatioFrameLayout aspectRatioFrame, int resizeMode) { - aspectRatioFrame.setResizeMode(resizeMode); - } - - private final class ComponentListener implements SimpleExoPlayer.VideoListener, - TextRenderer.Output, ExoPlayer.EventListener { - - // TextRenderer.Output implementation - - @Override - public void onCues(List cues) { - if (subtitleView != null) { - subtitleView.onCues(cues); - } - } - - // SimpleExoPlayer.VideoListener implementation - - @Override - public void onVideoSizeChanged(int width, int height, int unappliedRotationDegrees, - float pixelWidthHeightRatio) { - if (contentFrame != null) { - float aspectRatio = height == 0 ? 1 : (width * pixelWidthHeightRatio) / height; - contentFrame.setAspectRatio(aspectRatio); - } - } - - @Override - public void onRenderedFirstFrame() { - if (shutterView != null) { - shutterView.setVisibility(INVISIBLE); - } - } - - @Override - public void onTracksChanged(TrackGroupArray tracks, TrackSelectionArray selections) { - updateForCurrentTrackSelections(); - } - - // ExoPlayer.EventListener implementation - - @Override - public void onLoadingChanged(boolean isLoading) { - // Do nothing. - } - - @Override - public void onPlayerStateChanged(boolean playWhenReady, int playbackState) { - maybeShowController(false); - } - - @Override - public void onPlayerError(ExoPlaybackException e) { - // Do nothing. - } - - @Override - public void onPositionDiscontinuity() { - // Do nothing. - } - - @Override - public void onTimelineChanged(Timeline timeline, Object manifest) { - // Do nothing. - } - - } - -} diff --git a/library/core/src/main/java/com/google/android/exoplayer2/ui/SubtitlePainter.java b/library/core/src/main/java/com/google/android/exoplayer2/ui/SubtitlePainter.java deleted file mode 100644 index d4f09b1721..0000000000 --- a/library/core/src/main/java/com/google/android/exoplayer2/ui/SubtitlePainter.java +++ /dev/null @@ -1,405 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.google.android.exoplayer2.ui; - -import android.content.Context; -import android.content.res.Resources; -import android.content.res.TypedArray; -import android.graphics.Bitmap; -import android.graphics.Canvas; -import android.graphics.Color; -import android.graphics.Paint; -import android.graphics.Paint.Join; -import android.graphics.Paint.Style; -import android.graphics.Rect; -import android.graphics.RectF; -import android.text.Layout.Alignment; -import android.text.StaticLayout; -import android.text.TextPaint; -import android.text.TextUtils; -import android.util.DisplayMetrics; -import android.util.Log; -import com.google.android.exoplayer2.text.CaptionStyleCompat; -import com.google.android.exoplayer2.text.Cue; -import com.google.android.exoplayer2.util.Util; - -/** - * Paints subtitle {@link Cue}s. - */ -/* package */ final class SubtitlePainter { - - private static final String TAG = "SubtitlePainter"; - - /** - * Ratio of inner padding to font size. - */ - private static final float INNER_PADDING_RATIO = 0.125f; - - /** - * Temporary rectangle used for computing line bounds. - */ - private final RectF lineBounds = new RectF(); - - // Styled dimensions. - private final float cornerRadius; - private final float outlineWidth; - private final float shadowRadius; - private final float shadowOffset; - private final float spacingMult; - private final float spacingAdd; - - private final TextPaint textPaint; - private final Paint paint; - - // Previous input variables. - private CharSequence cueText; - private Alignment cueTextAlignment; - private Bitmap cueBitmap; - private float cueLine; - @Cue.LineType - private int cueLineType; - @Cue.AnchorType - private int cueLineAnchor; - private float cuePosition; - @Cue.AnchorType - private int cuePositionAnchor; - private float cueSize; - private boolean applyEmbeddedStyles; - private int foregroundColor; - private int backgroundColor; - private int windowColor; - private int edgeColor; - @CaptionStyleCompat.EdgeType - private int edgeType; - private float textSizePx; - private float bottomPaddingFraction; - private int parentLeft; - private int parentTop; - private int parentRight; - private int parentBottom; - - // Derived drawing variables. - private StaticLayout textLayout; - private int textLeft; - private int textTop; - private int textPaddingX; - private Rect bitmapRect; - - @SuppressWarnings("ResourceType") - public SubtitlePainter(Context context) { - int[] viewAttr = {android.R.attr.lineSpacingExtra, android.R.attr.lineSpacingMultiplier}; - TypedArray styledAttributes = context.obtainStyledAttributes(null, viewAttr, 0, 0); - spacingAdd = styledAttributes.getDimensionPixelSize(0, 0); - spacingMult = styledAttributes.getFloat(1, 1); - styledAttributes.recycle(); - - Resources resources = context.getResources(); - DisplayMetrics displayMetrics = resources.getDisplayMetrics(); - int twoDpInPx = Math.round((2f * displayMetrics.densityDpi) / DisplayMetrics.DENSITY_DEFAULT); - cornerRadius = twoDpInPx; - outlineWidth = twoDpInPx; - shadowRadius = twoDpInPx; - shadowOffset = twoDpInPx; - - textPaint = new TextPaint(); - textPaint.setAntiAlias(true); - textPaint.setSubpixelText(true); - - paint = new Paint(); - paint.setAntiAlias(true); - paint.setStyle(Style.FILL); - } - - /** - * Draws the provided {@link Cue} into a canvas with the specified styling. - *

- * A call to this method is able to use cached results of calculations made during the previous - * call, and so an instance of this class is able to optimize repeated calls to this method in - * which the same parameters are passed. - * - * @param cue The cue to draw. - * @param applyEmbeddedStyles Whether styling embedded within the cue should be applied. - * @param style The style to use when drawing the cue text. - * @param textSizePx The text size to use when drawing the cue text, in pixels. - * @param bottomPaddingFraction The bottom padding fraction to apply when {@link Cue#line} is - * {@link Cue#DIMEN_UNSET}, as a fraction of the viewport height - * @param canvas The canvas into which to draw. - * @param cueBoxLeft The left position of the enclosing cue box. - * @param cueBoxTop The top position of the enclosing cue box. - * @param cueBoxRight The right position of the enclosing cue box. - * @param cueBoxBottom The bottom position of the enclosing cue box. - */ - public void draw(Cue cue, boolean applyEmbeddedStyles, CaptionStyleCompat style, float textSizePx, - float bottomPaddingFraction, Canvas canvas, int cueBoxLeft, int cueBoxTop, int cueBoxRight, - int cueBoxBottom) { - boolean isTextCue = cue.bitmap == null; - CharSequence cueText = null; - Bitmap cueBitmap = null; - int windowColor = Color.BLACK; - if (isTextCue) { - cueText = cue.text; - if (TextUtils.isEmpty(cueText)) { - // Nothing to draw. - return; - } - windowColor = cue.windowColorSet ? cue.windowColor : style.windowColor; - if (!applyEmbeddedStyles) { - // Strip out any embedded styling. - cueText = cueText.toString(); - windowColor = style.windowColor; - } - } else { - cueBitmap = cue.bitmap; - } - if (areCharSequencesEqual(this.cueText, cueText) - && Util.areEqual(this.cueTextAlignment, cue.textAlignment) - && this.cueBitmap == cueBitmap - && this.cueLine == cue.line - && this.cueLineType == cue.lineType - && Util.areEqual(this.cueLineAnchor, cue.lineAnchor) - && this.cuePosition == cue.position - && Util.areEqual(this.cuePositionAnchor, cue.positionAnchor) - && this.cueSize == cue.size - && this.applyEmbeddedStyles == applyEmbeddedStyles - && this.foregroundColor == style.foregroundColor - && this.backgroundColor == style.backgroundColor - && this.windowColor == windowColor - && this.edgeType == style.edgeType - && this.edgeColor == style.edgeColor - && Util.areEqual(this.textPaint.getTypeface(), style.typeface) - && this.textSizePx == textSizePx - && this.bottomPaddingFraction == bottomPaddingFraction - && this.parentLeft == cueBoxLeft - && this.parentTop == cueBoxTop - && this.parentRight == cueBoxRight - && this.parentBottom == cueBoxBottom) { - // We can use the cached layout. - drawLayout(canvas, isTextCue); - return; - } - - this.cueText = cueText; - this.cueTextAlignment = cue.textAlignment; - this.cueBitmap = cueBitmap; - this.cueLine = cue.line; - this.cueLineType = cue.lineType; - this.cueLineAnchor = cue.lineAnchor; - this.cuePosition = cue.position; - this.cuePositionAnchor = cue.positionAnchor; - this.cueSize = cue.size; - this.applyEmbeddedStyles = applyEmbeddedStyles; - this.foregroundColor = style.foregroundColor; - this.backgroundColor = style.backgroundColor; - this.windowColor = windowColor; - this.edgeType = style.edgeType; - this.edgeColor = style.edgeColor; - this.textPaint.setTypeface(style.typeface); - this.textSizePx = textSizePx; - this.bottomPaddingFraction = bottomPaddingFraction; - this.parentLeft = cueBoxLeft; - this.parentTop = cueBoxTop; - this.parentRight = cueBoxRight; - this.parentBottom = cueBoxBottom; - - if (isTextCue) { - setupTextLayout(); - } else { - setupBitmapLayout(); - } - drawLayout(canvas, isTextCue); - } - - private void setupTextLayout() { - int parentWidth = parentRight - parentLeft; - int parentHeight = parentBottom - parentTop; - - textPaint.setTextSize(textSizePx); - int textPaddingX = (int) (textSizePx * INNER_PADDING_RATIO + 0.5f); - - int availableWidth = parentWidth - textPaddingX * 2; - if (cueSize != Cue.DIMEN_UNSET) { - availableWidth = (int) (availableWidth * cueSize); - } - if (availableWidth <= 0) { - Log.w(TAG, "Skipped drawing subtitle cue (insufficient space)"); - return; - } - - Alignment textAlignment = cueTextAlignment == null ? Alignment.ALIGN_CENTER : cueTextAlignment; - textLayout = new StaticLayout(cueText, textPaint, availableWidth, textAlignment, spacingMult, - spacingAdd, true); - int textHeight = textLayout.getHeight(); - int textWidth = 0; - int lineCount = textLayout.getLineCount(); - for (int i = 0; i < lineCount; i++) { - textWidth = Math.max((int) Math.ceil(textLayout.getLineWidth(i)), textWidth); - } - if (cueSize != Cue.DIMEN_UNSET && textWidth < availableWidth) { - textWidth = availableWidth; - } - textWidth += textPaddingX * 2; - - int textLeft; - int textRight; - if (cuePosition != Cue.DIMEN_UNSET) { - int anchorPosition = Math.round(parentWidth * cuePosition) + parentLeft; - textLeft = cuePositionAnchor == Cue.ANCHOR_TYPE_END ? anchorPosition - textWidth - : cuePositionAnchor == Cue.ANCHOR_TYPE_MIDDLE ? (anchorPosition * 2 - textWidth) / 2 - : anchorPosition; - textLeft = Math.max(textLeft, parentLeft); - textRight = Math.min(textLeft + textWidth, parentRight); - } else { - textLeft = (parentWidth - textWidth) / 2; - textRight = textLeft + textWidth; - } - - textWidth = textRight - textLeft; - if (textWidth <= 0) { - Log.w(TAG, "Skipped drawing subtitle cue (invalid horizontal positioning)"); - return; - } - - int textTop; - if (cueLine != Cue.DIMEN_UNSET) { - int anchorPosition; - if (cueLineType == Cue.LINE_TYPE_FRACTION) { - anchorPosition = Math.round(parentHeight * cueLine) + parentTop; - } else { - // cueLineType == Cue.LINE_TYPE_NUMBER - int firstLineHeight = textLayout.getLineBottom(0) - textLayout.getLineTop(0); - if (cueLine >= 0) { - anchorPosition = Math.round(cueLine * firstLineHeight) + parentTop; - } else { - anchorPosition = Math.round((cueLine + 1) * firstLineHeight) + parentBottom; - } - } - textTop = cueLineAnchor == Cue.ANCHOR_TYPE_END ? anchorPosition - textHeight - : cueLineAnchor == Cue.ANCHOR_TYPE_MIDDLE ? (anchorPosition * 2 - textHeight) / 2 - : anchorPosition; - if (textTop + textHeight > parentBottom) { - textTop = parentBottom - textHeight; - } else if (textTop < parentTop) { - textTop = parentTop; - } - } else { - textTop = parentBottom - textHeight - (int) (parentHeight * bottomPaddingFraction); - } - - // Update the derived drawing variables. - this.textLayout = new StaticLayout(cueText, textPaint, textWidth, textAlignment, spacingMult, - spacingAdd, true); - this.textLeft = textLeft; - this.textTop = textTop; - this.textPaddingX = textPaddingX; - } - - private void setupBitmapLayout() { - int parentWidth = parentRight - parentLeft; - int parentHeight = parentBottom - parentTop; - float anchorX = parentLeft + (parentWidth * cuePosition); - float anchorY = parentTop + (parentHeight * cueLine); - int width = Math.round(parentWidth * cueSize); - int height = Math.round(width * ((float) cueBitmap.getHeight() / cueBitmap.getWidth())); - int x = Math.round(cueLineAnchor == Cue.ANCHOR_TYPE_END ? (anchorX - width) - : cueLineAnchor == Cue.ANCHOR_TYPE_MIDDLE ? (anchorX - (width / 2)) : anchorX); - int y = Math.round(cuePositionAnchor == Cue.ANCHOR_TYPE_END ? (anchorY - height) - : cuePositionAnchor == Cue.ANCHOR_TYPE_MIDDLE ? (anchorY - (height / 2)) : anchorY); - bitmapRect = new Rect(x, y, x + width, y + height); - } - - private void drawLayout(Canvas canvas, boolean isTextCue) { - if (isTextCue) { - drawTextLayout(canvas); - } else { - drawBitmapLayout(canvas); - } - } - - private void drawTextLayout(Canvas canvas) { - StaticLayout layout = textLayout; - if (layout == null) { - // Nothing to draw. - return; - } - - int saveCount = canvas.save(); - canvas.translate(textLeft, textTop); - - if (Color.alpha(windowColor) > 0) { - paint.setColor(windowColor); - canvas.drawRect(-textPaddingX, 0, layout.getWidth() + textPaddingX, layout.getHeight(), - paint); - } - - if (Color.alpha(backgroundColor) > 0) { - paint.setColor(backgroundColor); - float previousBottom = layout.getLineTop(0); - int lineCount = layout.getLineCount(); - for (int i = 0; i < lineCount; i++) { - lineBounds.left = layout.getLineLeft(i) - textPaddingX; - lineBounds.right = layout.getLineRight(i) + textPaddingX; - lineBounds.top = previousBottom; - lineBounds.bottom = layout.getLineBottom(i); - previousBottom = lineBounds.bottom; - canvas.drawRoundRect(lineBounds, cornerRadius, cornerRadius, paint); - } - } - - if (edgeType == CaptionStyleCompat.EDGE_TYPE_OUTLINE) { - textPaint.setStrokeJoin(Join.ROUND); - textPaint.setStrokeWidth(outlineWidth); - textPaint.setColor(edgeColor); - textPaint.setStyle(Style.FILL_AND_STROKE); - layout.draw(canvas); - } else if (edgeType == CaptionStyleCompat.EDGE_TYPE_DROP_SHADOW) { - textPaint.setShadowLayer(shadowRadius, shadowOffset, shadowOffset, edgeColor); - } else if (edgeType == CaptionStyleCompat.EDGE_TYPE_RAISED - || edgeType == CaptionStyleCompat.EDGE_TYPE_DEPRESSED) { - boolean raised = edgeType == CaptionStyleCompat.EDGE_TYPE_RAISED; - int colorUp = raised ? Color.WHITE : edgeColor; - int colorDown = raised ? edgeColor : Color.WHITE; - float offset = shadowRadius / 2f; - textPaint.setColor(foregroundColor); - textPaint.setStyle(Style.FILL); - textPaint.setShadowLayer(shadowRadius, -offset, -offset, colorUp); - layout.draw(canvas); - textPaint.setShadowLayer(shadowRadius, offset, offset, colorDown); - } - - textPaint.setColor(foregroundColor); - textPaint.setStyle(Style.FILL); - layout.draw(canvas); - textPaint.setShadowLayer(0, 0, 0, 0); - - canvas.restoreToCount(saveCount); - } - - private void drawBitmapLayout(Canvas canvas) { - canvas.drawBitmap(cueBitmap, null, bitmapRect, null); - } - - /** - * This method is used instead of {@link TextUtils#equals(CharSequence, CharSequence)} because the - * latter only checks the text of each sequence, and does not check for equality of styling that - * may be embedded within the {@link CharSequence}s. - */ - private static boolean areCharSequencesEqual(CharSequence first, CharSequence second) { - // Some CharSequence implementations don't perform a cheap referential equality check in their - // equals methods, so we perform one explicitly here. - return first == second || (first != null && first.equals(second)); - } - -} diff --git a/library/core/src/main/java/com/google/android/exoplayer2/ui/SubtitleView.java b/library/core/src/main/java/com/google/android/exoplayer2/ui/SubtitleView.java deleted file mode 100644 index 49516ab6f4..0000000000 --- a/library/core/src/main/java/com/google/android/exoplayer2/ui/SubtitleView.java +++ /dev/null @@ -1,265 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.google.android.exoplayer2.ui; - -import android.annotation.TargetApi; -import android.content.Context; -import android.content.res.Resources; -import android.graphics.Canvas; -import android.util.AttributeSet; -import android.util.TypedValue; -import android.view.View; -import android.view.accessibility.CaptioningManager; -import com.google.android.exoplayer2.text.CaptionStyleCompat; -import com.google.android.exoplayer2.text.Cue; -import com.google.android.exoplayer2.text.TextRenderer; -import com.google.android.exoplayer2.util.Util; -import java.util.ArrayList; -import java.util.List; - -/** - * A view for displaying subtitle {@link Cue}s. - */ -public final class SubtitleView extends View implements TextRenderer.Output { - - /** - * The default fractional text size. - * - * @see #setFractionalTextSize(float, boolean) - */ - public static final float DEFAULT_TEXT_SIZE_FRACTION = 0.0533f; - - /** - * The default bottom padding to apply when {@link Cue#line} is {@link Cue#DIMEN_UNSET}, as a - * fraction of the viewport height. - * - * @see #setBottomPaddingFraction(float) - */ - public static final float DEFAULT_BOTTOM_PADDING_FRACTION = 0.08f; - - private static final int FRACTIONAL = 0; - private static final int FRACTIONAL_IGNORE_PADDING = 1; - private static final int ABSOLUTE = 2; - - private final List painters; - - private List cues; - private int textSizeType; - private float textSize; - private boolean applyEmbeddedStyles; - private CaptionStyleCompat style; - private float bottomPaddingFraction; - - public SubtitleView(Context context) { - this(context, null); - } - - public SubtitleView(Context context, AttributeSet attrs) { - super(context, attrs); - painters = new ArrayList<>(); - textSizeType = FRACTIONAL; - textSize = DEFAULT_TEXT_SIZE_FRACTION; - applyEmbeddedStyles = true; - style = CaptionStyleCompat.DEFAULT; - bottomPaddingFraction = DEFAULT_BOTTOM_PADDING_FRACTION; - } - - @Override - public void onCues(List cues) { - setCues(cues); - } - - /** - * Sets the cues to be displayed by the view. - * - * @param cues The cues to display. - */ - public void setCues(List cues) { - if (this.cues == cues) { - return; - } - this.cues = cues; - // Ensure we have sufficient painters. - int cueCount = (cues == null) ? 0 : cues.size(); - while (painters.size() < cueCount) { - painters.add(new SubtitlePainter(getContext())); - } - // Invalidate to trigger drawing. - invalidate(); - } - - /** - * Set the text size to a given unit and value. - *

- * See {@link TypedValue} for the possible dimension units. - * - * @param unit The desired dimension unit. - * @param size The desired size in the given units. - */ - public void setFixedTextSize(int unit, float size) { - Context context = getContext(); - Resources resources; - if (context == null) { - resources = Resources.getSystem(); - } else { - resources = context.getResources(); - } - setTextSize(ABSOLUTE, TypedValue.applyDimension(unit, size, resources.getDisplayMetrics())); - } - - /** - * Sets the text size to one derived from {@link CaptioningManager#getFontScale()}, or to a - * default size before API level 19. - */ - public void setUserDefaultTextSize() { - float fontScale = Util.SDK_INT >= 19 && !isInEditMode() ? getUserCaptionFontScaleV19() : 1f; - setFractionalTextSize(DEFAULT_TEXT_SIZE_FRACTION * fontScale); - } - - /** - * Sets the text size to be a fraction of the view's remaining height after its top and bottom - * padding have been subtracted. - *

- * Equivalent to {@code #setFractionalTextSize(fractionOfHeight, false)}. - * - * @param fractionOfHeight A fraction between 0 and 1. - */ - public void setFractionalTextSize(float fractionOfHeight) { - setFractionalTextSize(fractionOfHeight, false); - } - - /** - * Sets the text size to be a fraction of the height of this view. - * - * @param fractionOfHeight A fraction between 0 and 1. - * @param ignorePadding Set to true if {@code fractionOfHeight} should be interpreted as a - * fraction of this view's height ignoring any top and bottom padding. Set to false if - * {@code fractionOfHeight} should be interpreted as a fraction of this view's remaining - * height after the top and bottom padding has been subtracted. - */ - public void setFractionalTextSize(float fractionOfHeight, boolean ignorePadding) { - setTextSize(ignorePadding ? FRACTIONAL_IGNORE_PADDING : FRACTIONAL, fractionOfHeight); - } - - private void setTextSize(int textSizeType, float textSize) { - if (this.textSizeType == textSizeType && this.textSize == textSize) { - return; - } - this.textSizeType = textSizeType; - this.textSize = textSize; - // Invalidate to trigger drawing. - invalidate(); - } - - /** - * Sets whether styling embedded within the cues should be applied. Enabled by default. - * - * @param applyEmbeddedStyles Whether styling embedded within the cues should be applied. - */ - public void setApplyEmbeddedStyles(boolean applyEmbeddedStyles) { - if (this.applyEmbeddedStyles == applyEmbeddedStyles) { - return; - } - this.applyEmbeddedStyles = applyEmbeddedStyles; - // Invalidate to trigger drawing. - invalidate(); - } - - /** - * Sets the caption style to be equivalent to the one returned by - * {@link CaptioningManager#getUserStyle()}, or to a default style before API level 19. - */ - public void setUserDefaultStyle() { - setStyle(Util.SDK_INT >= 19 && !isInEditMode() - ? getUserCaptionStyleV19() : CaptionStyleCompat.DEFAULT); - } - - /** - * Sets the caption style. - * - * @param style A style for the view. - */ - public void setStyle(CaptionStyleCompat style) { - if (this.style == style) { - return; - } - this.style = style; - // Invalidate to trigger drawing. - invalidate(); - } - - /** - * Sets the bottom padding fraction to apply when {@link Cue#line} is {@link Cue#DIMEN_UNSET}, - * as a fraction of the view's remaining height after its top and bottom padding have been - * subtracted. - *

- * Note that this padding is applied in addition to any standard view padding. - * - * @param bottomPaddingFraction The bottom padding fraction. - */ - public void setBottomPaddingFraction(float bottomPaddingFraction) { - if (this.bottomPaddingFraction == bottomPaddingFraction) { - return; - } - this.bottomPaddingFraction = bottomPaddingFraction; - // Invalidate to trigger drawing. - invalidate(); - } - - @Override - public void dispatchDraw(Canvas canvas) { - int cueCount = (cues == null) ? 0 : cues.size(); - int rawTop = getTop(); - int rawBottom = getBottom(); - - // Calculate the bounds after padding is taken into account. - int left = getLeft() + getPaddingLeft(); - int top = rawTop + getPaddingTop(); - int right = getRight() + getPaddingRight(); - int bottom = rawBottom - getPaddingBottom(); - if (bottom <= top || right <= left) { - // No space to draw subtitles. - return; - } - - float textSizePx = textSizeType == ABSOLUTE ? textSize - : textSize * (textSizeType == FRACTIONAL ? (bottom - top) : (rawBottom - rawTop)); - if (textSizePx <= 0) { - // Text has no height. - return; - } - - for (int i = 0; i < cueCount; i++) { - painters.get(i).draw(cues.get(i), applyEmbeddedStyles, style, textSizePx, - bottomPaddingFraction, canvas, left, top, right, bottom); - } - } - - @TargetApi(19) - private float getUserCaptionFontScaleV19() { - CaptioningManager captioningManager = - (CaptioningManager) getContext().getSystemService(Context.CAPTIONING_SERVICE); - return captioningManager.getFontScale(); - } - - @TargetApi(19) - private CaptionStyleCompat getUserCaptionStyleV19() { - CaptioningManager captioningManager = - (CaptioningManager) getContext().getSystemService(Context.CAPTIONING_SERVICE); - return CaptionStyleCompat.createFromCaptionStyle(captioningManager.getUserStyle()); - } - -} diff --git a/library/core/src/main/res/drawable-anydpi-v21/exo_controls_fastforward.xml b/library/core/src/main/res/drawable-anydpi-v21/exo_controls_fastforward.xml deleted file mode 100644 index 4b86e109e9..0000000000 --- a/library/core/src/main/res/drawable-anydpi-v21/exo_controls_fastforward.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - diff --git a/library/core/src/main/res/drawable-anydpi-v21/exo_controls_next.xml b/library/core/src/main/res/drawable-anydpi-v21/exo_controls_next.xml deleted file mode 100644 index 6305bcbc90..0000000000 --- a/library/core/src/main/res/drawable-anydpi-v21/exo_controls_next.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - diff --git a/library/core/src/main/res/drawable-anydpi-v21/exo_controls_pause.xml b/library/core/src/main/res/drawable-anydpi-v21/exo_controls_pause.xml deleted file mode 100644 index 45cd68bed6..0000000000 --- a/library/core/src/main/res/drawable-anydpi-v21/exo_controls_pause.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - diff --git a/library/core/src/main/res/drawable-anydpi-v21/exo_controls_play.xml b/library/core/src/main/res/drawable-anydpi-v21/exo_controls_play.xml deleted file mode 100644 index c8c4cdb127..0000000000 --- a/library/core/src/main/res/drawable-anydpi-v21/exo_controls_play.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - diff --git a/library/core/src/main/res/drawable-anydpi-v21/exo_controls_previous.xml b/library/core/src/main/res/drawable-anydpi-v21/exo_controls_previous.xml deleted file mode 100644 index 9564a2a350..0000000000 --- a/library/core/src/main/res/drawable-anydpi-v21/exo_controls_previous.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - diff --git a/library/core/src/main/res/drawable-anydpi-v21/exo_controls_rewind.xml b/library/core/src/main/res/drawable-anydpi-v21/exo_controls_rewind.xml deleted file mode 100644 index 976b706170..0000000000 --- a/library/core/src/main/res/drawable-anydpi-v21/exo_controls_rewind.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - diff --git a/library/core/src/main/res/drawable-hdpi/exo_controls_fastforward.png b/library/core/src/main/res/drawable-hdpi/exo_controls_fastforward.png deleted file mode 100644 index 843df84091bb7e43f85088e2985cb17712eb4b12..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 354 zcmV-o0iFJdP)~5QPzl6$m6^1rmWoAW4u24uIeQ2qc0dZ~z1cKp+tuU<=Tl1wDU|KjmESlLKwAWuDy0?Ju3$lDWgaLQ6r z0eDtE9z+44UOeG~t54wP|l{5gJor@<%0p%hYWnGgfPpkqdf0AlUPwWE9 zNzwy&j_*7c1yUB0GQcyx_gEHC_L3ITV_iT$r#{kSS>WME4DhUjerS&a^(q`cUxg@7 z>;fMjBM(p0`Wnp~2fpykpksx@fldP%%7ZT8JQ6#2el3T&ua}Yx&(M0D_F4;3p3nl` zi}@SRkL60~|CGOa+SW_`B50Tbdk7(f5JJd}D`jHlVpJacasU7T07*qoM6N<$f{G`F Aj{pDw diff --git a/library/core/src/main/res/drawable-hdpi/exo_controls_next.png b/library/core/src/main/res/drawable-hdpi/exo_controls_next.png deleted file mode 100644 index c37541472ebb20e052a5c4e68f82852d4a489ec0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 323 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA3?vioaBc-s;Q>A&uI>ds|7if^nwEqEt&k}R z@(X5g;PIDueVZlw_jHBT=RXUWJnsHpeOP02w9fW;RW^Pzpt^cb7srr_xVM)qg$@}A zuwG>3G3w^Ik@o)g{K>04IW(I1PF(rP?e^C6Z?e~t}8mc^KVEOXY<)RyS%oA nDVdY8h==XT9hM(YvbWwlo}!Q;Ydn(!=oAJ|S3j3^P6E)*( diff --git a/library/core/src/main/res/drawable-hdpi/exo_controls_pause.png b/library/core/src/main/res/drawable-hdpi/exo_controls_pause.png deleted file mode 100644 index 0a23452746c0f33622ff4d909e2213b98ce990b9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 108 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDH3?y^UWFG-iYymzYu0Z<#|Nl#G&c6#}F_r}R z1v5B2yO9RuhnjGkCiCxvXAbpmr5JpHO}HTOB5@xyCL>^BiEHX8zeV3eCC}QlkKsiLyr`#VO({s-JnDN;}FLM6npO-wE_EgQu&X%Q~loCID~aZ8HD> diff --git a/library/core/src/main/res/drawable-hdpi/exo_controls_previous.png b/library/core/src/main/res/drawable-hdpi/exo_controls_previous.png deleted file mode 100644 index 3eae5c883bf2c1d8b950e35307ca7e0bf0fc664b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 292 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA3?vioaBc-st^qzFuI>ds|0xFHS4_484HPR0 z@(X4#xE%a&;{M-UA61GMy32^HPd(Xe_1wSeC_hj|g{O;SNJZS+OB=}UWAyQc>(V?C+-zHvv;vr9<^9_ b@df)-Q^{poR@z^IZeZ|q^>bP0l+XkKj~kZ6 diff --git a/library/core/src/main/res/drawable-hdpi/exo_controls_rewind.png b/library/core/src/main/res/drawable-hdpi/exo_controls_rewind.png deleted file mode 100644 index 36537d3b7320d0e2b352527ae374bc8c6f3a1d64..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 347 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA3?vioaBc-sfdM`tuI>ds|EUU+r44zwK8TlpZ=V`AX~Kb@xBLUpBqutG}z@yPb#Hr0Npiu4KG(s4v}T&!I9w zksXaJ*N)%W2b^$lV+00^T7r*g}8FYn}2J|fI5tI_Y)qkwje@(&0_aOxv z<9#Jszxs2`G_3UgsrOxvSk=Qd V9_(4)c?IZH22WQ%mvv4FO#p)T!5#nr diff --git a/library/core/src/main/res/drawable-ldpi/exo_controls_fastforward.png b/library/core/src/main/res/drawable-ldpi/exo_controls_fastforward.png deleted file mode 100644 index 19b9e6015c7cfe517037032e34ef1fd2de721475..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 192 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1|%Pp+x`Gj?Vc`@3FFZCs1}JDH z@L6DS8&IL6qH~W_kS>F3nw?5rI|NsA8 zhciwsRt5^knFK8Ok63Nl!oaeRxzJ2kx1sT@gN~y&x5EQdf$Gbbm?UQK9kJQ6CBa(Y z^o2uA0WHrjYjtc;i_D5-I8`FE#ba524kRmlTeg(dV1rbLYDOj_#~$`aDN#`j%)c8WnnBX#3hUUC#G)b@n5Q@1XqqOr zhwV{8RHT7?2Wy6)%l5__jbao3NO#hahox3Je0A~ do~wX~q2W~A`8Aye6Mzn3@O1TaS?83{1OTu`L7V^p diff --git a/library/core/src/main/res/drawable-ldpi/exo_controls_previous.png b/library/core/src/main/res/drawable-ldpi/exo_controls_previous.png deleted file mode 100644 index 930534d3127ceef161e086437aab19a725009e09..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 187 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1|%Pp+x`GjO`a}}Ar-fh6C_v{H>5qV7ZF(4 z$KLV$@)4#5TX+TTs~=NTiDVE-V|%Z zT*VkP1r_4{%kwX|!nEd~(^Z8gw;b!DBSE%&7bdIu>NK308E)g3vbV?ft1c)I$ztaD0e0sshPLKpx5 diff --git a/library/core/src/main/res/drawable-ldpi/exo_controls_rewind.png b/library/core/src/main/res/drawable-ldpi/exo_controls_rewind.png deleted file mode 100644 index 83d71782f65462fbc78127f99fa99dd10cbee9ef..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 214 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1|%Pp+x`Gjb3I)gLn>}1CrGd^ZqR%1|Nnm* z2b=jpraMfc7(Ui2{Ofn`o9owS^-t^h%r9;`u`(yQx*s7=}aaufe`1SBa5=zR%ISv-ZrD6LEYMBqE7&? z;mmA9yR#08j80h{8#N?4?hBvDc-V49V3P}5$M@h>X(ByLpBNY{e!iaao+04@(4h>T Lu6{1-oD!MFWREOXtO~A2?8{a7`g_FQ2I_1JnIRi`g+>uM{xwds|EUK$mVS={nk-oo zMzKY@y3JY5_^D&pQ=bmwa@5MXu) z@^pItmdKI;Vst0BZD- A)c^nh diff --git a/library/core/src/main/res/drawable-mdpi/exo_controls_pause.png b/library/core/src/main/res/drawable-mdpi/exo_controls_pause.png deleted file mode 100644 index f54c942201a83c6b866d651cb6dda51ab06d5cc9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 153 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnF3?v&v(vJfv(Ey(iSN8&+{}^EVDaKns1#Bfj ze!&d!4iW+?+N{j~fFin{E{-7@=aUl_NCyN3B_#` zyzvFT4)#Z99dwK*@G&%=cbLO_M2Gn)gMTnjll#U zebYe)NHa7(caX6G@)=*HMHgTe~DWM4f@A*$c diff --git a/library/core/src/main/res/drawable-mdpi/exo_controls_previous.png b/library/core/src/main/res/drawable-mdpi/exo_controls_previous.png deleted file mode 100644 index 950e213d2f550cf8022cc7ee063e509e6903d393..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 227 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=ffJRh}-6Ar-fh6C_v{Cy4YgF+H$n zI-?#a&%d&2#WRKlQPK+g_%FGHvol=Xz))x`@V_l{(QM|1Pg)K&zwD(JbTc;;y>$5V z(_YLWn4RIMOGCxqdYgtcK88gMUIs4~z z1~5loTbylnB<#n9y)q8^b?iqNJwz`&0!mMsm!ME*pTO7fYBJ-azl(Wi1sZUmdKI;Vst07C3gL;wH) diff --git a/library/core/src/main/res/drawable-mdpi/exo_controls_rewind.png b/library/core/src/main/res/drawable-mdpi/exo_controls_rewind.png deleted file mode 100644 index e75efae1893e7c53fce2d4c8809bbbe12e577923..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 273 zcmV+s0q*{ZP)`R z%w)4WEs7urf)7yDIa2=0w~Cd>9TCEByne3G6glVI|tLyqc0l7I60o&l_5><&Qr%*Cs`1zG>w zrB)~q1X z@^Hn92fydexcT4i@{=WsXErogKMntLwbk8b_SrOP_g7WAYW}h80aLG(GCX`b<2Azu zT_CF(DAcfg`8sBc@K;t0GVj*6uYb+(ta}%q!WE!whSe*ph9r+4Gb9<*{B6`5R{2z> zF+42WIN4qJ{oCMqq8e>mp0mvA+rif|YbW0s@nTsEnL=5M@Ml&Zz8on{crSeKS*^xx zrx^A_Z#!N)gc@CMJbC<|b*-!h+cT>RB}cygnzduj*|g2I4C0r(-V6Fk^8x+D;OXk; Jvd$@?2>^VR(}n;5 diff --git a/library/core/src/main/res/drawable-xhdpi/exo_controls_next.png b/library/core/src/main/res/drawable-xhdpi/exo_controls_next.png deleted file mode 100644 index bc1ebf83c58c0f6b983817f62a24213cf25c2c27..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 334 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7T#lEU=;OqaSW-r_4cNskYk`od!eHM zOQ3*`gNTcyt{r}#)c+<@MXM4-f zhpq$~2m~TiQg7Z__N)26WP14@XNK*YUuv)yq|fdwYDmbv9K!dY&&>HL!@)z#*2x@r zo4e~2SL}&66gRYYcTuay8?+egzqa?iKrg>VC>bmL1i7;T#`6T@CAEmyH<6wnL?Pgg&ebxsLQ05&!_aR2}S diff --git a/library/core/src/main/res/drawable-xhdpi/exo_controls_play.png b/library/core/src/main/res/drawable-xhdpi/exo_controls_play.png deleted file mode 100644 index f2f934413e8580fe363938e427fbc11905441893..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 343 zcmV-d0jU0oP)G|d_tc@}WR?%2|^fWQwsU}LWWBG2$FJPU}Nu&HMOfj3rr z77)2$bI$?-UuZ~Y$daY09(oIDZoxL2MW-i%tQk8CNrS`oyp7~Kvyy|3DA?w z%mU;mvw#5E$t);9W-|XE;D#+c3ix1KuTS8N%{+btU)aWL29Map<016GRvu5`aQ^Pq z{V_@;Kvvoc3Xqw$LIUKbEwcdmY0D@;N7^z7(3Q4?0(7P=kpR7E8z?}3+6D-)k+vQJ pY^ANEfZANz2qA?qY_z{C1* z%d}ffN-Gw;|0n*{`1iWB*_H?Pd(P|aTBLDl|7zYWcIF^a#%HNQjC$ss4B6t0r}P99 zes?jvRTO;iQ;aEYGDpBBmxgL_##@Sl3ZaY#;^i6s{o?QtWxD3a_Jdb3LDl%ce+kBY zDl9*yaX!eE=ZLt;`CzRq#uN5$XW0pK OCWEJ|pUXO@geCy!sjGbe diff --git a/library/core/src/main/res/drawable-xhdpi/exo_controls_rewind.png b/library/core/src/main/res/drawable-xhdpi/exo_controls_rewind.png deleted file mode 100644 index 3340ef9bd227be28c3641fc1a111522d9d0af362..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 400 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I3?%1nZ+ru!LIQk3T-^(N{!<$?d%wI4G+(+T z$S;_|kmEGV1`&r(zgw*zPI6BVmD#t5ulUMy6^-P8+MT9AWfweM978JN-cGUPYjzM| zsZHRQ;Mw=V@&Et%eCjWBGvzmkPSbM8s46!#k?K9RN&olRnu(u18}~K+T%2U#?{!{g zT~OPTnSY}t7o}fwl$@0Qj$hOH)0ws5K*8(5o(tTMd5SG6-*sAn`=a&z#VLwa=`TZh z%s-bra+}b8S-^DMt#f7jXWpDl6QVfyONCe??d>Y3Y7#J8s zJY5_^D&pQwKb?2TK*U9MQUaF=r?1atr}!^V|NlRvw`Yd$(w$QI?<3`3>g4pk`C#Ao z*aC?Bq<#DR?#c0-D|c0R&pvrEdy!q=ou880Oh4b5k76Y3*kztw*42IAQQ~^>bP0 Hl+XkKRE;tn diff --git a/library/core/src/main/res/drawable-xxhdpi/exo_controls_next.png b/library/core/src/main/res/drawable-xxhdpi/exo_controls_next.png deleted file mode 100644 index 232f09e910954a5a56b32d2cb807f2e68f8de781..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 391 zcmeAS@N?(olHy`uVBq!ia0vp^2_VeD3?#3*wSy#_0(?ST-3xsFQwF}~PUr`kCR!5Y z7tG*y)Qt7M!Y{seD`%c}?5@(zm5SXa?clKjsNlY*i(^Pd+}o>XxeggHuq6JTyzJt( zZP#l5hksRi<0~@f_BqQBjvTX3KM(TKTsq~FSzWo``#QEQZwwYx9ccJcbD$xLo%vF3 z8{`>6vf8#!6LBX zI49c`KOpO1!znK2w<(QvP3unRPh~m9U>m%imAT@*z=3XGrmqdpZtYj&`f;C~p`V$d z;vUC=%gqe)FVdQ&MBb@0Q^V4E&u=k diff --git a/library/core/src/main/res/drawable-xxhdpi/exo_controls_pause.png b/library/core/src/main/res/drawable-xxhdpi/exo_controls_pause.png deleted file mode 100644 index 50a545db4d67f51ccc127aa98582a2d5236e127f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 113 zcmeAS@N?(olHy`uVBq!ia0vp^2_VeK3?y%aJ*@^(YymzYuI>ds|NsA=KV^|KP=v80 z$S;_|;n|HeAV<>E#WBR_ALEHZkC5TjW01WFdewB_m zpN&S#A|!*?^k{}16VXoYnre9T)QqH5!op(Rt?bj}tv}c;oS*SOslKRi@&Y&Ci%$dR zhP?2L$lEM(JEzsGdg0l)jLqV=-e6}st}o!u{dPy~NUjrvcs8ro@R-D7ev`ELYA)%6 Rr9gi&c)I$ztaD0e0ssTizK8$- diff --git a/library/core/src/main/res/drawable-xxhdpi/exo_controls_previous.png b/library/core/src/main/res/drawable-xxhdpi/exo_controls_previous.png deleted file mode 100644 index f71acc4875e1ee7375b4023d800abd6b1292e717..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 464 zcmeAS@N?(olHy`uVBq!ia0vp^2_VeD3?#3*wSy#z0(?ST-3xsF(-#!%=eQ5FU9}|0 zFPP!IK{1ES>EA3i_dlnLJYPI{zq`if>)y>g?E=9n>o14*?|%G!e#s*KRX`0dJY5_^ zD&pQ=wJc%^6mh=D?)&*RbHay~vpgC9qg57qNxBvp-{qa-v+DTLDM4PEs&`i}kBX0c z@HAA#;E&nF17%th5B#3(k+5yDM}nz1r;nYAki`2|rdVOlFW#LjX37%}R4FPpEN^GD zvE_O2(viWRpShySBcW7`)2B~}siG=i!R`*0Uz3k8%##y&A;oj$$84sWeuIFo#_f%> zY7-l#^Rc}u=3+MeW6Jd*{*c38eT4(EZUzB=h1jxQNU>GSQ8@6pg;7qO>w`sN!|_fg z8$XE$g^L*ag;^`+7#w))!YH@AD#A+>=<;2Rb!XSeIO>$-0R7D1>FVdQ&MBb@0G@yI AVgLXD diff --git a/library/core/src/main/res/drawable-xxhdpi/exo_controls_rewind.png b/library/core/src/main/res/drawable-xxhdpi/exo_controls_rewind.png deleted file mode 100644 index db0555f9e5c5f89cf29edafc7c5f277556f3cb74..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 571 zcmeAS@N?(olHy`uVBq!ia0vp^2_VeD3?#3*wSy$81AIbU-3xsF4-n8x+3yN;hDJ${ zUob=Q{m(M*4Sut5h%`U9nS7cjcz?S4VwLrmH8x-Op5K3bce23a@8K5hd){5)sP1bJzu zex8u|$?iP!?4_9w#fJS7cQ&kE{)6%CEt3bSG8KD_c_U(uuXp5qeV?iS_c@ce-x7|; z-<~o_C=+&n+17iHx!=9`a>eB599HvUX0hcgXFPu4!kQ!Tx8C?3zTAA=J0l@1WYxo$ zfmXI!hPGbI-Bp*lFO`}nxAgL2q05U)szm(z7xo@{SqNnL^e?}>_|KPy%d?c{#ufx6 zpLzFlhV=5-hlb|&7B(m69eBjox6yI8p^Wq4yz2#Lc=wxC+^KPNzF%O`)|UIN<_{O| z4Zoj14x9L->#~^|_!Zo7m|RnLreUU x-~XHn5bxna#>s3aK743!bbf5IM-v<<_wCJor+pLc2m?j~gQu&X%Q~loCIETRKIZ@c diff --git a/library/core/src/main/res/layout/exo_playback_control_view.xml b/library/core/src/main/res/layout/exo_playback_control_view.xml deleted file mode 100644 index f8ef5a6fdd..0000000000 --- a/library/core/src/main/res/layout/exo_playback_control_view.xml +++ /dev/null @@ -1,87 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/library/core/src/main/res/layout/exo_simple_player_view.xml b/library/core/src/main/res/layout/exo_simple_player_view.xml deleted file mode 100644 index 1f59b7796d..0000000000 --- a/library/core/src/main/res/layout/exo_simple_player_view.xml +++ /dev/null @@ -1,49 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - diff --git a/library/core/src/main/res/values-af/strings.xml b/library/core/src/main/res/values-af/strings.xml deleted file mode 100644 index 9f1bce53d9..0000000000 --- a/library/core/src/main/res/values-af/strings.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - "Vorige snit" - "Volgende snit" - "Wag" - "Speel" - "Stop" - "Spoel terug" - "Vinnig vorentoe" - diff --git a/library/core/src/main/res/values-am/strings.xml b/library/core/src/main/res/values-am/strings.xml deleted file mode 100644 index f06c2a664e..0000000000 --- a/library/core/src/main/res/values-am/strings.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - "ቀዳሚ ትራክ" - "ቀጣይ ትራክ" - "ለአፍታ አቁም" - "አጫውት" - "አቁም" - "ወደኋላ አጠንጥን" - "በፍጥነት አሳልፍ" - diff --git a/library/core/src/main/res/values-ar/strings.xml b/library/core/src/main/res/values-ar/strings.xml deleted file mode 100644 index a40c961bf7..0000000000 --- a/library/core/src/main/res/values-ar/strings.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - "المقطع الصوتي السابق" - "المقطع الصوتي التالي" - "إيقاف مؤقت" - "تشغيل" - "إيقاف" - "إرجاع" - "تقديم سريع" - diff --git a/library/core/src/main/res/values-az-rAZ/strings.xml b/library/core/src/main/res/values-az-rAZ/strings.xml deleted file mode 100644 index 7b3b9366b5..0000000000 --- a/library/core/src/main/res/values-az-rAZ/strings.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - "Öncəki trek" - "Növbəti trek" - "Pauza" - "Oyun" - "Dayandır" - "Geri sarıma" - "Sürətlə irəli" - diff --git a/library/core/src/main/res/values-b+sr+Latn/strings.xml b/library/core/src/main/res/values-b+sr+Latn/strings.xml deleted file mode 100644 index b5fdd74402..0000000000 --- a/library/core/src/main/res/values-b+sr+Latn/strings.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - "Prethodna pesma" - "Sledeća pesma" - "Pauza" - "Pusti" - "Zaustavi" - "Premotaj unazad" - "Premotaj unapred" - diff --git a/library/core/src/main/res/values-be-rBY/strings.xml b/library/core/src/main/res/values-be-rBY/strings.xml deleted file mode 100644 index 890c23ebd5..0000000000 --- a/library/core/src/main/res/values-be-rBY/strings.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - "Папярэдні трэк" - "Наступны трэк" - "Прыпыніць" - "Прайграць" - "Спыніць" - "Перамотка назад" - "Перамотка ўперад" - diff --git a/library/core/src/main/res/values-bg/strings.xml b/library/core/src/main/res/values-bg/strings.xml deleted file mode 100644 index 30b905fb8e..0000000000 --- a/library/core/src/main/res/values-bg/strings.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - "Предишен запис" - "Следващ запис" - "Пауза" - "Пускане" - "Спиране" - "Превъртане назад" - "Превъртане напред" - diff --git a/library/core/src/main/res/values-bn-rBD/strings.xml b/library/core/src/main/res/values-bn-rBD/strings.xml deleted file mode 100644 index ca5d9461d3..0000000000 --- a/library/core/src/main/res/values-bn-rBD/strings.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - "পূর্ববর্তী ট্র্যাক" - "পরবর্তী ট্র্যাক" - "বিরাম দিন" - "প্লে করুন" - "থামান" - "গুটিয়ে নিন" - "দ্রুত সামনে এগোন" - diff --git a/library/core/src/main/res/values-bs-rBA/strings.xml b/library/core/src/main/res/values-bs-rBA/strings.xml deleted file mode 100644 index 9cb0ca4d76..0000000000 --- a/library/core/src/main/res/values-bs-rBA/strings.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - "Prethodna numera" - "Sljedeća numera" - "Pauziraj" - "Reproduciraj" - "Zaustavi" - "Premotaj" - "Ubrzaj" - diff --git a/library/core/src/main/res/values-ca/strings.xml b/library/core/src/main/res/values-ca/strings.xml deleted file mode 100644 index 0816c76b12..0000000000 --- a/library/core/src/main/res/values-ca/strings.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - "Ruta anterior" - "Ruta següent" - "Posa en pausa" - "Reprodueix" - "Atura" - "Rebobina" - "Avança ràpidament" - diff --git a/library/core/src/main/res/values-cs/strings.xml b/library/core/src/main/res/values-cs/strings.xml deleted file mode 100644 index 22cff4041e..0000000000 --- a/library/core/src/main/res/values-cs/strings.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - "Předchozí skladba" - "Další skladba" - "Pozastavit" - "Přehrát" - "Zastavit" - "Přetočit zpět" - "Přetočit vpřed" - diff --git a/library/core/src/main/res/values-da/strings.xml b/library/core/src/main/res/values-da/strings.xml deleted file mode 100644 index a6710bea50..0000000000 --- a/library/core/src/main/res/values-da/strings.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - "Forrige nummer" - "Næste nummer" - "Pause" - "Afspil" - "Stop" - "Spol tilbage" - "Spol frem" - diff --git a/library/core/src/main/res/values-de/strings.xml b/library/core/src/main/res/values-de/strings.xml deleted file mode 100644 index cdfd2d4baf..0000000000 --- a/library/core/src/main/res/values-de/strings.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - "Vorheriger Titel" - "Nächster Titel" - "Pausieren" - "Wiedergabe" - "Beenden" - "Zurückspulen" - "Vorspulen" - diff --git a/library/core/src/main/res/values-el/strings.xml b/library/core/src/main/res/values-el/strings.xml deleted file mode 100644 index 1e11df3b14..0000000000 --- a/library/core/src/main/res/values-el/strings.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - "Προηγούμενο κομμάτι" - "Επόμενο κομμάτι" - "Παύση" - "Αναπαραγωγή" - "Διακοπή" - "Επαναφορά" - "Γρήγορη προώθηση" - diff --git a/library/core/src/main/res/values-en-rAU/strings.xml b/library/core/src/main/res/values-en-rAU/strings.xml deleted file mode 100644 index 5077cf2b94..0000000000 --- a/library/core/src/main/res/values-en-rAU/strings.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - "Previous track" - "Next track" - "Pause" - "Play" - "Stop" - "Rewind" - "Fast-forward" - diff --git a/library/core/src/main/res/values-en-rGB/strings.xml b/library/core/src/main/res/values-en-rGB/strings.xml deleted file mode 100644 index 5077cf2b94..0000000000 --- a/library/core/src/main/res/values-en-rGB/strings.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - "Previous track" - "Next track" - "Pause" - "Play" - "Stop" - "Rewind" - "Fast-forward" - diff --git a/library/core/src/main/res/values-en-rIN/strings.xml b/library/core/src/main/res/values-en-rIN/strings.xml deleted file mode 100644 index 5077cf2b94..0000000000 --- a/library/core/src/main/res/values-en-rIN/strings.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - "Previous track" - "Next track" - "Pause" - "Play" - "Stop" - "Rewind" - "Fast-forward" - diff --git a/library/core/src/main/res/values-es-rUS/strings.xml b/library/core/src/main/res/values-es-rUS/strings.xml deleted file mode 100644 index 72b176e538..0000000000 --- a/library/core/src/main/res/values-es-rUS/strings.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - "Pista anterior" - "Siguiente pista" - "Pausar" - "Reproducir" - "Detener" - "Retroceder" - "Avanzar" - diff --git a/library/core/src/main/res/values-es/strings.xml b/library/core/src/main/res/values-es/strings.xml deleted file mode 100644 index 3b188d266d..0000000000 --- a/library/core/src/main/res/values-es/strings.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - "Canción anterior" - "Siguiente canción" - "Pausar" - "Reproducir" - "Detener" - "Rebobinar" - "Avance rápido" - diff --git a/library/core/src/main/res/values-et-rEE/strings.xml b/library/core/src/main/res/values-et-rEE/strings.xml deleted file mode 100644 index 7a01bd9d5a..0000000000 --- a/library/core/src/main/res/values-et-rEE/strings.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - "Eelmine lugu" - "Järgmine lugu" - "Peata" - "Esita" - "Peata" - "Keri tagasi" - "Keri edasi" - diff --git a/library/core/src/main/res/values-eu-rES/strings.xml b/library/core/src/main/res/values-eu-rES/strings.xml deleted file mode 100644 index 3dd51d2138..0000000000 --- a/library/core/src/main/res/values-eu-rES/strings.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - "Aurreko pista" - "Hurrengo pista" - "Pausatu" - "Erreproduzitu" - "Gelditu" - "Atzeratu" - "Aurreratu" - diff --git a/library/core/src/main/res/values-fa/strings.xml b/library/core/src/main/res/values-fa/strings.xml deleted file mode 100644 index a8955ca2f3..0000000000 --- a/library/core/src/main/res/values-fa/strings.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - "آهنگ قبلی" - "آهنگ بعدی" - "مکث" - "پخش" - "توقف" - "عقب بردن" - "جلو بردن سریع" - diff --git a/library/core/src/main/res/values-fi/strings.xml b/library/core/src/main/res/values-fi/strings.xml deleted file mode 100644 index 5f1352d1af..0000000000 --- a/library/core/src/main/res/values-fi/strings.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - "Edellinen raita" - "Seuraava raita" - "Tauko" - "Toista" - "Seis" - "Kelaa taakse" - "Kelaa eteen" - diff --git a/library/core/src/main/res/values-fr-rCA/strings.xml b/library/core/src/main/res/values-fr-rCA/strings.xml deleted file mode 100644 index 51ba11e0c0..0000000000 --- a/library/core/src/main/res/values-fr-rCA/strings.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - "Chanson précédente" - "Chanson suivante" - "Pause" - "Lecture" - "Arrêter" - "Reculer" - "Avance rapide" - diff --git a/library/core/src/main/res/values-fr/strings.xml b/library/core/src/main/res/values-fr/strings.xml deleted file mode 100644 index d55b32b6f7..0000000000 --- a/library/core/src/main/res/values-fr/strings.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - "Piste précédente" - "Piste suivante" - "Interrompre" - "Lire" - "Arrêter" - "Retour arrière" - "Avance rapide" - diff --git a/library/core/src/main/res/values-gl-rES/strings.xml b/library/core/src/main/res/values-gl-rES/strings.xml deleted file mode 100644 index 99ae59c7f9..0000000000 --- a/library/core/src/main/res/values-gl-rES/strings.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - "Pista anterior" - "Seguinte pista" - "Pausar" - "Reproducir" - "Deter" - "Rebobinar" - "Avance rápido" - diff --git a/library/core/src/main/res/values-gu-rIN/strings.xml b/library/core/src/main/res/values-gu-rIN/strings.xml deleted file mode 100644 index 6feab0a3a6..0000000000 --- a/library/core/src/main/res/values-gu-rIN/strings.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - "પહેલાનો ટ્રૅક" - "આગલો ટ્રૅક" - "થોભો" - "ચલાવો" - "રોકો" - "રીવાઇન્ડ કરો" - "ઝડપી ફોરવર્ડ કરો" - diff --git a/library/core/src/main/res/values-hi/strings.xml b/library/core/src/main/res/values-hi/strings.xml deleted file mode 100644 index 5229b67d0e..0000000000 --- a/library/core/src/main/res/values-hi/strings.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - "पिछला ट्रैक" - "अगला ट्रैक" - "रोकें" - "चलाएं" - "बंद करें" - "रिवाइंड करें" - "फ़ास्ट फ़ॉरवर्ड" - diff --git a/library/core/src/main/res/values-hr/strings.xml b/library/core/src/main/res/values-hr/strings.xml deleted file mode 100644 index c0b075edde..0000000000 --- a/library/core/src/main/res/values-hr/strings.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - "Prethodna pjesma" - "Sljedeća pjesma" - "Pauziraj" - "Reproduciraj" - "Zaustavi" - "Unatrag" - "Brzo unaprijed" - diff --git a/library/core/src/main/res/values-hu/strings.xml b/library/core/src/main/res/values-hu/strings.xml deleted file mode 100644 index 2a34684edb..0000000000 --- a/library/core/src/main/res/values-hu/strings.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - "Előző szám" - "Következő szám" - "Szünet" - "Lejátszás" - "Leállítás" - "Visszatekerés" - "Előretekerés" - diff --git a/library/core/src/main/res/values-hy-rAM/strings.xml b/library/core/src/main/res/values-hy-rAM/strings.xml deleted file mode 100644 index 05f9d04ab7..0000000000 --- a/library/core/src/main/res/values-hy-rAM/strings.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - "Նախորդը" - "Հաջորդը" - "Դադարեցնել" - "Նվագարկել" - "Դադարեցնել" - "Հետ փաթաթել" - "Արագ առաջ անցնել" - diff --git a/library/core/src/main/res/values-in/strings.xml b/library/core/src/main/res/values-in/strings.xml deleted file mode 100644 index 062933a0a8..0000000000 --- a/library/core/src/main/res/values-in/strings.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - "Lagu sebelumnya" - "Lagu berikutnya" - "Jeda" - "Putar" - "Berhenti" - "Putar Ulang" - "Maju cepat" - diff --git a/library/core/src/main/res/values-is-rIS/strings.xml b/library/core/src/main/res/values-is-rIS/strings.xml deleted file mode 100644 index 9c4421a272..0000000000 --- a/library/core/src/main/res/values-is-rIS/strings.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - "Fyrra lag" - "Næsta lag" - "Hlé" - "Spila" - "Stöðva" - "Spóla til baka" - "Spóla áfram" - diff --git a/library/core/src/main/res/values-it/strings.xml b/library/core/src/main/res/values-it/strings.xml deleted file mode 100644 index 71525a2b3e..0000000000 --- a/library/core/src/main/res/values-it/strings.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - "Traccia precedente" - "Traccia successiva" - "Metti in pausa" - "Riproduci" - "Interrompi" - "Riavvolgi" - "Avanti veloce" - diff --git a/library/core/src/main/res/values-iw/strings.xml b/library/core/src/main/res/values-iw/strings.xml deleted file mode 100644 index f33cc2adb0..0000000000 --- a/library/core/src/main/res/values-iw/strings.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - "הרצועה הקודמת" - "הרצועה הבאה" - "השהה" - "הפעל" - "הפסק" - "הרץ אחורה" - "הרץ קדימה" - diff --git a/library/core/src/main/res/values-ja/strings.xml b/library/core/src/main/res/values-ja/strings.xml deleted file mode 100644 index baa459aeca..0000000000 --- a/library/core/src/main/res/values-ja/strings.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - "前のトラック" - "次のトラック" - "一時停止" - "再生" - "停止" - "巻き戻し" - "早送り" - diff --git a/library/core/src/main/res/values-ka-rGE/strings.xml b/library/core/src/main/res/values-ka-rGE/strings.xml deleted file mode 100644 index 5b87f86c34..0000000000 --- a/library/core/src/main/res/values-ka-rGE/strings.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - "წინა ჩანაწერი" - "შემდეგი ჩანაწერი" - "პაუზა" - "დაკვრა" - "შეწყვეტა" - "უკან გადახვევა" - "წინ გადახვევა" - diff --git a/library/core/src/main/res/values-kk-rKZ/strings.xml b/library/core/src/main/res/values-kk-rKZ/strings.xml deleted file mode 100644 index c1bf5c8b4b..0000000000 --- a/library/core/src/main/res/values-kk-rKZ/strings.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - "Алдыңғы трек" - "Келесі трек" - "Кідірту" - "Ойнату" - "Тоқтату" - "Кері айналдыру" - "Жылдам алға айналдыру" - diff --git a/library/core/src/main/res/values-km-rKH/strings.xml b/library/core/src/main/res/values-km-rKH/strings.xml deleted file mode 100644 index dbeeab60a6..0000000000 --- a/library/core/src/main/res/values-km-rKH/strings.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - "បទ​មុន" - "បទ​បន្ទាប់" - "ផ្អាក" - "ចាក់" - "បញ្ឈប់" - "ខា​ថយក្រោយ" - "ទៅ​មុខ​​​រហ័ស" - diff --git a/library/core/src/main/res/values-kn-rIN/strings.xml b/library/core/src/main/res/values-kn-rIN/strings.xml deleted file mode 100644 index b73cf0fdb0..0000000000 --- a/library/core/src/main/res/values-kn-rIN/strings.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - "ಹಿಂದಿನ ಟ್ರ್ಯಾಕ್" - "ಮುಂದಿನ ಟ್ರ್ಯಾಕ್" - "ವಿರಾಮಗೊಳಿಸು" - "ಪ್ಲೇ ಮಾಡು" - "ನಿಲ್ಲಿಸು" - "ರಿವೈಂಡ್ ಮಾಡು" - "ವೇಗವಾಗಿ ಮುಂದಕ್ಕೆ" - diff --git a/library/core/src/main/res/values-ko/strings.xml b/library/core/src/main/res/values-ko/strings.xml deleted file mode 100644 index 7097e2d9f7..0000000000 --- a/library/core/src/main/res/values-ko/strings.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - "이전 트랙" - "다음 트랙" - "일시중지" - "재생" - "중지" - "되감기" - "빨리 감기" - diff --git a/library/core/src/main/res/values-ky-rKG/strings.xml b/library/core/src/main/res/values-ky-rKG/strings.xml deleted file mode 100644 index 7090c178c3..0000000000 --- a/library/core/src/main/res/values-ky-rKG/strings.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - "Мурунку трек" - "Кийинки трек" - "Тындыруу" - "Ойнотуу" - "Токтотуу" - "Артка түрүү" - "Алдыга түрүү" - diff --git a/library/core/src/main/res/values-lo-rLA/strings.xml b/library/core/src/main/res/values-lo-rLA/strings.xml deleted file mode 100644 index 44095e4323..0000000000 --- a/library/core/src/main/res/values-lo-rLA/strings.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - "​ເພງ​ກ່ອນ​ໜ້າ" - "​ເພງ​ຕໍ່​ໄປ" - "ຢຸດຊົ່ວຄາວ" - "ຫຼິ້ນ" - "ຢຸດ" - "​ຣີ​​ວາຍກັບ" - "ເລື່ອນ​ໄປ​ໜ້າ" - diff --git a/library/core/src/main/res/values-lt/strings.xml b/library/core/src/main/res/values-lt/strings.xml deleted file mode 100644 index 138caec322..0000000000 --- a/library/core/src/main/res/values-lt/strings.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - "Ankstesnis takelis" - "Kitas takelis" - "Pristabdyti" - "Leisti" - "Stabdyti" - "Sukti atgal" - "Sukti pirmyn" - diff --git a/library/core/src/main/res/values-lv/strings.xml b/library/core/src/main/res/values-lv/strings.xml deleted file mode 100644 index 4c91da86cc..0000000000 --- a/library/core/src/main/res/values-lv/strings.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - "Iepriekšējais ieraksts" - "Nākamais ieraksts" - "Pārtraukt" - "Atskaņot" - "Apturēt" - "Attīt atpakaļ" - "Ātri patīt" - diff --git a/library/core/src/main/res/values-mk-rMK/strings.xml b/library/core/src/main/res/values-mk-rMK/strings.xml deleted file mode 100644 index e9fedf689f..0000000000 --- a/library/core/src/main/res/values-mk-rMK/strings.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - "Претходна песна" - "Следна песна" - "Пауза" - "Пушти" - "Запри" - "Премотај назад" - "Брзо премотај напред" - diff --git a/library/core/src/main/res/values-ml-rIN/strings.xml b/library/core/src/main/res/values-ml-rIN/strings.xml deleted file mode 100644 index acc33934fb..0000000000 --- a/library/core/src/main/res/values-ml-rIN/strings.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - "മുമ്പത്തെ ട്രാക്ക്" - "അടുത്ത ട്രാക്ക്" - "താൽക്കാലികമായി നിർത്തുക" - "പ്ലേ ചെയ്യുക" - "നിര്‍ത്തുക" - "റിവൈൻഡുചെയ്യുക" - "വേഗത്തിലുള്ള കൈമാറൽ" - diff --git a/library/core/src/main/res/values-mn-rMN/strings.xml b/library/core/src/main/res/values-mn-rMN/strings.xml deleted file mode 100644 index 6434e9ea16..0000000000 --- a/library/core/src/main/res/values-mn-rMN/strings.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - "Өмнөх трек" - "Дараагийн трек" - "Түр зогсоох" - "Тоглуулах" - "Зогсоох" - "Буцааж хураах" - "Хурдан урагшлуулах" - diff --git a/library/core/src/main/res/values-mr-rIN/strings.xml b/library/core/src/main/res/values-mr-rIN/strings.xml deleted file mode 100644 index 8f4d0d75b1..0000000000 --- a/library/core/src/main/res/values-mr-rIN/strings.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - "मागील ट्रॅक" - "पुढील ट्रॅक" - "विराम द्या" - "प्ले करा" - "थांबा" - "रिवाईँड करा" - "फास्ट फॉरवर्ड करा" - diff --git a/library/core/src/main/res/values-ms-rMY/strings.xml b/library/core/src/main/res/values-ms-rMY/strings.xml deleted file mode 100644 index 91f74bbc1c..0000000000 --- a/library/core/src/main/res/values-ms-rMY/strings.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - "Lagu sebelumnya" - "Lagu seterusnya" - "Jeda" - "Main" - "Berhenti" - "Gulung semula" - "Mara laju" - diff --git a/library/core/src/main/res/values-my-rMM/strings.xml b/library/core/src/main/res/values-my-rMM/strings.xml deleted file mode 100644 index 4b68e6e950..0000000000 --- a/library/core/src/main/res/values-my-rMM/strings.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - "ယခင် တစ်ပုဒ်" - "နောက် တစ်ပုဒ်" - "ခဏရပ်ရန်" - "ဖွင့်ရန်" - "ရပ်ရန်" - "ပြန်ရစ်ရန်" - "ရှေ့သို့ သွားရန်" - diff --git a/library/core/src/main/res/values-nb/strings.xml b/library/core/src/main/res/values-nb/strings.xml deleted file mode 100644 index 37454235ad..0000000000 --- a/library/core/src/main/res/values-nb/strings.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - "Forrige spor" - "Neste spor" - "Sett på pause" - "Spill av" - "Stopp" - "Tilbakespoling" - "Fremoverspoling" - diff --git a/library/core/src/main/res/values-ne-rNP/strings.xml b/library/core/src/main/res/values-ne-rNP/strings.xml deleted file mode 100644 index 375e44afce..0000000000 --- a/library/core/src/main/res/values-ne-rNP/strings.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - "अघिल्लो ट्रयाक" - "अर्को ट्रयाक" - "रोक्नुहोस्" - "चलाउनुहोस्" - "रोक्नुहोस्" - "दोहोर्याउनुहोस्" - "फास्ट फर्वार्ड" - diff --git a/library/core/src/main/res/values-nl/strings.xml b/library/core/src/main/res/values-nl/strings.xml deleted file mode 100644 index 2bdbf0bdae..0000000000 --- a/library/core/src/main/res/values-nl/strings.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - "Vorig nummer" - "Volgend nummer" - "Onderbreken" - "Afspelen" - "Stoppen" - "Terugspoelen" - "Vooruitspoelen" - diff --git a/library/core/src/main/res/values-pa-rIN/strings.xml b/library/core/src/main/res/values-pa-rIN/strings.xml deleted file mode 100644 index 143508e071..0000000000 --- a/library/core/src/main/res/values-pa-rIN/strings.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - "ਪਿਛਲਾ ਟਰੈਕ" - "ਅਗਲਾ ਟਰੈਕ" - "ਰੋਕੋ" - "ਪਲੇ ਕਰੋ" - "ਰੋਕੋ" - "ਰੀਵਾਈਂਡ ਕਰੋ" - "ਅੱਗੇ ਭੇਜੋ" - diff --git a/library/core/src/main/res/values-pl/strings.xml b/library/core/src/main/res/values-pl/strings.xml deleted file mode 100644 index 64f52d5d09..0000000000 --- a/library/core/src/main/res/values-pl/strings.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - "Poprzedni utwór" - "Następny utwór" - "Wstrzymaj" - "Odtwórz" - "Zatrzymaj" - "Przewiń do tyłu" - "Przewiń do przodu" - diff --git a/library/core/src/main/res/values-pt-rBR/strings.xml b/library/core/src/main/res/values-pt-rBR/strings.xml deleted file mode 100644 index 51bcf4d723..0000000000 --- a/library/core/src/main/res/values-pt-rBR/strings.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - "Faixa anterior" - "Próxima faixa" - "Pausar" - "Reproduzir" - "Parar" - "Retroceder" - "Avançar" - diff --git a/library/core/src/main/res/values-pt-rPT/strings.xml b/library/core/src/main/res/values-pt-rPT/strings.xml deleted file mode 100644 index 5b3c9131d0..0000000000 --- a/library/core/src/main/res/values-pt-rPT/strings.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - "Faixa anterior" - "Faixa seguinte" - "Interromper" - "Reproduzir" - "Parar" - "Rebobinar" - "Avançar" - diff --git a/library/core/src/main/res/values-pt/strings.xml b/library/core/src/main/res/values-pt/strings.xml deleted file mode 100644 index 51bcf4d723..0000000000 --- a/library/core/src/main/res/values-pt/strings.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - "Faixa anterior" - "Próxima faixa" - "Pausar" - "Reproduzir" - "Parar" - "Retroceder" - "Avançar" - diff --git a/library/core/src/main/res/values-ro/strings.xml b/library/core/src/main/res/values-ro/strings.xml deleted file mode 100644 index 5a7feda78c..0000000000 --- a/library/core/src/main/res/values-ro/strings.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - "Melodia anterioară" - "Melodia următoare" - "Pauză" - "Redați" - "Opriți" - "Derulați" - "Derulați rapid înainte" - diff --git a/library/core/src/main/res/values-ru/strings.xml b/library/core/src/main/res/values-ru/strings.xml deleted file mode 100644 index da47546a8b..0000000000 --- a/library/core/src/main/res/values-ru/strings.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - "Предыдущий трек" - "Следующий трек" - "Приостановить" - "Воспроизвести" - "Остановить" - "Перемотать назад" - "Перемотать вперед" - diff --git a/library/core/src/main/res/values-si-rLK/strings.xml b/library/core/src/main/res/values-si-rLK/strings.xml deleted file mode 100644 index 0b579240e8..0000000000 --- a/library/core/src/main/res/values-si-rLK/strings.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - "පෙර ගීතය" - "ඊළඟ ගීතය" - "විරාමය" - "ධාවනය කරන්න" - "නතර කරන්න" - "නැවත ඔතන්න" - "වේගයෙන් ඉදිරියට යන" - diff --git a/library/core/src/main/res/values-sk/strings.xml b/library/core/src/main/res/values-sk/strings.xml deleted file mode 100644 index 7596497e06..0000000000 --- a/library/core/src/main/res/values-sk/strings.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - "Predchádzajúca stopa" - "Ďalšia stopa" - "Pozastaviť" - "Prehrať" - "Zastaviť" - "Pretočiť späť" - "Pretočiť dopredu" - diff --git a/library/core/src/main/res/values-sl/strings.xml b/library/core/src/main/res/values-sl/strings.xml deleted file mode 100644 index a77586b50c..0000000000 --- a/library/core/src/main/res/values-sl/strings.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - "Prejšnja skladba" - "Naslednja skladba" - "Zaustavi" - "Predvajaj" - "Ustavi" - "Previj nazaj" - "Previj naprej" - diff --git a/library/core/src/main/res/values-sq-rAL/strings.xml b/library/core/src/main/res/values-sq-rAL/strings.xml deleted file mode 100644 index 1fb824366d..0000000000 --- a/library/core/src/main/res/values-sq-rAL/strings.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - "Kënga e mëparshme" - "Kënga tjetër" - "Pauzë" - "Luaj" - "Ndalo" - "Kthehu pas" - "Përparo me shpejtësi" - diff --git a/library/core/src/main/res/values-sr/strings.xml b/library/core/src/main/res/values-sr/strings.xml deleted file mode 100644 index 175ad4fe7f..0000000000 --- a/library/core/src/main/res/values-sr/strings.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - "Претходна песма" - "Следећа песма" - "Пауза" - "Пусти" - "Заустави" - "Премотај уназад" - "Премотај унапред" - diff --git a/library/core/src/main/res/values-sv/strings.xml b/library/core/src/main/res/values-sv/strings.xml deleted file mode 100644 index e6a8960458..0000000000 --- a/library/core/src/main/res/values-sv/strings.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - "Föregående spår" - "Nästa spår" - "Pausa" - "Spela upp" - "Avbryt" - "Spola tillbaka" - "Snabbspola framåt" - diff --git a/library/core/src/main/res/values-sw/strings.xml b/library/core/src/main/res/values-sw/strings.xml deleted file mode 100644 index 8055b7daff..0000000000 --- a/library/core/src/main/res/values-sw/strings.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - "Wimbo uliotangulia" - "Wimbo unaofuata" - "Sitisha" - "Cheza" - "Simamisha" - "Rudisha nyuma" - "Peleka mbele kwa kasi" - diff --git a/library/core/src/main/res/values-ta-rIN/strings.xml b/library/core/src/main/res/values-ta-rIN/strings.xml deleted file mode 100644 index 3eb995d467..0000000000 --- a/library/core/src/main/res/values-ta-rIN/strings.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - "முந்தைய ட்ராக்" - "அடுத்த ட்ராக்" - "இடைநிறுத்து" - "இயக்கு" - "நிறுத்து" - "மீண்டும் காட்டு" - "வேகமாக முன்செல்" - diff --git a/library/core/src/main/res/values-te-rIN/strings.xml b/library/core/src/main/res/values-te-rIN/strings.xml deleted file mode 100644 index fe7930455a..0000000000 --- a/library/core/src/main/res/values-te-rIN/strings.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - "మునుపటి ట్రాక్" - "తదుపరి ట్రాక్" - "పాజ్ చేయి" - "ప్లే చేయి" - "ఆపివేయి" - "రివైండ్ చేయి" - "వేగంగా ఫార్వార్డ్ చేయి" - diff --git a/library/core/src/main/res/values-th/strings.xml b/library/core/src/main/res/values-th/strings.xml deleted file mode 100644 index deb2aac87d..0000000000 --- a/library/core/src/main/res/values-th/strings.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - "แทร็กก่อนหน้า" - "แทร็กถัดไป" - "หยุดชั่วคราว" - "เล่น" - "หยุด" - "กรอกลับ" - "กรอไปข้างหน้า" - diff --git a/library/core/src/main/res/values-tl/strings.xml b/library/core/src/main/res/values-tl/strings.xml deleted file mode 100644 index 28dcb3267e..0000000000 --- a/library/core/src/main/res/values-tl/strings.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - "Nakaraang track" - "Susunod na track" - "I-pause" - "I-play" - "Ihinto" - "I-rewind" - "I-fast forward" - diff --git a/library/core/src/main/res/values-tr/strings.xml b/library/core/src/main/res/values-tr/strings.xml deleted file mode 100644 index 4265d796fe..0000000000 --- a/library/core/src/main/res/values-tr/strings.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - "Önceki parça" - "Sonraki parça" - "Duraklat" - "Çal" - "Durdur" - "Geri sar" - "İleri sar" - diff --git a/library/core/src/main/res/values-uk/strings.xml b/library/core/src/main/res/values-uk/strings.xml deleted file mode 100644 index 487ca07556..0000000000 --- a/library/core/src/main/res/values-uk/strings.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - "Попередня композиція" - "Наступна композиція" - "Пауза" - "Відтворити" - "Зупинити" - "Перемотати назад" - "Перемотати вперед" - diff --git a/library/core/src/main/res/values-ur-rPK/strings.xml b/library/core/src/main/res/values-ur-rPK/strings.xml deleted file mode 100644 index 55fa908bcd..0000000000 --- a/library/core/src/main/res/values-ur-rPK/strings.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - "پچھلا ٹریک" - "اگلا ٹریک" - "موقوف کریں" - "چلائیں" - "روکیں" - "ریوائینڈ کریں" - "تیزی سے فارورڈ کریں" - diff --git a/library/core/src/main/res/values-uz-rUZ/strings.xml b/library/core/src/main/res/values-uz-rUZ/strings.xml deleted file mode 100644 index 9cee926844..0000000000 --- a/library/core/src/main/res/values-uz-rUZ/strings.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - "Avvalgi musiqa" - "Keyingi musiqa" - "To‘xtatib turish" - "Ijro qilish" - "To‘xtatish" - "Orqaga o‘tkazish" - "Oldinga o‘tkazish" - diff --git a/library/core/src/main/res/values-v11/styles.xml b/library/core/src/main/res/values-v11/styles.xml deleted file mode 100644 index 6f77440287..0000000000 --- a/library/core/src/main/res/values-v11/styles.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - diff --git a/library/core/src/main/res/values-vi/strings.xml b/library/core/src/main/res/values-vi/strings.xml deleted file mode 100644 index 917ec8e95c..0000000000 --- a/library/core/src/main/res/values-vi/strings.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - "Bản nhạc trước" - "Bản nhạc tiếp theo" - "Tạm dừng" - "Phát" - "Ngừng" - "Tua lại" - "Tua đi" - diff --git a/library/core/src/main/res/values-zh-rCN/strings.xml b/library/core/src/main/res/values-zh-rCN/strings.xml deleted file mode 100644 index 41e02409e2..0000000000 --- a/library/core/src/main/res/values-zh-rCN/strings.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - "上一曲" - "下一曲" - "暂停" - "播放" - "停止" - "快退" - "快进" - diff --git a/library/core/src/main/res/values-zh-rHK/strings.xml b/library/core/src/main/res/values-zh-rHK/strings.xml deleted file mode 100644 index a3244bcd70..0000000000 --- a/library/core/src/main/res/values-zh-rHK/strings.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - "上一首曲目" - "下一首曲目" - "暫停" - "播放" - "停止" - "倒帶" - "向前快轉" - diff --git a/library/core/src/main/res/values-zh-rTW/strings.xml b/library/core/src/main/res/values-zh-rTW/strings.xml deleted file mode 100644 index ee915c5d9d..0000000000 --- a/library/core/src/main/res/values-zh-rTW/strings.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - "上一首曲目" - "下一首曲目" - "暫停" - "播放" - "停止" - "倒轉" - "快轉" - diff --git a/library/core/src/main/res/values-zu/strings.xml b/library/core/src/main/res/values-zu/strings.xml deleted file mode 100644 index e998846454..0000000000 --- a/library/core/src/main/res/values-zu/strings.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - "Ithrekhi yangaphambilini" - "Ithrekhi elandelayo" - "Misa isikhashana" - "Dlala" - "Misa" - "Buyisela emumva" - "Ukudlulisa ngokushesha" - diff --git a/library/core/src/main/res/values/attrs.xml b/library/core/src/main/res/values/attrs.xml deleted file mode 100644 index c73bfb0a3c..0000000000 --- a/library/core/src/main/res/values/attrs.xml +++ /dev/null @@ -1,62 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/library/core/src/main/res/values/constants.xml b/library/core/src/main/res/values/constants.xml deleted file mode 100644 index 5c86696ea0..0000000000 --- a/library/core/src/main/res/values/constants.xml +++ /dev/null @@ -1,21 +0,0 @@ - - - - - 71dp - 52dp - - diff --git a/library/core/src/main/res/values/ids.xml b/library/core/src/main/res/values/ids.xml deleted file mode 100644 index 61db83825e..0000000000 --- a/library/core/src/main/res/values/ids.xml +++ /dev/null @@ -1,34 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - diff --git a/library/core/src/main/res/values/strings.xml b/library/core/src/main/res/values/strings.xml deleted file mode 100644 index 1e652dddb3..0000000000 --- a/library/core/src/main/res/values/strings.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - Previous track - Next track - Pause - Play - Stop - Rewind - Fast forward - diff --git a/library/core/src/main/res/values/styles.xml b/library/core/src/main/res/values/styles.xml deleted file mode 100644 index a67cffe420..0000000000 --- a/library/core/src/main/res/values/styles.xml +++ /dev/null @@ -1,54 +0,0 @@ - - - - - - - - - - - - - - - - - - - diff --git a/library/ui/build.gradle b/library/ui/build.gradle new file mode 100644 index 0000000000..4c47d44e26 --- /dev/null +++ b/library/ui/build.gradle @@ -0,0 +1,35 @@ +// Copyright (C) 2016 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +apply plugin: 'com.android.library' + +android { + compileSdkVersion project.ext.compileSdkVersion + buildToolsVersion project.ext.buildToolsVersion + + defaultConfig { + minSdkVersion project.ext.minSdkVersion + targetSdkVersion project.ext.targetSdkVersion + } +} + +dependencies { + compile project(':library-core') + compile 'com.android.support:support-annotations:25.2.0' +} + +ext { + releaseArtifact = 'exoplayer-ui' + releaseDescription = 'The ExoPlayer library UI module.' +} +apply from: '../../publish.gradle' From 58ac5720242e3893cecc66f476a862ba164da059 Mon Sep 17 00:00:00 2001 From: olly Date: Mon, 20 Mar 2017 07:56:36 -0700 Subject: [PATCH 007/119] Tell Moe about new UI module + update external settings.gradle ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=150625313 --- library/ui/src/main/AndroidManifest.xml | 17 + .../exoplayer2/ui/AspectRatioFrameLayout.java | 148 ++++ .../exoplayer2/ui/DebugTextViewHelper.java | 181 ++++ .../exoplayer2/ui/PlaybackControlView.java | 812 ++++++++++++++++++ .../exoplayer2/ui/SimpleExoPlayerView.java | 717 ++++++++++++++++ .../exoplayer2/ui/SubtitlePainter.java | 405 +++++++++ .../android/exoplayer2/ui/SubtitleView.java | 265 ++++++ .../exo_controls_fastforward.xml | 25 + .../drawable-anydpi-v21/exo_controls_next.xml | 25 + .../exo_controls_pause.xml | 25 + .../drawable-anydpi-v21/exo_controls_play.xml | 25 + .../exo_controls_previous.xml | 25 + .../exo_controls_rewind.xml | 25 + .../exo_controls_fastforward.png | Bin 0 -> 354 bytes .../res/drawable-hdpi/exo_controls_next.png | Bin 0 -> 323 bytes .../res/drawable-hdpi/exo_controls_pause.png | Bin 0 -> 108 bytes .../res/drawable-hdpi/exo_controls_play.png | Bin 0 -> 286 bytes .../drawable-hdpi/exo_controls_previous.png | Bin 0 -> 292 bytes .../res/drawable-hdpi/exo_controls_rewind.png | Bin 0 -> 347 bytes .../exo_controls_fastforward.png | Bin 0 -> 192 bytes .../res/drawable-ldpi/exo_controls_next.png | Bin 0 -> 167 bytes .../res/drawable-ldpi/exo_controls_pause.png | Bin 0 -> 91 bytes .../res/drawable-ldpi/exo_controls_play.png | Bin 0 -> 182 bytes .../drawable-ldpi/exo_controls_previous.png | Bin 0 -> 187 bytes .../res/drawable-ldpi/exo_controls_rewind.png | Bin 0 -> 214 bytes .../exo_controls_fastforward.png | Bin 0 -> 255 bytes .../res/drawable-mdpi/exo_controls_next.png | Bin 0 -> 276 bytes .../res/drawable-mdpi/exo_controls_pause.png | Bin 0 -> 153 bytes .../res/drawable-mdpi/exo_controls_play.png | Bin 0 -> 228 bytes .../drawable-mdpi/exo_controls_previous.png | Bin 0 -> 227 bytes .../res/drawable-mdpi/exo_controls_rewind.png | Bin 0 -> 273 bytes .../exo_controls_fastforward.png | Bin 0 -> 392 bytes .../res/drawable-xhdpi/exo_controls_next.png | Bin 0 -> 334 bytes .../res/drawable-xhdpi/exo_controls_pause.png | Bin 0 -> 164 bytes .../res/drawable-xhdpi/exo_controls_play.png | Bin 0 -> 343 bytes .../drawable-xhdpi/exo_controls_previous.png | Bin 0 -> 339 bytes .../drawable-xhdpi/exo_controls_rewind.png | Bin 0 -> 400 bytes .../exo_controls_fastforward.png | Bin 0 -> 584 bytes .../res/drawable-xxhdpi/exo_controls_next.png | Bin 0 -> 391 bytes .../drawable-xxhdpi/exo_controls_pause.png | Bin 0 -> 113 bytes .../res/drawable-xxhdpi/exo_controls_play.png | Bin 0 -> 384 bytes .../drawable-xxhdpi/exo_controls_previous.png | Bin 0 -> 464 bytes .../drawable-xxhdpi/exo_controls_rewind.png | Bin 0 -> 571 bytes .../res/layout/exo_playback_control_view.xml | 87 ++ .../res/layout/exo_simple_player_view.xml | 49 ++ library/ui/src/main/res/values-af/strings.xml | 25 + library/ui/src/main/res/values-am/strings.xml | 25 + library/ui/src/main/res/values-ar/strings.xml | 25 + .../ui/src/main/res/values-az-rAZ/strings.xml | 25 + .../src/main/res/values-b+sr+Latn/strings.xml | 25 + .../ui/src/main/res/values-be-rBY/strings.xml | 25 + library/ui/src/main/res/values-bg/strings.xml | 25 + .../ui/src/main/res/values-bn-rBD/strings.xml | 25 + .../ui/src/main/res/values-bs-rBA/strings.xml | 25 + library/ui/src/main/res/values-ca/strings.xml | 25 + library/ui/src/main/res/values-cs/strings.xml | 25 + library/ui/src/main/res/values-da/strings.xml | 25 + library/ui/src/main/res/values-de/strings.xml | 25 + library/ui/src/main/res/values-el/strings.xml | 25 + .../ui/src/main/res/values-en-rAU/strings.xml | 25 + .../ui/src/main/res/values-en-rGB/strings.xml | 25 + .../ui/src/main/res/values-en-rIN/strings.xml | 25 + .../ui/src/main/res/values-es-rUS/strings.xml | 25 + library/ui/src/main/res/values-es/strings.xml | 25 + .../ui/src/main/res/values-et-rEE/strings.xml | 25 + .../ui/src/main/res/values-eu-rES/strings.xml | 25 + library/ui/src/main/res/values-fa/strings.xml | 25 + library/ui/src/main/res/values-fi/strings.xml | 25 + .../ui/src/main/res/values-fr-rCA/strings.xml | 25 + library/ui/src/main/res/values-fr/strings.xml | 25 + .../ui/src/main/res/values-gl-rES/strings.xml | 25 + .../ui/src/main/res/values-gu-rIN/strings.xml | 25 + library/ui/src/main/res/values-hi/strings.xml | 25 + library/ui/src/main/res/values-hr/strings.xml | 25 + library/ui/src/main/res/values-hu/strings.xml | 25 + .../ui/src/main/res/values-hy-rAM/strings.xml | 25 + library/ui/src/main/res/values-in/strings.xml | 25 + .../ui/src/main/res/values-is-rIS/strings.xml | 25 + library/ui/src/main/res/values-it/strings.xml | 25 + library/ui/src/main/res/values-iw/strings.xml | 25 + library/ui/src/main/res/values-ja/strings.xml | 25 + .../ui/src/main/res/values-ka-rGE/strings.xml | 25 + .../ui/src/main/res/values-kk-rKZ/strings.xml | 25 + .../ui/src/main/res/values-km-rKH/strings.xml | 25 + .../ui/src/main/res/values-kn-rIN/strings.xml | 25 + library/ui/src/main/res/values-ko/strings.xml | 25 + .../ui/src/main/res/values-ky-rKG/strings.xml | 25 + .../ui/src/main/res/values-lo-rLA/strings.xml | 25 + library/ui/src/main/res/values-lt/strings.xml | 25 + library/ui/src/main/res/values-lv/strings.xml | 25 + .../ui/src/main/res/values-mk-rMK/strings.xml | 25 + .../ui/src/main/res/values-ml-rIN/strings.xml | 25 + .../ui/src/main/res/values-mn-rMN/strings.xml | 25 + .../ui/src/main/res/values-mr-rIN/strings.xml | 25 + .../ui/src/main/res/values-ms-rMY/strings.xml | 25 + .../ui/src/main/res/values-my-rMM/strings.xml | 25 + library/ui/src/main/res/values-nb/strings.xml | 25 + .../ui/src/main/res/values-ne-rNP/strings.xml | 25 + library/ui/src/main/res/values-nl/strings.xml | 25 + .../ui/src/main/res/values-pa-rIN/strings.xml | 25 + library/ui/src/main/res/values-pl/strings.xml | 25 + .../ui/src/main/res/values-pt-rBR/strings.xml | 25 + .../ui/src/main/res/values-pt-rPT/strings.xml | 25 + library/ui/src/main/res/values-pt/strings.xml | 25 + library/ui/src/main/res/values-ro/strings.xml | 25 + library/ui/src/main/res/values-ru/strings.xml | 25 + .../ui/src/main/res/values-si-rLK/strings.xml | 25 + library/ui/src/main/res/values-sk/strings.xml | 25 + library/ui/src/main/res/values-sl/strings.xml | 25 + .../ui/src/main/res/values-sq-rAL/strings.xml | 25 + library/ui/src/main/res/values-sr/strings.xml | 25 + library/ui/src/main/res/values-sv/strings.xml | 25 + library/ui/src/main/res/values-sw/strings.xml | 25 + .../ui/src/main/res/values-ta-rIN/strings.xml | 25 + .../ui/src/main/res/values-te-rIN/strings.xml | 25 + library/ui/src/main/res/values-th/strings.xml | 25 + library/ui/src/main/res/values-tl/strings.xml | 25 + library/ui/src/main/res/values-tr/strings.xml | 25 + library/ui/src/main/res/values-uk/strings.xml | 25 + .../ui/src/main/res/values-ur-rPK/strings.xml | 25 + .../ui/src/main/res/values-uz-rUZ/strings.xml | 25 + library/ui/src/main/res/values-v11/styles.xml | 24 + library/ui/src/main/res/values-vi/strings.xml | 25 + .../ui/src/main/res/values-zh-rCN/strings.xml | 25 + .../ui/src/main/res/values-zh-rHK/strings.xml | 25 + .../ui/src/main/res/values-zh-rTW/strings.xml | 25 + library/ui/src/main/res/values-zu/strings.xml | 25 + library/ui/src/main/res/values/attrs.xml | 62 ++ library/ui/src/main/res/values/constants.xml | 21 + library/ui/src/main/res/values/ids.xml | 34 + library/ui/src/main/res/values/strings.xml | 24 + library/ui/src/main/res/values/styles.xml | 54 ++ settings.gradle | 2 + 133 files changed, 5077 insertions(+) create mode 100644 library/ui/src/main/AndroidManifest.xml create mode 100644 library/ui/src/main/java/com/google/android/exoplayer2/ui/AspectRatioFrameLayout.java create mode 100644 library/ui/src/main/java/com/google/android/exoplayer2/ui/DebugTextViewHelper.java create mode 100644 library/ui/src/main/java/com/google/android/exoplayer2/ui/PlaybackControlView.java create mode 100644 library/ui/src/main/java/com/google/android/exoplayer2/ui/SimpleExoPlayerView.java create mode 100644 library/ui/src/main/java/com/google/android/exoplayer2/ui/SubtitlePainter.java create mode 100644 library/ui/src/main/java/com/google/android/exoplayer2/ui/SubtitleView.java create mode 100644 library/ui/src/main/res/drawable-anydpi-v21/exo_controls_fastforward.xml create mode 100644 library/ui/src/main/res/drawable-anydpi-v21/exo_controls_next.xml create mode 100644 library/ui/src/main/res/drawable-anydpi-v21/exo_controls_pause.xml create mode 100644 library/ui/src/main/res/drawable-anydpi-v21/exo_controls_play.xml create mode 100644 library/ui/src/main/res/drawable-anydpi-v21/exo_controls_previous.xml create mode 100644 library/ui/src/main/res/drawable-anydpi-v21/exo_controls_rewind.xml create mode 100644 library/ui/src/main/res/drawable-hdpi/exo_controls_fastforward.png create mode 100644 library/ui/src/main/res/drawable-hdpi/exo_controls_next.png create mode 100644 library/ui/src/main/res/drawable-hdpi/exo_controls_pause.png create mode 100644 library/ui/src/main/res/drawable-hdpi/exo_controls_play.png create mode 100644 library/ui/src/main/res/drawable-hdpi/exo_controls_previous.png create mode 100644 library/ui/src/main/res/drawable-hdpi/exo_controls_rewind.png create mode 100644 library/ui/src/main/res/drawable-ldpi/exo_controls_fastforward.png create mode 100644 library/ui/src/main/res/drawable-ldpi/exo_controls_next.png create mode 100644 library/ui/src/main/res/drawable-ldpi/exo_controls_pause.png create mode 100644 library/ui/src/main/res/drawable-ldpi/exo_controls_play.png create mode 100644 library/ui/src/main/res/drawable-ldpi/exo_controls_previous.png create mode 100644 library/ui/src/main/res/drawable-ldpi/exo_controls_rewind.png create mode 100644 library/ui/src/main/res/drawable-mdpi/exo_controls_fastforward.png create mode 100644 library/ui/src/main/res/drawable-mdpi/exo_controls_next.png create mode 100644 library/ui/src/main/res/drawable-mdpi/exo_controls_pause.png create mode 100644 library/ui/src/main/res/drawable-mdpi/exo_controls_play.png create mode 100644 library/ui/src/main/res/drawable-mdpi/exo_controls_previous.png create mode 100644 library/ui/src/main/res/drawable-mdpi/exo_controls_rewind.png create mode 100644 library/ui/src/main/res/drawable-xhdpi/exo_controls_fastforward.png create mode 100644 library/ui/src/main/res/drawable-xhdpi/exo_controls_next.png create mode 100644 library/ui/src/main/res/drawable-xhdpi/exo_controls_pause.png create mode 100644 library/ui/src/main/res/drawable-xhdpi/exo_controls_play.png create mode 100644 library/ui/src/main/res/drawable-xhdpi/exo_controls_previous.png create mode 100644 library/ui/src/main/res/drawable-xhdpi/exo_controls_rewind.png create mode 100644 library/ui/src/main/res/drawable-xxhdpi/exo_controls_fastforward.png create mode 100644 library/ui/src/main/res/drawable-xxhdpi/exo_controls_next.png create mode 100644 library/ui/src/main/res/drawable-xxhdpi/exo_controls_pause.png create mode 100644 library/ui/src/main/res/drawable-xxhdpi/exo_controls_play.png create mode 100644 library/ui/src/main/res/drawable-xxhdpi/exo_controls_previous.png create mode 100644 library/ui/src/main/res/drawable-xxhdpi/exo_controls_rewind.png create mode 100644 library/ui/src/main/res/layout/exo_playback_control_view.xml create mode 100644 library/ui/src/main/res/layout/exo_simple_player_view.xml create mode 100644 library/ui/src/main/res/values-af/strings.xml create mode 100644 library/ui/src/main/res/values-am/strings.xml create mode 100644 library/ui/src/main/res/values-ar/strings.xml create mode 100644 library/ui/src/main/res/values-az-rAZ/strings.xml create mode 100644 library/ui/src/main/res/values-b+sr+Latn/strings.xml create mode 100644 library/ui/src/main/res/values-be-rBY/strings.xml create mode 100644 library/ui/src/main/res/values-bg/strings.xml create mode 100644 library/ui/src/main/res/values-bn-rBD/strings.xml create mode 100644 library/ui/src/main/res/values-bs-rBA/strings.xml create mode 100644 library/ui/src/main/res/values-ca/strings.xml create mode 100644 library/ui/src/main/res/values-cs/strings.xml create mode 100644 library/ui/src/main/res/values-da/strings.xml create mode 100644 library/ui/src/main/res/values-de/strings.xml create mode 100644 library/ui/src/main/res/values-el/strings.xml create mode 100644 library/ui/src/main/res/values-en-rAU/strings.xml create mode 100644 library/ui/src/main/res/values-en-rGB/strings.xml create mode 100644 library/ui/src/main/res/values-en-rIN/strings.xml create mode 100644 library/ui/src/main/res/values-es-rUS/strings.xml create mode 100644 library/ui/src/main/res/values-es/strings.xml create mode 100644 library/ui/src/main/res/values-et-rEE/strings.xml create mode 100644 library/ui/src/main/res/values-eu-rES/strings.xml create mode 100644 library/ui/src/main/res/values-fa/strings.xml create mode 100644 library/ui/src/main/res/values-fi/strings.xml create mode 100644 library/ui/src/main/res/values-fr-rCA/strings.xml create mode 100644 library/ui/src/main/res/values-fr/strings.xml create mode 100644 library/ui/src/main/res/values-gl-rES/strings.xml create mode 100644 library/ui/src/main/res/values-gu-rIN/strings.xml create mode 100644 library/ui/src/main/res/values-hi/strings.xml create mode 100644 library/ui/src/main/res/values-hr/strings.xml create mode 100644 library/ui/src/main/res/values-hu/strings.xml create mode 100644 library/ui/src/main/res/values-hy-rAM/strings.xml create mode 100644 library/ui/src/main/res/values-in/strings.xml create mode 100644 library/ui/src/main/res/values-is-rIS/strings.xml create mode 100644 library/ui/src/main/res/values-it/strings.xml create mode 100644 library/ui/src/main/res/values-iw/strings.xml create mode 100644 library/ui/src/main/res/values-ja/strings.xml create mode 100644 library/ui/src/main/res/values-ka-rGE/strings.xml create mode 100644 library/ui/src/main/res/values-kk-rKZ/strings.xml create mode 100644 library/ui/src/main/res/values-km-rKH/strings.xml create mode 100644 library/ui/src/main/res/values-kn-rIN/strings.xml create mode 100644 library/ui/src/main/res/values-ko/strings.xml create mode 100644 library/ui/src/main/res/values-ky-rKG/strings.xml create mode 100644 library/ui/src/main/res/values-lo-rLA/strings.xml create mode 100644 library/ui/src/main/res/values-lt/strings.xml create mode 100644 library/ui/src/main/res/values-lv/strings.xml create mode 100644 library/ui/src/main/res/values-mk-rMK/strings.xml create mode 100644 library/ui/src/main/res/values-ml-rIN/strings.xml create mode 100644 library/ui/src/main/res/values-mn-rMN/strings.xml create mode 100644 library/ui/src/main/res/values-mr-rIN/strings.xml create mode 100644 library/ui/src/main/res/values-ms-rMY/strings.xml create mode 100644 library/ui/src/main/res/values-my-rMM/strings.xml create mode 100644 library/ui/src/main/res/values-nb/strings.xml create mode 100644 library/ui/src/main/res/values-ne-rNP/strings.xml create mode 100644 library/ui/src/main/res/values-nl/strings.xml create mode 100644 library/ui/src/main/res/values-pa-rIN/strings.xml create mode 100644 library/ui/src/main/res/values-pl/strings.xml create mode 100644 library/ui/src/main/res/values-pt-rBR/strings.xml create mode 100644 library/ui/src/main/res/values-pt-rPT/strings.xml create mode 100644 library/ui/src/main/res/values-pt/strings.xml create mode 100644 library/ui/src/main/res/values-ro/strings.xml create mode 100644 library/ui/src/main/res/values-ru/strings.xml create mode 100644 library/ui/src/main/res/values-si-rLK/strings.xml create mode 100644 library/ui/src/main/res/values-sk/strings.xml create mode 100644 library/ui/src/main/res/values-sl/strings.xml create mode 100644 library/ui/src/main/res/values-sq-rAL/strings.xml create mode 100644 library/ui/src/main/res/values-sr/strings.xml create mode 100644 library/ui/src/main/res/values-sv/strings.xml create mode 100644 library/ui/src/main/res/values-sw/strings.xml create mode 100644 library/ui/src/main/res/values-ta-rIN/strings.xml create mode 100644 library/ui/src/main/res/values-te-rIN/strings.xml create mode 100644 library/ui/src/main/res/values-th/strings.xml create mode 100644 library/ui/src/main/res/values-tl/strings.xml create mode 100644 library/ui/src/main/res/values-tr/strings.xml create mode 100644 library/ui/src/main/res/values-uk/strings.xml create mode 100644 library/ui/src/main/res/values-ur-rPK/strings.xml create mode 100644 library/ui/src/main/res/values-uz-rUZ/strings.xml create mode 100644 library/ui/src/main/res/values-v11/styles.xml create mode 100644 library/ui/src/main/res/values-vi/strings.xml create mode 100644 library/ui/src/main/res/values-zh-rCN/strings.xml create mode 100644 library/ui/src/main/res/values-zh-rHK/strings.xml create mode 100644 library/ui/src/main/res/values-zh-rTW/strings.xml create mode 100644 library/ui/src/main/res/values-zu/strings.xml create mode 100644 library/ui/src/main/res/values/attrs.xml create mode 100644 library/ui/src/main/res/values/constants.xml create mode 100644 library/ui/src/main/res/values/ids.xml create mode 100644 library/ui/src/main/res/values/strings.xml create mode 100644 library/ui/src/main/res/values/styles.xml diff --git a/library/ui/src/main/AndroidManifest.xml b/library/ui/src/main/AndroidManifest.xml new file mode 100644 index 0000000000..72189e22a7 --- /dev/null +++ b/library/ui/src/main/AndroidManifest.xml @@ -0,0 +1,17 @@ + + + + diff --git a/library/ui/src/main/java/com/google/android/exoplayer2/ui/AspectRatioFrameLayout.java b/library/ui/src/main/java/com/google/android/exoplayer2/ui/AspectRatioFrameLayout.java new file mode 100644 index 0000000000..38294f5208 --- /dev/null +++ b/library/ui/src/main/java/com/google/android/exoplayer2/ui/AspectRatioFrameLayout.java @@ -0,0 +1,148 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.exoplayer2.ui; + +import android.content.Context; +import android.content.res.TypedArray; +import android.support.annotation.IntDef; +import android.util.AttributeSet; +import android.widget.FrameLayout; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * A {@link FrameLayout} that resizes itself to match a specified aspect ratio. + */ +public final class AspectRatioFrameLayout extends FrameLayout { + + /** + * Resize modes for {@link AspectRatioFrameLayout}. + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({RESIZE_MODE_FIT, RESIZE_MODE_FIXED_WIDTH, RESIZE_MODE_FIXED_HEIGHT, RESIZE_MODE_FILL}) + public @interface ResizeMode {} + + /** + * Either the width or height is decreased to obtain the desired aspect ratio. + */ + public static final int RESIZE_MODE_FIT = 0; + /** + * The width is fixed and the height is increased or decreased to obtain the desired aspect ratio. + */ + public static final int RESIZE_MODE_FIXED_WIDTH = 1; + /** + * The height is fixed and the width is increased or decreased to obtain the desired aspect ratio. + */ + public static final int RESIZE_MODE_FIXED_HEIGHT = 2; + /** + * The specified aspect ratio is ignored. + */ + public static final int RESIZE_MODE_FILL = 3; + + /** + * The {@link FrameLayout} will not resize itself if the fractional difference between its natural + * aspect ratio and the requested aspect ratio falls below this threshold. + *

+ * This tolerance allows the view to occupy the whole of the screen when the requested aspect + * ratio is very close, but not exactly equal to, the aspect ratio of the screen. This may reduce + * the number of view layers that need to be composited by the underlying system, which can help + * to reduce power consumption. + */ + private static final float MAX_ASPECT_RATIO_DEFORMATION_FRACTION = 0.01f; + + private float videoAspectRatio; + private int resizeMode; + + public AspectRatioFrameLayout(Context context) { + this(context, null); + } + + public AspectRatioFrameLayout(Context context, AttributeSet attrs) { + super(context, attrs); + resizeMode = RESIZE_MODE_FIT; + if (attrs != null) { + TypedArray a = context.getTheme().obtainStyledAttributes(attrs, + R.styleable.AspectRatioFrameLayout, 0, 0); + try { + resizeMode = a.getInt(R.styleable.AspectRatioFrameLayout_resize_mode, RESIZE_MODE_FIT); + } finally { + a.recycle(); + } + } + } + + /** + * Set the aspect ratio that this view should satisfy. + * + * @param widthHeightRatio The width to height ratio. + */ + public void setAspectRatio(float widthHeightRatio) { + if (this.videoAspectRatio != widthHeightRatio) { + this.videoAspectRatio = widthHeightRatio; + requestLayout(); + } + } + + /** + * Sets the resize mode. + * + * @param resizeMode The resize mode. + */ + public void setResizeMode(@ResizeMode int resizeMode) { + if (this.resizeMode != resizeMode) { + this.resizeMode = resizeMode; + requestLayout(); + } + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + if (resizeMode == RESIZE_MODE_FILL || videoAspectRatio <= 0) { + // Aspect ratio not set. + return; + } + + int width = getMeasuredWidth(); + int height = getMeasuredHeight(); + float viewAspectRatio = (float) width / height; + float aspectDeformation = videoAspectRatio / viewAspectRatio - 1; + if (Math.abs(aspectDeformation) <= MAX_ASPECT_RATIO_DEFORMATION_FRACTION) { + // We're within the allowed tolerance. + return; + } + + switch (resizeMode) { + case RESIZE_MODE_FIXED_WIDTH: + height = (int) (width / videoAspectRatio); + break; + case RESIZE_MODE_FIXED_HEIGHT: + width = (int) (height * videoAspectRatio); + break; + default: + if (aspectDeformation > 0) { + height = (int) (width / videoAspectRatio); + } else { + width = (int) (height * videoAspectRatio); + } + break; + } + super.onMeasure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), + MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY)); + } + +} diff --git a/library/ui/src/main/java/com/google/android/exoplayer2/ui/DebugTextViewHelper.java b/library/ui/src/main/java/com/google/android/exoplayer2/ui/DebugTextViewHelper.java new file mode 100644 index 0000000000..1bf5b59a4a --- /dev/null +++ b/library/ui/src/main/java/com/google/android/exoplayer2/ui/DebugTextViewHelper.java @@ -0,0 +1,181 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.exoplayer2.ui; + +import android.widget.TextView; +import com.google.android.exoplayer2.ExoPlaybackException; +import com.google.android.exoplayer2.ExoPlayer; +import com.google.android.exoplayer2.Format; +import com.google.android.exoplayer2.SimpleExoPlayer; +import com.google.android.exoplayer2.Timeline; +import com.google.android.exoplayer2.decoder.DecoderCounters; +import com.google.android.exoplayer2.source.TrackGroupArray; +import com.google.android.exoplayer2.trackselection.TrackSelectionArray; + +/** + * A helper class for periodically updating a {@link TextView} with debug information obtained from + * a {@link SimpleExoPlayer}. + */ +public final class DebugTextViewHelper implements Runnable, ExoPlayer.EventListener { + + private static final int REFRESH_INTERVAL_MS = 1000; + + private final SimpleExoPlayer player; + private final TextView textView; + + private boolean started; + + /** + * @param player The {@link SimpleExoPlayer} from which debug information should be obtained. + * @param textView The {@link TextView} that should be updated to display the information. + */ + public DebugTextViewHelper(SimpleExoPlayer player, TextView textView) { + this.player = player; + this.textView = textView; + } + + /** + * Starts periodic updates of the {@link TextView}. Must be called from the application's main + * thread. + */ + public void start() { + if (started) { + return; + } + started = true; + player.addListener(this); + updateAndPost(); + } + + /** + * Stops periodic updates of the {@link TextView}. Must be called from the application's main + * thread. + */ + public void stop() { + if (!started) { + return; + } + started = false; + player.removeListener(this); + textView.removeCallbacks(this); + } + + // ExoPlayer.EventListener implementation. + + @Override + public void onLoadingChanged(boolean isLoading) { + // Do nothing. + } + + @Override + public void onPlayerStateChanged(boolean playWhenReady, int playbackState) { + updateAndPost(); + } + + @Override + public void onPositionDiscontinuity() { + updateAndPost(); + } + + @Override + public void onTimelineChanged(Timeline timeline, Object manifest) { + // Do nothing. + } + + @Override + public void onPlayerError(ExoPlaybackException error) { + // Do nothing. + } + + @Override + public void onTracksChanged(TrackGroupArray tracks, TrackSelectionArray selections) { + // Do nothing. + } + + // Runnable implementation. + + @Override + public void run() { + updateAndPost(); + } + + // Private methods. + + private void updateAndPost() { + textView.setText(getPlayerStateString() + getPlayerWindowIndexString() + getVideoString() + + getAudioString()); + textView.removeCallbacks(this); + textView.postDelayed(this, REFRESH_INTERVAL_MS); + } + + private String getPlayerStateString() { + String text = "playWhenReady:" + player.getPlayWhenReady() + " playbackState:"; + switch (player.getPlaybackState()) { + case ExoPlayer.STATE_BUFFERING: + text += "buffering"; + break; + case ExoPlayer.STATE_ENDED: + text += "ended"; + break; + case ExoPlayer.STATE_IDLE: + text += "idle"; + break; + case ExoPlayer.STATE_READY: + text += "ready"; + break; + default: + text += "unknown"; + break; + } + return text; + } + + private String getPlayerWindowIndexString() { + return " window:" + player.getCurrentWindowIndex(); + } + + private String getVideoString() { + Format format = player.getVideoFormat(); + if (format == null) { + return ""; + } + return "\n" + format.sampleMimeType + "(id:" + format.id + " r:" + format.width + "x" + + format.height + getDecoderCountersBufferCountString(player.getVideoDecoderCounters()) + + ")"; + } + + private String getAudioString() { + Format format = player.getAudioFormat(); + if (format == null) { + return ""; + } + return "\n" + format.sampleMimeType + "(id:" + format.id + " hz:" + format.sampleRate + " ch:" + + format.channelCount + + getDecoderCountersBufferCountString(player.getAudioDecoderCounters()) + ")"; + } + + private static String getDecoderCountersBufferCountString(DecoderCounters counters) { + if (counters == null) { + return ""; + } + counters.ensureUpdated(); + return " rb:" + counters.renderedOutputBufferCount + + " sb:" + counters.skippedOutputBufferCount + + " db:" + counters.droppedOutputBufferCount + + " mcdb:" + counters.maxConsecutiveDroppedOutputBufferCount; + } + +} diff --git a/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlaybackControlView.java b/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlaybackControlView.java new file mode 100644 index 0000000000..88a65589fc --- /dev/null +++ b/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlaybackControlView.java @@ -0,0 +1,812 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.exoplayer2.ui; + +import android.annotation.TargetApi; +import android.content.Context; +import android.content.res.TypedArray; +import android.os.SystemClock; +import android.util.AttributeSet; +import android.view.KeyEvent; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.FrameLayout; +import android.widget.SeekBar; +import android.widget.TextView; +import com.google.android.exoplayer2.C; +import com.google.android.exoplayer2.ExoPlaybackException; +import com.google.android.exoplayer2.ExoPlayer; +import com.google.android.exoplayer2.Timeline; +import com.google.android.exoplayer2.source.TrackGroupArray; +import com.google.android.exoplayer2.trackselection.TrackSelectionArray; +import com.google.android.exoplayer2.util.Util; +import java.util.Formatter; +import java.util.Locale; + +/** + * A view for controlling {@link ExoPlayer} instances. + *

+ * A PlaybackControlView can be customized by setting attributes (or calling corresponding methods), + * overriding the view's layout file or by specifying a custom view layout file, as outlined below. + * + *

Attributes

+ * The following attributes can be set on a PlaybackControlView when used in a layout XML file: + *

+ *

    + *
  • {@code show_timeout} - The time between the last user interaction and the controls + * being automatically hidden, in milliseconds. Use zero if the controls should not + * automatically timeout. + *
      + *
    • Corresponding method: {@link #setShowTimeoutMs(int)}
    • + *
    • Default: {@link #DEFAULT_SHOW_TIMEOUT_MS}
    • + *
    + *
  • + *
  • {@code rewind_increment} - The duration of the rewind applied when the user taps the + * rewind button, in milliseconds. Use zero to disable the rewind button. + *
      + *
    • Corresponding method: {@link #setRewindIncrementMs(int)}
    • + *
    • Default: {@link #DEFAULT_REWIND_MS}
    • + *
    + *
  • + *
  • {@code fastforward_increment} - Like {@code rewind_increment}, but for fast forward. + *
      + *
    • Corresponding method: {@link #setFastForwardIncrementMs(int)}
    • + *
    • Default: {@link #DEFAULT_FAST_FORWARD_MS}
    • + *
    + *
  • + *
  • {@code controller_layout_id} - Specifies the id of the layout to be inflated. See + * below for more details. + *
      + *
    • Corresponding method: None
    • + *
    • Default: {@code R.id.exo_playback_control_view}
    • + *
    + *
  • + *
+ * + *

Overriding the layout file

+ * To customize the layout of PlaybackControlView throughout your app, or just for certain + * configurations, you can define {@code exo_playback_control_view.xml} layout files in your + * application {@code res/layout*} directories. These layouts will override the one provided by the + * ExoPlayer library, and will be inflated for use by PlaybackControlView. The view identifies and + * binds its children by looking for the following ids: + *

+ *

    + *
  • {@code exo_play} - The play button. + *
      + *
    • Type: {@link View}
    • + *
    + *
  • + *
  • {@code exo_pause} - The pause button. + *
      + *
    • Type: {@link View}
    • + *
    + *
  • + *
  • {@code exo_ffwd} - The fast forward button. + *
      + *
    • Type: {@link View}
    • + *
    + *
  • + *
  • {@code exo_rew} - The rewind button. + *
      + *
    • Type: {@link View}
    • + *
    + *
  • + *
  • {@code exo_prev} - The previous track button. + *
      + *
    • Type: {@link View}
    • + *
    + *
  • + *
  • {@code exo_next} - The next track button. + *
      + *
    • Type: {@link View}
    • + *
    + *
  • + *
  • {@code exo_position} - Text view displaying the current playback position. + *
      + *
    • Type: {@link TextView}
    • + *
    + *
  • + *
  • {@code exo_duration} - Text view displaying the current media duration. + *
      + *
    • Type: {@link TextView}
    • + *
    + *
  • + *
  • {@code exo_progress} - Seek bar that's updated during playback and allows seeking. + *
      + *
    • Type: {@link SeekBar}
    • + *
    + *
  • + *
+ *

+ * All child views are optional and so can be omitted if not required, however where defined they + * must be of the expected type. + * + *

Specifying a custom layout file

+ * Defining your own {@code exo_playback_control_view.xml} is useful to customize the layout of + * PlaybackControlView throughout your application. It's also possible to customize the layout for a + * single instance in a layout file. This is achieved by setting the {@code controller_layout_id} + * attribute on a PlaybackControlView. This will cause the specified layout to be inflated instead + * of {@code exo_playback_control_view.xml} for only the instance on which the attribute is set. + */ +public class PlaybackControlView extends FrameLayout { + + /** + * Listener to be notified about changes of the visibility of the UI control. + */ + public interface VisibilityListener { + + /** + * Called when the visibility changes. + * + * @param visibility The new visibility. Either {@link View#VISIBLE} or {@link View#GONE}. + */ + void onVisibilityChange(int visibility); + + } + + /** + * Dispatches seek operations to the player. + */ + public interface SeekDispatcher { + + /** + * @param player The player to seek. + * @param windowIndex The index of the window. + * @param positionMs The seek position in the specified window, or {@link C#TIME_UNSET} to seek + * to the window's default position. + * @return True if the seek was dispatched. False otherwise. + */ + boolean dispatchSeek(ExoPlayer player, int windowIndex, long positionMs); + + } + + /** + * Default {@link SeekDispatcher} that dispatches seeks to the player without modification. + */ + public static final SeekDispatcher DEFAULT_SEEK_DISPATCHER = new SeekDispatcher() { + + @Override + public boolean dispatchSeek(ExoPlayer player, int windowIndex, long positionMs) { + player.seekTo(windowIndex, positionMs); + return true; + } + + }; + + public static final int DEFAULT_FAST_FORWARD_MS = 15000; + public static final int DEFAULT_REWIND_MS = 5000; + public static final int DEFAULT_SHOW_TIMEOUT_MS = 5000; + + private static final int PROGRESS_BAR_MAX = 1000; + private static final long MAX_POSITION_FOR_SEEK_TO_PREVIOUS = 3000; + + private final ComponentListener componentListener; + private final View previousButton; + private final View nextButton; + private final View playButton; + private final View pauseButton; + private final View fastForwardButton; + private final View rewindButton; + private final TextView durationView; + private final TextView positionView; + private final SeekBar progressBar; + private final StringBuilder formatBuilder; + private final Formatter formatter; + private final Timeline.Window currentWindow; + + private ExoPlayer player; + private SeekDispatcher seekDispatcher; + private VisibilityListener visibilityListener; + + private boolean isAttachedToWindow; + private boolean dragging; + private int rewindMs; + private int fastForwardMs; + private int showTimeoutMs; + private long hideAtMs; + + private final Runnable updateProgressAction = new Runnable() { + @Override + public void run() { + updateProgress(); + } + }; + + private final Runnable hideAction = new Runnable() { + @Override + public void run() { + hide(); + } + }; + + public PlaybackControlView(Context context) { + this(context, null); + } + + public PlaybackControlView(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public PlaybackControlView(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + + int controllerLayoutId = R.layout.exo_playback_control_view; + rewindMs = DEFAULT_REWIND_MS; + fastForwardMs = DEFAULT_FAST_FORWARD_MS; + showTimeoutMs = DEFAULT_SHOW_TIMEOUT_MS; + if (attrs != null) { + TypedArray a = context.getTheme().obtainStyledAttributes(attrs, + R.styleable.PlaybackControlView, 0, 0); + try { + rewindMs = a.getInt(R.styleable.PlaybackControlView_rewind_increment, rewindMs); + fastForwardMs = a.getInt(R.styleable.PlaybackControlView_fastforward_increment, + fastForwardMs); + showTimeoutMs = a.getInt(R.styleable.PlaybackControlView_show_timeout, showTimeoutMs); + controllerLayoutId = a.getResourceId(R.styleable.PlaybackControlView_controller_layout_id, + controllerLayoutId); + } finally { + a.recycle(); + } + } + currentWindow = new Timeline.Window(); + formatBuilder = new StringBuilder(); + formatter = new Formatter(formatBuilder, Locale.getDefault()); + componentListener = new ComponentListener(); + seekDispatcher = DEFAULT_SEEK_DISPATCHER; + + LayoutInflater.from(context).inflate(controllerLayoutId, this); + setDescendantFocusability(FOCUS_AFTER_DESCENDANTS); + + durationView = (TextView) findViewById(R.id.exo_duration); + positionView = (TextView) findViewById(R.id.exo_position); + progressBar = (SeekBar) findViewById(R.id.exo_progress); + if (progressBar != null) { + progressBar.setOnSeekBarChangeListener(componentListener); + progressBar.setMax(PROGRESS_BAR_MAX); + } + playButton = findViewById(R.id.exo_play); + if (playButton != null) { + playButton.setOnClickListener(componentListener); + } + pauseButton = findViewById(R.id.exo_pause); + if (pauseButton != null) { + pauseButton.setOnClickListener(componentListener); + } + previousButton = findViewById(R.id.exo_prev); + if (previousButton != null) { + previousButton.setOnClickListener(componentListener); + } + nextButton = findViewById(R.id.exo_next); + if (nextButton != null) { + nextButton.setOnClickListener(componentListener); + } + rewindButton = findViewById(R.id.exo_rew); + if (rewindButton != null) { + rewindButton.setOnClickListener(componentListener); + } + fastForwardButton = findViewById(R.id.exo_ffwd); + if (fastForwardButton != null) { + fastForwardButton.setOnClickListener(componentListener); + } + } + + /** + * Returns the player currently being controlled by this view, or null if no player is set. + */ + public ExoPlayer getPlayer() { + return player; + } + + /** + * Sets the {@link ExoPlayer} to control. + * + * @param player the {@code ExoPlayer} to control. + */ + public void setPlayer(ExoPlayer player) { + if (this.player == player) { + return; + } + if (this.player != null) { + this.player.removeListener(componentListener); + } + this.player = player; + if (player != null) { + player.addListener(componentListener); + } + updateAll(); + } + + /** + * Sets the {@link VisibilityListener}. + * + * @param listener The listener to be notified about visibility changes. + */ + public void setVisibilityListener(VisibilityListener listener) { + this.visibilityListener = listener; + } + + /** + * Sets the {@link SeekDispatcher}. + * + * @param seekDispatcher The {@link SeekDispatcher}, or null to use + * {@link #DEFAULT_SEEK_DISPATCHER}. + */ + public void setSeekDispatcher(SeekDispatcher seekDispatcher) { + this.seekDispatcher = seekDispatcher == null ? DEFAULT_SEEK_DISPATCHER : seekDispatcher; + } + + /** + * Sets the rewind increment in milliseconds. + * + * @param rewindMs The rewind increment in milliseconds. A non-positive value will cause the + * rewind button to be disabled. + */ + public void setRewindIncrementMs(int rewindMs) { + this.rewindMs = rewindMs; + updateNavigation(); + } + + /** + * Sets the fast forward increment in milliseconds. + * + * @param fastForwardMs The fast forward increment in milliseconds. A non-positive value will + * cause the fast forward button to be disabled. + */ + public void setFastForwardIncrementMs(int fastForwardMs) { + this.fastForwardMs = fastForwardMs; + updateNavigation(); + } + + /** + * Returns the playback controls timeout. The playback controls are automatically hidden after + * this duration of time has elapsed without user input. + * + * @return The duration in milliseconds. A non-positive value indicates that the controls will + * remain visible indefinitely. + */ + public int getShowTimeoutMs() { + return showTimeoutMs; + } + + /** + * Sets the playback controls timeout. The playback controls are automatically hidden after this + * duration of time has elapsed without user input. + * + * @param showTimeoutMs The duration in milliseconds. A non-positive value will cause the controls + * to remain visible indefinitely. + */ + public void setShowTimeoutMs(int showTimeoutMs) { + this.showTimeoutMs = showTimeoutMs; + } + + /** + * Shows the playback controls. If {@link #getShowTimeoutMs()} is positive then the controls will + * be automatically hidden after this duration of time has elapsed without user input. + */ + public void show() { + if (!isVisible()) { + setVisibility(VISIBLE); + if (visibilityListener != null) { + visibilityListener.onVisibilityChange(getVisibility()); + } + updateAll(); + requestPlayPauseFocus(); + } + // Call hideAfterTimeout even if already visible to reset the timeout. + hideAfterTimeout(); + } + + /** + * Hides the controller. + */ + public void hide() { + if (isVisible()) { + setVisibility(GONE); + if (visibilityListener != null) { + visibilityListener.onVisibilityChange(getVisibility()); + } + removeCallbacks(updateProgressAction); + removeCallbacks(hideAction); + hideAtMs = C.TIME_UNSET; + } + } + + /** + * Returns whether the controller is currently visible. + */ + public boolean isVisible() { + return getVisibility() == VISIBLE; + } + + private void hideAfterTimeout() { + removeCallbacks(hideAction); + if (showTimeoutMs > 0) { + hideAtMs = SystemClock.uptimeMillis() + showTimeoutMs; + if (isAttachedToWindow) { + postDelayed(hideAction, showTimeoutMs); + } + } else { + hideAtMs = C.TIME_UNSET; + } + } + + private void updateAll() { + updatePlayPauseButton(); + updateNavigation(); + updateProgress(); + } + + private void updatePlayPauseButton() { + if (!isVisible() || !isAttachedToWindow) { + return; + } + boolean requestPlayPauseFocus = false; + boolean playing = player != null && player.getPlayWhenReady(); + if (playButton != null) { + requestPlayPauseFocus |= playing && playButton.isFocused(); + playButton.setVisibility(playing ? View.GONE : View.VISIBLE); + } + if (pauseButton != null) { + requestPlayPauseFocus |= !playing && pauseButton.isFocused(); + pauseButton.setVisibility(!playing ? View.GONE : View.VISIBLE); + } + if (requestPlayPauseFocus) { + requestPlayPauseFocus(); + } + } + + private void updateNavigation() { + if (!isVisible() || !isAttachedToWindow) { + return; + } + Timeline currentTimeline = player != null ? player.getCurrentTimeline() : null; + boolean haveNonEmptyTimeline = currentTimeline != null && !currentTimeline.isEmpty(); + boolean isSeekable = false; + boolean enablePrevious = false; + boolean enableNext = false; + if (haveNonEmptyTimeline) { + int currentWindowIndex = player.getCurrentWindowIndex(); + currentTimeline.getWindow(currentWindowIndex, currentWindow); + isSeekable = currentWindow.isSeekable; + enablePrevious = currentWindowIndex > 0 || isSeekable || !currentWindow.isDynamic; + enableNext = (currentWindowIndex < currentTimeline.getWindowCount() - 1) + || currentWindow.isDynamic; + } + setButtonEnabled(enablePrevious , previousButton); + setButtonEnabled(enableNext, nextButton); + setButtonEnabled(fastForwardMs > 0 && isSeekable, fastForwardButton); + setButtonEnabled(rewindMs > 0 && isSeekable, rewindButton); + if (progressBar != null) { + progressBar.setEnabled(isSeekable); + } + } + + private void updateProgress() { + if (!isVisible() || !isAttachedToWindow) { + return; + } + long duration = player == null ? 0 : player.getDuration(); + long position = player == null ? 0 : player.getCurrentPosition(); + if (durationView != null) { + durationView.setText(stringForTime(duration)); + } + if (positionView != null && !dragging) { + positionView.setText(stringForTime(position)); + } + + if (progressBar != null) { + if (!dragging) { + progressBar.setProgress(progressBarValue(position)); + } + long bufferedPosition = player == null ? 0 : player.getBufferedPosition(); + progressBar.setSecondaryProgress(progressBarValue(bufferedPosition)); + // Remove scheduled updates. + } + removeCallbacks(updateProgressAction); + // Schedule an update if necessary. + int playbackState = player == null ? ExoPlayer.STATE_IDLE : player.getPlaybackState(); + if (playbackState != ExoPlayer.STATE_IDLE && playbackState != ExoPlayer.STATE_ENDED) { + long delayMs; + if (player.getPlayWhenReady() && playbackState == ExoPlayer.STATE_READY) { + delayMs = 1000 - (position % 1000); + if (delayMs < 200) { + delayMs += 1000; + } + } else { + delayMs = 1000; + } + postDelayed(updateProgressAction, delayMs); + } + } + + private void requestPlayPauseFocus() { + boolean playing = player != null && player.getPlayWhenReady(); + if (!playing && playButton != null) { + playButton.requestFocus(); + } else if (playing && pauseButton != null) { + pauseButton.requestFocus(); + } + } + + private void setButtonEnabled(boolean enabled, View view) { + if (view == null) { + return; + } + view.setEnabled(enabled); + if (Util.SDK_INT >= 11) { + setViewAlphaV11(view, enabled ? 1f : 0.3f); + view.setVisibility(VISIBLE); + } else { + view.setVisibility(enabled ? VISIBLE : INVISIBLE); + } + } + + @TargetApi(11) + private void setViewAlphaV11(View view, float alpha) { + view.setAlpha(alpha); + } + + private String stringForTime(long timeMs) { + if (timeMs == C.TIME_UNSET) { + timeMs = 0; + } + long totalSeconds = (timeMs + 500) / 1000; + long seconds = totalSeconds % 60; + long minutes = (totalSeconds / 60) % 60; + long hours = totalSeconds / 3600; + formatBuilder.setLength(0); + return hours > 0 ? formatter.format("%d:%02d:%02d", hours, minutes, seconds).toString() + : formatter.format("%02d:%02d", minutes, seconds).toString(); + } + + private int progressBarValue(long position) { + long duration = player == null ? C.TIME_UNSET : player.getDuration(); + return duration == C.TIME_UNSET || duration == 0 ? 0 + : (int) ((position * PROGRESS_BAR_MAX) / duration); + } + + private long positionValue(int progress) { + long duration = player == null ? C.TIME_UNSET : player.getDuration(); + return duration == C.TIME_UNSET ? 0 : ((duration * progress) / PROGRESS_BAR_MAX); + } + + private void previous() { + Timeline currentTimeline = player.getCurrentTimeline(); + if (currentTimeline.isEmpty()) { + return; + } + int currentWindowIndex = player.getCurrentWindowIndex(); + currentTimeline.getWindow(currentWindowIndex, currentWindow); + if (currentWindowIndex > 0 && (player.getCurrentPosition() <= MAX_POSITION_FOR_SEEK_TO_PREVIOUS + || (currentWindow.isDynamic && !currentWindow.isSeekable))) { + seekTo(currentWindowIndex - 1, C.TIME_UNSET); + } else { + seekTo(0); + } + } + + private void next() { + Timeline currentTimeline = player.getCurrentTimeline(); + if (currentTimeline.isEmpty()) { + return; + } + int currentWindowIndex = player.getCurrentWindowIndex(); + if (currentWindowIndex < currentTimeline.getWindowCount() - 1) { + seekTo(currentWindowIndex + 1, C.TIME_UNSET); + } else if (currentTimeline.getWindow(currentWindowIndex, currentWindow, false).isDynamic) { + seekTo(currentWindowIndex, C.TIME_UNSET); + } + } + + private void rewind() { + if (rewindMs <= 0) { + return; + } + seekTo(Math.max(player.getCurrentPosition() - rewindMs, 0)); + } + + private void fastForward() { + if (fastForwardMs <= 0) { + return; + } + seekTo(Math.min(player.getCurrentPosition() + fastForwardMs, player.getDuration())); + } + + private void seekTo(long positionMs) { + seekTo(player.getCurrentWindowIndex(), positionMs); + } + + private void seekTo(int windowIndex, long positionMs) { + boolean dispatched = seekDispatcher.dispatchSeek(player, windowIndex, positionMs); + if (!dispatched) { + // The seek wasn't dispatched. If the progress bar was dragged by the user to perform the + // seek then it'll now be in the wrong position. Trigger a progress update to snap it back. + updateProgress(); + } + } + + @Override + public void onAttachedToWindow() { + super.onAttachedToWindow(); + isAttachedToWindow = true; + if (hideAtMs != C.TIME_UNSET) { + long delayMs = hideAtMs - SystemClock.uptimeMillis(); + if (delayMs <= 0) { + hide(); + } else { + postDelayed(hideAction, delayMs); + } + } + updateAll(); + } + + @Override + public void onDetachedFromWindow() { + super.onDetachedFromWindow(); + isAttachedToWindow = false; + removeCallbacks(updateProgressAction); + removeCallbacks(hideAction); + } + + @Override + public boolean dispatchKeyEvent(KeyEvent event) { + boolean handled = dispatchMediaKeyEvent(event) || super.dispatchKeyEvent(event); + if (handled) { + show(); + } + return handled; + } + + /** + * Called to process media key events. Any {@link KeyEvent} can be passed but only media key + * events will be handled. + * + * @param event A key event. + * @return Whether the key event was handled. + */ + public boolean dispatchMediaKeyEvent(KeyEvent event) { + int keyCode = event.getKeyCode(); + if (player == null || !isHandledMediaKey(keyCode)) { + return false; + } + if (event.getAction() == KeyEvent.ACTION_DOWN) { + switch (keyCode) { + case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD: + fastForward(); + break; + case KeyEvent.KEYCODE_MEDIA_REWIND: + rewind(); + break; + case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE: + player.setPlayWhenReady(!player.getPlayWhenReady()); + break; + case KeyEvent.KEYCODE_MEDIA_PLAY: + player.setPlayWhenReady(true); + break; + case KeyEvent.KEYCODE_MEDIA_PAUSE: + player.setPlayWhenReady(false); + break; + case KeyEvent.KEYCODE_MEDIA_NEXT: + next(); + break; + case KeyEvent.KEYCODE_MEDIA_PREVIOUS: + previous(); + break; + default: + break; + } + } + show(); + return true; + } + + private static boolean isHandledMediaKey(int keyCode) { + return keyCode == KeyEvent.KEYCODE_MEDIA_FAST_FORWARD + || keyCode == KeyEvent.KEYCODE_MEDIA_REWIND + || keyCode == KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE + || keyCode == KeyEvent.KEYCODE_MEDIA_PLAY + || keyCode == KeyEvent.KEYCODE_MEDIA_PAUSE + || keyCode == KeyEvent.KEYCODE_MEDIA_NEXT + || keyCode == KeyEvent.KEYCODE_MEDIA_PREVIOUS; + } + + private final class ComponentListener implements ExoPlayer.EventListener, + SeekBar.OnSeekBarChangeListener, OnClickListener { + + @Override + public void onStartTrackingTouch(SeekBar seekBar) { + removeCallbacks(hideAction); + dragging = true; + } + + @Override + public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { + if (fromUser) { + long position = positionValue(progress); + if (positionView != null) { + positionView.setText(stringForTime(position)); + } + if (player != null && !dragging) { + seekTo(position); + } + } + } + + @Override + public void onStopTrackingTouch(SeekBar seekBar) { + dragging = false; + if (player != null) { + seekTo(positionValue(seekBar.getProgress())); + } + hideAfterTimeout(); + } + + @Override + public void onPlayerStateChanged(boolean playWhenReady, int playbackState) { + updatePlayPauseButton(); + updateProgress(); + } + + @Override + public void onPositionDiscontinuity() { + updateNavigation(); + updateProgress(); + } + + @Override + public void onTimelineChanged(Timeline timeline, Object manifest) { + updateNavigation(); + updateProgress(); + } + + @Override + public void onLoadingChanged(boolean isLoading) { + // Do nothing. + } + + @Override + public void onTracksChanged(TrackGroupArray tracks, TrackSelectionArray selections) { + // Do nothing. + } + + @Override + public void onPlayerError(ExoPlaybackException error) { + // Do nothing. + } + + @Override + public void onClick(View view) { + if (player != null) { + if (nextButton == view) { + next(); + } else if (previousButton == view) { + previous(); + } else if (fastForwardButton == view) { + fastForward(); + } else if (rewindButton == view) { + rewind(); + } else if (playButton == view) { + player.setPlayWhenReady(true); + } else if (pauseButton == view) { + player.setPlayWhenReady(false); + } + } + hideAfterTimeout(); + } + + } + +} diff --git a/library/ui/src/main/java/com/google/android/exoplayer2/ui/SimpleExoPlayerView.java b/library/ui/src/main/java/com/google/android/exoplayer2/ui/SimpleExoPlayerView.java new file mode 100644 index 0000000000..08cb2536dc --- /dev/null +++ b/library/ui/src/main/java/com/google/android/exoplayer2/ui/SimpleExoPlayerView.java @@ -0,0 +1,717 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.exoplayer2.ui; + +import android.annotation.TargetApi; +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.util.AttributeSet; +import android.view.KeyEvent; +import android.view.LayoutInflater; +import android.view.MotionEvent; +import android.view.SurfaceView; +import android.view.TextureView; +import android.view.View; +import android.view.ViewGroup; +import android.widget.FrameLayout; +import android.widget.ImageView; +import com.google.android.exoplayer2.C; +import com.google.android.exoplayer2.ExoPlaybackException; +import com.google.android.exoplayer2.ExoPlayer; +import com.google.android.exoplayer2.SimpleExoPlayer; +import com.google.android.exoplayer2.Timeline; +import com.google.android.exoplayer2.metadata.Metadata; +import com.google.android.exoplayer2.metadata.id3.ApicFrame; +import com.google.android.exoplayer2.source.TrackGroupArray; +import com.google.android.exoplayer2.text.Cue; +import com.google.android.exoplayer2.text.TextRenderer; +import com.google.android.exoplayer2.trackselection.TrackSelection; +import com.google.android.exoplayer2.trackselection.TrackSelectionArray; +import com.google.android.exoplayer2.ui.AspectRatioFrameLayout.ResizeMode; +import com.google.android.exoplayer2.ui.PlaybackControlView.SeekDispatcher; +import com.google.android.exoplayer2.util.Assertions; +import java.util.List; + +/** + * A high level view for {@link SimpleExoPlayer} media playbacks. It displays video, subtitles and + * album art during playback, and displays playback controls using a {@link PlaybackControlView}. + *

+ * A SimpleExoPlayerView can be customized by setting attributes (or calling corresponding methods), + * overriding the view's layout file or by specifying a custom view layout file, as outlined below. + * + *

Attributes

+ * The following attributes can be set on a SimpleExoPlayerView when used in a layout XML file: + *

+ *

    + *
  • {@code use_artwork} - Whether artwork is used if available in audio streams. + *
      + *
    • Corresponding method: {@link #setUseArtwork(boolean)}
    • + *
    • Default: {@code true}
    • + *
    + *
  • + *
  • {@code default_artwork} - Default artwork to use if no artwork available in audio + * streams. + *
      + *
    • Corresponding method: {@link #setDefaultArtwork(Bitmap)}
    • + *
    • Default: {@code null}
    • + *
    + *
  • + *
  • {@code use_controller} - Whether playback controls are displayed. + *
      + *
    • Corresponding method: {@link #setUseController(boolean)}
    • + *
    • Default: {@code true}
    • + *
    + *
  • + *
  • {@code resize_mode} - Controls how video and album art is resized within the view. + * Valid values are {@code fit}, {@code fixed_width}, {@code fixed_height} and {@code fill}. + *
      + *
    • Corresponding method: {@link #setResizeMode(int)}
    • + *
    • Default: {@code fit}
    • + *
    + *
  • + *
  • {@code surface_type} - The type of surface view used for video playbacks. Valid + * values are {@code surface_view}, {@code texture_view} and {@code none}. Using {@code none} + * is recommended for audio only applications, since creating the surface can be expensive. + * Using {@code surface_view} is recommended for video applications. + *
      + *
    • Corresponding method: None
    • + *
    • Default: {@code surface_view}
    • + *
    + *
  • + *
  • {@code player_layout_id} - Specifies the id of the layout to be inflated. See below + * for more details. + *
      + *
    • Corresponding method: None
    • + *
    • Default: {@code R.id.exo_simple_player_view}
    • + *
    + *
  • {@code controller_layout_id} - Specifies the id of the layout resource to be + * inflated by the child {@link PlaybackControlView}. See below for more details. + *
      + *
    • Corresponding method: None
    • + *
    • Default: {@code R.id.exo_playback_control_view}
    • + *
    + *
  • All attributes that can be set on a {@link PlaybackControlView} can also be set on a + * SimpleExoPlayerView, and will be propagated to the inflated {@link PlaybackControlView}. + *
  • + *
+ * + *

Overriding the layout file

+ * To customize the layout of SimpleExoPlayerView throughout your app, or just for certain + * configurations, you can define {@code exo_simple_player_view.xml} layout files in your + * application {@code res/layout*} directories. These layouts will override the one provided by the + * ExoPlayer library, and will be inflated for use by SimpleExoPlayerView. The view identifies and + * binds its children by looking for the following ids: + *

+ *

    + *
  • {@code exo_content_frame} - A frame whose aspect ratio is resized based on the video + * or album art of the media being played, and the configured {@code resize_mode}. The video + * surface view is inflated into this frame as its first child. + *
      + *
    • Type: {@link AspectRatioFrameLayout}
    • + *
    + *
  • + *
  • {@code exo_shutter} - A view that's made visible when video should be hidden. This + * view is typically an opaque view that covers the video surface view, thereby obscuring it + * when visible. + *
      + *
    • Type: {@link View}
    • + *
    + *
  • + *
  • {@code exo_subtitles} - Displays subtitles. + *
      + *
    • Type: {@link SubtitleView}
    • + *
    + *
  • + *
  • {@code exo_artwork} - Displays album art. + *
      + *
    • Type: {@link ImageView}
    • + *
    + *
  • + *
  • {@code exo_controller_placeholder} - A placeholder that's replaced with the inflated + * {@link PlaybackControlView}. + *
      + *
    • Type: {@link View}
    • + *
    + *
  • + *
  • {@code exo_overlay} - A {@link FrameLayout} positioned on top of the player which + * the app can access via {@link #getOverlayFrameLayout()}, provided for convenience. + *
      + *
    • Type: {@link FrameLayout}
    • + *
    + *
  • + *
+ *

+ * All child views are optional and so can be omitted if not required, however where defined they + * must be of the expected type. + * + *

Specifying a custom layout file

+ * Defining your own {@code exo_simple_player_view.xml} is useful to customize the layout of + * SimpleExoPlayerView throughout your application. It's also possible to customize the layout for a + * single instance in a layout file. This is achieved by setting the {@code player_layout_id} + * attribute on a SimpleExoPlayerView. This will cause the specified layout to be inflated instead + * of {@code exo_simple_player_view.xml} for only the instance on which the attribute is set. + */ +@TargetApi(16) +public final class SimpleExoPlayerView extends FrameLayout { + + private static final int SURFACE_TYPE_NONE = 0; + private static final int SURFACE_TYPE_SURFACE_VIEW = 1; + private static final int SURFACE_TYPE_TEXTURE_VIEW = 2; + + private final AspectRatioFrameLayout contentFrame; + private final View shutterView; + private final View surfaceView; + private final ImageView artworkView; + private final SubtitleView subtitleView; + private final PlaybackControlView controller; + private final ComponentListener componentListener; + private final FrameLayout overlayFrameLayout; + + private SimpleExoPlayer player; + private boolean useController; + private boolean useArtwork; + private Bitmap defaultArtwork; + private int controllerShowTimeoutMs; + + public SimpleExoPlayerView(Context context) { + this(context, null); + } + + public SimpleExoPlayerView(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public SimpleExoPlayerView(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + + int playerLayoutId = R.layout.exo_simple_player_view; + boolean useArtwork = true; + int defaultArtworkId = 0; + boolean useController = true; + int surfaceType = SURFACE_TYPE_SURFACE_VIEW; + int resizeMode = AspectRatioFrameLayout.RESIZE_MODE_FIT; + int controllerShowTimeoutMs = PlaybackControlView.DEFAULT_SHOW_TIMEOUT_MS; + if (attrs != null) { + TypedArray a = context.getTheme().obtainStyledAttributes(attrs, + R.styleable.SimpleExoPlayerView, 0, 0); + try { + playerLayoutId = a.getResourceId(R.styleable.SimpleExoPlayerView_player_layout_id, + playerLayoutId); + useArtwork = a.getBoolean(R.styleable.SimpleExoPlayerView_use_artwork, useArtwork); + defaultArtworkId = a.getResourceId(R.styleable.SimpleExoPlayerView_default_artwork, + defaultArtworkId); + useController = a.getBoolean(R.styleable.SimpleExoPlayerView_use_controller, useController); + surfaceType = a.getInt(R.styleable.SimpleExoPlayerView_surface_type, surfaceType); + resizeMode = a.getInt(R.styleable.SimpleExoPlayerView_resize_mode, resizeMode); + controllerShowTimeoutMs = a.getInt(R.styleable.SimpleExoPlayerView_show_timeout, + controllerShowTimeoutMs); + } finally { + a.recycle(); + } + } + + LayoutInflater.from(context).inflate(playerLayoutId, this); + componentListener = new ComponentListener(); + setDescendantFocusability(FOCUS_AFTER_DESCENDANTS); + + // Content frame. + contentFrame = (AspectRatioFrameLayout) findViewById(R.id.exo_content_frame); + if (contentFrame != null) { + setResizeModeRaw(contentFrame, resizeMode); + } + + // Shutter view. + shutterView = findViewById(R.id.exo_shutter); + + // Create a surface view and insert it into the content frame, if there is one. + if (contentFrame != null && surfaceType != SURFACE_TYPE_NONE) { + ViewGroup.LayoutParams params = new ViewGroup.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); + surfaceView = surfaceType == SURFACE_TYPE_TEXTURE_VIEW ? new TextureView(context) + : new SurfaceView(context); + surfaceView.setLayoutParams(params); + contentFrame.addView(surfaceView, 0); + } else { + surfaceView = null; + } + + // Overlay frame layout. + overlayFrameLayout = (FrameLayout) findViewById(R.id.exo_overlay); + + // Artwork view. + artworkView = (ImageView) findViewById(R.id.exo_artwork); + this.useArtwork = useArtwork && artworkView != null; + if (defaultArtworkId != 0) { + defaultArtwork = BitmapFactory.decodeResource(context.getResources(), defaultArtworkId); + } + + // Subtitle view. + subtitleView = (SubtitleView) findViewById(R.id.exo_subtitles); + if (subtitleView != null) { + subtitleView.setUserDefaultStyle(); + subtitleView.setUserDefaultTextSize(); + } + + // Playback control view. + View controllerPlaceholder = findViewById(R.id.exo_controller_placeholder); + if (controllerPlaceholder != null) { + // Note: rewindMs and fastForwardMs are passed via attrs, so we don't need to make explicit + // calls to set them. + this.controller = new PlaybackControlView(context, attrs); + controller.setLayoutParams(controllerPlaceholder.getLayoutParams()); + ViewGroup parent = ((ViewGroup) controllerPlaceholder.getParent()); + int controllerIndex = parent.indexOfChild(controllerPlaceholder); + parent.removeView(controllerPlaceholder); + parent.addView(controller, controllerIndex); + } else { + this.controller = null; + } + this.controllerShowTimeoutMs = controller != null ? controllerShowTimeoutMs : 0; + this.useController = useController && controller != null; + hideController(); + } + + /** + * Returns the player currently set on this view, or null if no player is set. + */ + public SimpleExoPlayer getPlayer() { + return player; + } + + /** + * Set the {@link SimpleExoPlayer} to use. The {@link SimpleExoPlayer#setTextOutput} and + * {@link SimpleExoPlayer#setVideoListener} method of the player will be called and previous + * assignments are overridden. + * + * @param player The {@link SimpleExoPlayer} to use. + */ + public void setPlayer(SimpleExoPlayer player) { + if (this.player == player) { + return; + } + if (this.player != null) { + this.player.setTextOutput(null); + this.player.setVideoListener(null); + this.player.removeListener(componentListener); + this.player.setVideoSurface(null); + } + this.player = player; + if (useController) { + controller.setPlayer(player); + } + if (shutterView != null) { + shutterView.setVisibility(VISIBLE); + } + if (player != null) { + if (surfaceView instanceof TextureView) { + player.setVideoTextureView((TextureView) surfaceView); + } else if (surfaceView instanceof SurfaceView) { + player.setVideoSurfaceView((SurfaceView) surfaceView); + } + player.setVideoListener(componentListener); + player.addListener(componentListener); + player.setTextOutput(componentListener); + maybeShowController(false); + updateForCurrentTrackSelections(); + } else { + hideController(); + hideArtwork(); + } + } + + /** + * Sets the resize mode. + * + * @param resizeMode The resize mode. + */ + public void setResizeMode(@ResizeMode int resizeMode) { + Assertions.checkState(contentFrame != null); + contentFrame.setResizeMode(resizeMode); + } + + /** + * Returns whether artwork is displayed if present in the media. + */ + public boolean getUseArtwork() { + return useArtwork; + } + + /** + * Sets whether artwork is displayed if present in the media. + * + * @param useArtwork Whether artwork is displayed. + */ + public void setUseArtwork(boolean useArtwork) { + Assertions.checkState(!useArtwork || artworkView != null); + if (this.useArtwork != useArtwork) { + this.useArtwork = useArtwork; + updateForCurrentTrackSelections(); + } + } + + /** + * Returns the default artwork to display. + */ + public Bitmap getDefaultArtwork() { + return defaultArtwork; + } + + /** + * Sets the default artwork to display if {@code useArtwork} is {@code true} and no artwork is + * present in the media. + * + * @param defaultArtwork the default artwork to display. + */ + public void setDefaultArtwork(Bitmap defaultArtwork) { + if (this.defaultArtwork != defaultArtwork) { + this.defaultArtwork = defaultArtwork; + updateForCurrentTrackSelections(); + } + } + + /** + * Returns whether the playback controls are enabled. + */ + public boolean getUseController() { + return useController; + } + + /** + * Sets whether playback controls are enabled. If set to {@code false} the playback controls are + * never visible and are disconnected from the player. + * + * @param useController Whether playback controls should be enabled. + */ + public void setUseController(boolean useController) { + Assertions.checkState(!useController || controller != null); + if (this.useController == useController) { + return; + } + this.useController = useController; + if (useController) { + controller.setPlayer(player); + } else if (controller != null) { + controller.hide(); + controller.setPlayer(null); + } + } + + /** + * Called to process media key events. Any {@link KeyEvent} can be passed but only media key + * events will be handled. Does nothing if playback controls are disabled. + * + * @param event A key event. + * @return Whether the key event was handled. + */ + public boolean dispatchMediaKeyEvent(KeyEvent event) { + return useController && controller.dispatchMediaKeyEvent(event); + } + + /** + * Shows the playback controls. Does nothing if playback controls are disabled. + */ + public void showController() { + if (useController) { + maybeShowController(true); + } + } + + /** + * Hides the playback controls. Does nothing if playback controls are disabled. + */ + public void hideController() { + if (controller != null) { + controller.hide(); + } + } + + /** + * Returns the playback controls timeout. The playback controls are automatically hidden after + * this duration of time has elapsed without user input and with playback or buffering in + * progress. + * + * @return The timeout in milliseconds. A non-positive value will cause the controller to remain + * visible indefinitely. + */ + public int getControllerShowTimeoutMs() { + return controllerShowTimeoutMs; + } + + /** + * Sets the playback controls timeout. The playback controls are automatically hidden after this + * duration of time has elapsed without user input and with playback or buffering in progress. + * + * @param controllerShowTimeoutMs The timeout in milliseconds. A non-positive value will cause + * the controller to remain visible indefinitely. + */ + public void setControllerShowTimeoutMs(int controllerShowTimeoutMs) { + Assertions.checkState(controller != null); + this.controllerShowTimeoutMs = controllerShowTimeoutMs; + } + + /** + * Set the {@link PlaybackControlView.VisibilityListener}. + * + * @param listener The listener to be notified about visibility changes. + */ + public void setControllerVisibilityListener(PlaybackControlView.VisibilityListener listener) { + Assertions.checkState(controller != null); + controller.setVisibilityListener(listener); + } + + /** + * Sets the {@link SeekDispatcher}. + * + * @param seekDispatcher The {@link SeekDispatcher}, or null to use + * {@link PlaybackControlView#DEFAULT_SEEK_DISPATCHER}. + */ + public void setSeekDispatcher(SeekDispatcher seekDispatcher) { + Assertions.checkState(controller != null); + controller.setSeekDispatcher(seekDispatcher); + } + + /** + * Sets the rewind increment in milliseconds. + * + * @param rewindMs The rewind increment in milliseconds. + */ + public void setRewindIncrementMs(int rewindMs) { + Assertions.checkState(controller != null); + controller.setRewindIncrementMs(rewindMs); + } + + /** + * Sets the fast forward increment in milliseconds. + * + * @param fastForwardMs The fast forward increment in milliseconds. + */ + public void setFastForwardIncrementMs(int fastForwardMs) { + Assertions.checkState(controller != null); + controller.setFastForwardIncrementMs(fastForwardMs); + } + + /** + * Gets the view onto which video is rendered. This is either a {@link SurfaceView} (default) + * or a {@link TextureView} if the {@code use_texture_view} view attribute has been set to true. + * + * @return Either a {@link SurfaceView} or a {@link TextureView}. + */ + public View getVideoSurfaceView() { + return surfaceView; + } + + /** + * Gets the overlay {@link FrameLayout}, which can be populated with UI elements to show on top of + * the player. + * + * @return The overlay {@link FrameLayout}, or {@code null} if the layout has been customized and + * the overlay is not present. + */ + public FrameLayout getOverlayFrameLayout() { + return overlayFrameLayout; + } + + /** + * Gets the {@link SubtitleView}. + * + * @return The {@link SubtitleView}, or {@code null} if the layout has been customized and the + * subtitle view is not present. + */ + public SubtitleView getSubtitleView() { + return subtitleView; + } + + @Override + public boolean onTouchEvent(MotionEvent ev) { + if (!useController || player == null || ev.getActionMasked() != MotionEvent.ACTION_DOWN) { + return false; + } + if (controller.isVisible()) { + controller.hide(); + } else { + maybeShowController(true); + } + return true; + } + + @Override + public boolean onTrackballEvent(MotionEvent ev) { + if (!useController || player == null) { + return false; + } + maybeShowController(true); + return true; + } + + private void maybeShowController(boolean isForced) { + if (!useController || player == null) { + return; + } + int playbackState = player.getPlaybackState(); + boolean showIndefinitely = playbackState == ExoPlayer.STATE_IDLE + || playbackState == ExoPlayer.STATE_ENDED || !player.getPlayWhenReady(); + boolean wasShowingIndefinitely = controller.isVisible() && controller.getShowTimeoutMs() <= 0; + controller.setShowTimeoutMs(showIndefinitely ? 0 : controllerShowTimeoutMs); + if (isForced || showIndefinitely || wasShowingIndefinitely) { + controller.show(); + } + } + + private void updateForCurrentTrackSelections() { + if (player == null) { + return; + } + TrackSelectionArray selections = player.getCurrentTrackSelections(); + for (int i = 0; i < selections.length; i++) { + if (player.getRendererType(i) == C.TRACK_TYPE_VIDEO && selections.get(i) != null) { + // Video enabled so artwork must be hidden. If the shutter is closed, it will be opened in + // onRenderedFirstFrame(). + hideArtwork(); + return; + } + } + // Video disabled so the shutter must be closed. + if (shutterView != null) { + shutterView.setVisibility(VISIBLE); + } + // Display artwork if enabled and available, else hide it. + if (useArtwork) { + for (int i = 0; i < selections.length; i++) { + TrackSelection selection = selections.get(i); + if (selection != null) { + for (int j = 0; j < selection.length(); j++) { + Metadata metadata = selection.getFormat(j).metadata; + if (metadata != null && setArtworkFromMetadata(metadata)) { + return; + } + } + } + } + if (setArtworkFromBitmap(defaultArtwork)) { + return; + } + } + // Artwork disabled or unavailable. + hideArtwork(); + } + + private boolean setArtworkFromMetadata(Metadata metadata) { + for (int i = 0; i < metadata.length(); i++) { + Metadata.Entry metadataEntry = metadata.get(i); + if (metadataEntry instanceof ApicFrame) { + byte[] bitmapData = ((ApicFrame) metadataEntry).pictureData; + Bitmap bitmap = BitmapFactory.decodeByteArray(bitmapData, 0, bitmapData.length); + return setArtworkFromBitmap(bitmap); + } + } + return false; + } + + private boolean setArtworkFromBitmap(Bitmap bitmap) { + if (bitmap != null) { + int bitmapWidth = bitmap.getWidth(); + int bitmapHeight = bitmap.getHeight(); + if (bitmapWidth > 0 && bitmapHeight > 0) { + if (contentFrame != null) { + contentFrame.setAspectRatio((float) bitmapWidth / bitmapHeight); + } + artworkView.setImageBitmap(bitmap); + artworkView.setVisibility(VISIBLE); + return true; + } + } + return false; + } + + private void hideArtwork() { + if (artworkView != null) { + artworkView.setImageResource(android.R.color.transparent); // Clears any bitmap reference. + artworkView.setVisibility(INVISIBLE); + } + } + + @SuppressWarnings("ResourceType") + private static void setResizeModeRaw(AspectRatioFrameLayout aspectRatioFrame, int resizeMode) { + aspectRatioFrame.setResizeMode(resizeMode); + } + + private final class ComponentListener implements SimpleExoPlayer.VideoListener, + TextRenderer.Output, ExoPlayer.EventListener { + + // TextRenderer.Output implementation + + @Override + public void onCues(List cues) { + if (subtitleView != null) { + subtitleView.onCues(cues); + } + } + + // SimpleExoPlayer.VideoListener implementation + + @Override + public void onVideoSizeChanged(int width, int height, int unappliedRotationDegrees, + float pixelWidthHeightRatio) { + if (contentFrame != null) { + float aspectRatio = height == 0 ? 1 : (width * pixelWidthHeightRatio) / height; + contentFrame.setAspectRatio(aspectRatio); + } + } + + @Override + public void onRenderedFirstFrame() { + if (shutterView != null) { + shutterView.setVisibility(INVISIBLE); + } + } + + @Override + public void onTracksChanged(TrackGroupArray tracks, TrackSelectionArray selections) { + updateForCurrentTrackSelections(); + } + + // ExoPlayer.EventListener implementation + + @Override + public void onLoadingChanged(boolean isLoading) { + // Do nothing. + } + + @Override + public void onPlayerStateChanged(boolean playWhenReady, int playbackState) { + maybeShowController(false); + } + + @Override + public void onPlayerError(ExoPlaybackException e) { + // Do nothing. + } + + @Override + public void onPositionDiscontinuity() { + // Do nothing. + } + + @Override + public void onTimelineChanged(Timeline timeline, Object manifest) { + // Do nothing. + } + + } + +} diff --git a/library/ui/src/main/java/com/google/android/exoplayer2/ui/SubtitlePainter.java b/library/ui/src/main/java/com/google/android/exoplayer2/ui/SubtitlePainter.java new file mode 100644 index 0000000000..d4f09b1721 --- /dev/null +++ b/library/ui/src/main/java/com/google/android/exoplayer2/ui/SubtitlePainter.java @@ -0,0 +1,405 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.exoplayer2.ui; + +import android.content.Context; +import android.content.res.Resources; +import android.content.res.TypedArray; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.Paint.Join; +import android.graphics.Paint.Style; +import android.graphics.Rect; +import android.graphics.RectF; +import android.text.Layout.Alignment; +import android.text.StaticLayout; +import android.text.TextPaint; +import android.text.TextUtils; +import android.util.DisplayMetrics; +import android.util.Log; +import com.google.android.exoplayer2.text.CaptionStyleCompat; +import com.google.android.exoplayer2.text.Cue; +import com.google.android.exoplayer2.util.Util; + +/** + * Paints subtitle {@link Cue}s. + */ +/* package */ final class SubtitlePainter { + + private static final String TAG = "SubtitlePainter"; + + /** + * Ratio of inner padding to font size. + */ + private static final float INNER_PADDING_RATIO = 0.125f; + + /** + * Temporary rectangle used for computing line bounds. + */ + private final RectF lineBounds = new RectF(); + + // Styled dimensions. + private final float cornerRadius; + private final float outlineWidth; + private final float shadowRadius; + private final float shadowOffset; + private final float spacingMult; + private final float spacingAdd; + + private final TextPaint textPaint; + private final Paint paint; + + // Previous input variables. + private CharSequence cueText; + private Alignment cueTextAlignment; + private Bitmap cueBitmap; + private float cueLine; + @Cue.LineType + private int cueLineType; + @Cue.AnchorType + private int cueLineAnchor; + private float cuePosition; + @Cue.AnchorType + private int cuePositionAnchor; + private float cueSize; + private boolean applyEmbeddedStyles; + private int foregroundColor; + private int backgroundColor; + private int windowColor; + private int edgeColor; + @CaptionStyleCompat.EdgeType + private int edgeType; + private float textSizePx; + private float bottomPaddingFraction; + private int parentLeft; + private int parentTop; + private int parentRight; + private int parentBottom; + + // Derived drawing variables. + private StaticLayout textLayout; + private int textLeft; + private int textTop; + private int textPaddingX; + private Rect bitmapRect; + + @SuppressWarnings("ResourceType") + public SubtitlePainter(Context context) { + int[] viewAttr = {android.R.attr.lineSpacingExtra, android.R.attr.lineSpacingMultiplier}; + TypedArray styledAttributes = context.obtainStyledAttributes(null, viewAttr, 0, 0); + spacingAdd = styledAttributes.getDimensionPixelSize(0, 0); + spacingMult = styledAttributes.getFloat(1, 1); + styledAttributes.recycle(); + + Resources resources = context.getResources(); + DisplayMetrics displayMetrics = resources.getDisplayMetrics(); + int twoDpInPx = Math.round((2f * displayMetrics.densityDpi) / DisplayMetrics.DENSITY_DEFAULT); + cornerRadius = twoDpInPx; + outlineWidth = twoDpInPx; + shadowRadius = twoDpInPx; + shadowOffset = twoDpInPx; + + textPaint = new TextPaint(); + textPaint.setAntiAlias(true); + textPaint.setSubpixelText(true); + + paint = new Paint(); + paint.setAntiAlias(true); + paint.setStyle(Style.FILL); + } + + /** + * Draws the provided {@link Cue} into a canvas with the specified styling. + *

+ * A call to this method is able to use cached results of calculations made during the previous + * call, and so an instance of this class is able to optimize repeated calls to this method in + * which the same parameters are passed. + * + * @param cue The cue to draw. + * @param applyEmbeddedStyles Whether styling embedded within the cue should be applied. + * @param style The style to use when drawing the cue text. + * @param textSizePx The text size to use when drawing the cue text, in pixels. + * @param bottomPaddingFraction The bottom padding fraction to apply when {@link Cue#line} is + * {@link Cue#DIMEN_UNSET}, as a fraction of the viewport height + * @param canvas The canvas into which to draw. + * @param cueBoxLeft The left position of the enclosing cue box. + * @param cueBoxTop The top position of the enclosing cue box. + * @param cueBoxRight The right position of the enclosing cue box. + * @param cueBoxBottom The bottom position of the enclosing cue box. + */ + public void draw(Cue cue, boolean applyEmbeddedStyles, CaptionStyleCompat style, float textSizePx, + float bottomPaddingFraction, Canvas canvas, int cueBoxLeft, int cueBoxTop, int cueBoxRight, + int cueBoxBottom) { + boolean isTextCue = cue.bitmap == null; + CharSequence cueText = null; + Bitmap cueBitmap = null; + int windowColor = Color.BLACK; + if (isTextCue) { + cueText = cue.text; + if (TextUtils.isEmpty(cueText)) { + // Nothing to draw. + return; + } + windowColor = cue.windowColorSet ? cue.windowColor : style.windowColor; + if (!applyEmbeddedStyles) { + // Strip out any embedded styling. + cueText = cueText.toString(); + windowColor = style.windowColor; + } + } else { + cueBitmap = cue.bitmap; + } + if (areCharSequencesEqual(this.cueText, cueText) + && Util.areEqual(this.cueTextAlignment, cue.textAlignment) + && this.cueBitmap == cueBitmap + && this.cueLine == cue.line + && this.cueLineType == cue.lineType + && Util.areEqual(this.cueLineAnchor, cue.lineAnchor) + && this.cuePosition == cue.position + && Util.areEqual(this.cuePositionAnchor, cue.positionAnchor) + && this.cueSize == cue.size + && this.applyEmbeddedStyles == applyEmbeddedStyles + && this.foregroundColor == style.foregroundColor + && this.backgroundColor == style.backgroundColor + && this.windowColor == windowColor + && this.edgeType == style.edgeType + && this.edgeColor == style.edgeColor + && Util.areEqual(this.textPaint.getTypeface(), style.typeface) + && this.textSizePx == textSizePx + && this.bottomPaddingFraction == bottomPaddingFraction + && this.parentLeft == cueBoxLeft + && this.parentTop == cueBoxTop + && this.parentRight == cueBoxRight + && this.parentBottom == cueBoxBottom) { + // We can use the cached layout. + drawLayout(canvas, isTextCue); + return; + } + + this.cueText = cueText; + this.cueTextAlignment = cue.textAlignment; + this.cueBitmap = cueBitmap; + this.cueLine = cue.line; + this.cueLineType = cue.lineType; + this.cueLineAnchor = cue.lineAnchor; + this.cuePosition = cue.position; + this.cuePositionAnchor = cue.positionAnchor; + this.cueSize = cue.size; + this.applyEmbeddedStyles = applyEmbeddedStyles; + this.foregroundColor = style.foregroundColor; + this.backgroundColor = style.backgroundColor; + this.windowColor = windowColor; + this.edgeType = style.edgeType; + this.edgeColor = style.edgeColor; + this.textPaint.setTypeface(style.typeface); + this.textSizePx = textSizePx; + this.bottomPaddingFraction = bottomPaddingFraction; + this.parentLeft = cueBoxLeft; + this.parentTop = cueBoxTop; + this.parentRight = cueBoxRight; + this.parentBottom = cueBoxBottom; + + if (isTextCue) { + setupTextLayout(); + } else { + setupBitmapLayout(); + } + drawLayout(canvas, isTextCue); + } + + private void setupTextLayout() { + int parentWidth = parentRight - parentLeft; + int parentHeight = parentBottom - parentTop; + + textPaint.setTextSize(textSizePx); + int textPaddingX = (int) (textSizePx * INNER_PADDING_RATIO + 0.5f); + + int availableWidth = parentWidth - textPaddingX * 2; + if (cueSize != Cue.DIMEN_UNSET) { + availableWidth = (int) (availableWidth * cueSize); + } + if (availableWidth <= 0) { + Log.w(TAG, "Skipped drawing subtitle cue (insufficient space)"); + return; + } + + Alignment textAlignment = cueTextAlignment == null ? Alignment.ALIGN_CENTER : cueTextAlignment; + textLayout = new StaticLayout(cueText, textPaint, availableWidth, textAlignment, spacingMult, + spacingAdd, true); + int textHeight = textLayout.getHeight(); + int textWidth = 0; + int lineCount = textLayout.getLineCount(); + for (int i = 0; i < lineCount; i++) { + textWidth = Math.max((int) Math.ceil(textLayout.getLineWidth(i)), textWidth); + } + if (cueSize != Cue.DIMEN_UNSET && textWidth < availableWidth) { + textWidth = availableWidth; + } + textWidth += textPaddingX * 2; + + int textLeft; + int textRight; + if (cuePosition != Cue.DIMEN_UNSET) { + int anchorPosition = Math.round(parentWidth * cuePosition) + parentLeft; + textLeft = cuePositionAnchor == Cue.ANCHOR_TYPE_END ? anchorPosition - textWidth + : cuePositionAnchor == Cue.ANCHOR_TYPE_MIDDLE ? (anchorPosition * 2 - textWidth) / 2 + : anchorPosition; + textLeft = Math.max(textLeft, parentLeft); + textRight = Math.min(textLeft + textWidth, parentRight); + } else { + textLeft = (parentWidth - textWidth) / 2; + textRight = textLeft + textWidth; + } + + textWidth = textRight - textLeft; + if (textWidth <= 0) { + Log.w(TAG, "Skipped drawing subtitle cue (invalid horizontal positioning)"); + return; + } + + int textTop; + if (cueLine != Cue.DIMEN_UNSET) { + int anchorPosition; + if (cueLineType == Cue.LINE_TYPE_FRACTION) { + anchorPosition = Math.round(parentHeight * cueLine) + parentTop; + } else { + // cueLineType == Cue.LINE_TYPE_NUMBER + int firstLineHeight = textLayout.getLineBottom(0) - textLayout.getLineTop(0); + if (cueLine >= 0) { + anchorPosition = Math.round(cueLine * firstLineHeight) + parentTop; + } else { + anchorPosition = Math.round((cueLine + 1) * firstLineHeight) + parentBottom; + } + } + textTop = cueLineAnchor == Cue.ANCHOR_TYPE_END ? anchorPosition - textHeight + : cueLineAnchor == Cue.ANCHOR_TYPE_MIDDLE ? (anchorPosition * 2 - textHeight) / 2 + : anchorPosition; + if (textTop + textHeight > parentBottom) { + textTop = parentBottom - textHeight; + } else if (textTop < parentTop) { + textTop = parentTop; + } + } else { + textTop = parentBottom - textHeight - (int) (parentHeight * bottomPaddingFraction); + } + + // Update the derived drawing variables. + this.textLayout = new StaticLayout(cueText, textPaint, textWidth, textAlignment, spacingMult, + spacingAdd, true); + this.textLeft = textLeft; + this.textTop = textTop; + this.textPaddingX = textPaddingX; + } + + private void setupBitmapLayout() { + int parentWidth = parentRight - parentLeft; + int parentHeight = parentBottom - parentTop; + float anchorX = parentLeft + (parentWidth * cuePosition); + float anchorY = parentTop + (parentHeight * cueLine); + int width = Math.round(parentWidth * cueSize); + int height = Math.round(width * ((float) cueBitmap.getHeight() / cueBitmap.getWidth())); + int x = Math.round(cueLineAnchor == Cue.ANCHOR_TYPE_END ? (anchorX - width) + : cueLineAnchor == Cue.ANCHOR_TYPE_MIDDLE ? (anchorX - (width / 2)) : anchorX); + int y = Math.round(cuePositionAnchor == Cue.ANCHOR_TYPE_END ? (anchorY - height) + : cuePositionAnchor == Cue.ANCHOR_TYPE_MIDDLE ? (anchorY - (height / 2)) : anchorY); + bitmapRect = new Rect(x, y, x + width, y + height); + } + + private void drawLayout(Canvas canvas, boolean isTextCue) { + if (isTextCue) { + drawTextLayout(canvas); + } else { + drawBitmapLayout(canvas); + } + } + + private void drawTextLayout(Canvas canvas) { + StaticLayout layout = textLayout; + if (layout == null) { + // Nothing to draw. + return; + } + + int saveCount = canvas.save(); + canvas.translate(textLeft, textTop); + + if (Color.alpha(windowColor) > 0) { + paint.setColor(windowColor); + canvas.drawRect(-textPaddingX, 0, layout.getWidth() + textPaddingX, layout.getHeight(), + paint); + } + + if (Color.alpha(backgroundColor) > 0) { + paint.setColor(backgroundColor); + float previousBottom = layout.getLineTop(0); + int lineCount = layout.getLineCount(); + for (int i = 0; i < lineCount; i++) { + lineBounds.left = layout.getLineLeft(i) - textPaddingX; + lineBounds.right = layout.getLineRight(i) + textPaddingX; + lineBounds.top = previousBottom; + lineBounds.bottom = layout.getLineBottom(i); + previousBottom = lineBounds.bottom; + canvas.drawRoundRect(lineBounds, cornerRadius, cornerRadius, paint); + } + } + + if (edgeType == CaptionStyleCompat.EDGE_TYPE_OUTLINE) { + textPaint.setStrokeJoin(Join.ROUND); + textPaint.setStrokeWidth(outlineWidth); + textPaint.setColor(edgeColor); + textPaint.setStyle(Style.FILL_AND_STROKE); + layout.draw(canvas); + } else if (edgeType == CaptionStyleCompat.EDGE_TYPE_DROP_SHADOW) { + textPaint.setShadowLayer(shadowRadius, shadowOffset, shadowOffset, edgeColor); + } else if (edgeType == CaptionStyleCompat.EDGE_TYPE_RAISED + || edgeType == CaptionStyleCompat.EDGE_TYPE_DEPRESSED) { + boolean raised = edgeType == CaptionStyleCompat.EDGE_TYPE_RAISED; + int colorUp = raised ? Color.WHITE : edgeColor; + int colorDown = raised ? edgeColor : Color.WHITE; + float offset = shadowRadius / 2f; + textPaint.setColor(foregroundColor); + textPaint.setStyle(Style.FILL); + textPaint.setShadowLayer(shadowRadius, -offset, -offset, colorUp); + layout.draw(canvas); + textPaint.setShadowLayer(shadowRadius, offset, offset, colorDown); + } + + textPaint.setColor(foregroundColor); + textPaint.setStyle(Style.FILL); + layout.draw(canvas); + textPaint.setShadowLayer(0, 0, 0, 0); + + canvas.restoreToCount(saveCount); + } + + private void drawBitmapLayout(Canvas canvas) { + canvas.drawBitmap(cueBitmap, null, bitmapRect, null); + } + + /** + * This method is used instead of {@link TextUtils#equals(CharSequence, CharSequence)} because the + * latter only checks the text of each sequence, and does not check for equality of styling that + * may be embedded within the {@link CharSequence}s. + */ + private static boolean areCharSequencesEqual(CharSequence first, CharSequence second) { + // Some CharSequence implementations don't perform a cheap referential equality check in their + // equals methods, so we perform one explicitly here. + return first == second || (first != null && first.equals(second)); + } + +} diff --git a/library/ui/src/main/java/com/google/android/exoplayer2/ui/SubtitleView.java b/library/ui/src/main/java/com/google/android/exoplayer2/ui/SubtitleView.java new file mode 100644 index 0000000000..49516ab6f4 --- /dev/null +++ b/library/ui/src/main/java/com/google/android/exoplayer2/ui/SubtitleView.java @@ -0,0 +1,265 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.exoplayer2.ui; + +import android.annotation.TargetApi; +import android.content.Context; +import android.content.res.Resources; +import android.graphics.Canvas; +import android.util.AttributeSet; +import android.util.TypedValue; +import android.view.View; +import android.view.accessibility.CaptioningManager; +import com.google.android.exoplayer2.text.CaptionStyleCompat; +import com.google.android.exoplayer2.text.Cue; +import com.google.android.exoplayer2.text.TextRenderer; +import com.google.android.exoplayer2.util.Util; +import java.util.ArrayList; +import java.util.List; + +/** + * A view for displaying subtitle {@link Cue}s. + */ +public final class SubtitleView extends View implements TextRenderer.Output { + + /** + * The default fractional text size. + * + * @see #setFractionalTextSize(float, boolean) + */ + public static final float DEFAULT_TEXT_SIZE_FRACTION = 0.0533f; + + /** + * The default bottom padding to apply when {@link Cue#line} is {@link Cue#DIMEN_UNSET}, as a + * fraction of the viewport height. + * + * @see #setBottomPaddingFraction(float) + */ + public static final float DEFAULT_BOTTOM_PADDING_FRACTION = 0.08f; + + private static final int FRACTIONAL = 0; + private static final int FRACTIONAL_IGNORE_PADDING = 1; + private static final int ABSOLUTE = 2; + + private final List painters; + + private List cues; + private int textSizeType; + private float textSize; + private boolean applyEmbeddedStyles; + private CaptionStyleCompat style; + private float bottomPaddingFraction; + + public SubtitleView(Context context) { + this(context, null); + } + + public SubtitleView(Context context, AttributeSet attrs) { + super(context, attrs); + painters = new ArrayList<>(); + textSizeType = FRACTIONAL; + textSize = DEFAULT_TEXT_SIZE_FRACTION; + applyEmbeddedStyles = true; + style = CaptionStyleCompat.DEFAULT; + bottomPaddingFraction = DEFAULT_BOTTOM_PADDING_FRACTION; + } + + @Override + public void onCues(List cues) { + setCues(cues); + } + + /** + * Sets the cues to be displayed by the view. + * + * @param cues The cues to display. + */ + public void setCues(List cues) { + if (this.cues == cues) { + return; + } + this.cues = cues; + // Ensure we have sufficient painters. + int cueCount = (cues == null) ? 0 : cues.size(); + while (painters.size() < cueCount) { + painters.add(new SubtitlePainter(getContext())); + } + // Invalidate to trigger drawing. + invalidate(); + } + + /** + * Set the text size to a given unit and value. + *

+ * See {@link TypedValue} for the possible dimension units. + * + * @param unit The desired dimension unit. + * @param size The desired size in the given units. + */ + public void setFixedTextSize(int unit, float size) { + Context context = getContext(); + Resources resources; + if (context == null) { + resources = Resources.getSystem(); + } else { + resources = context.getResources(); + } + setTextSize(ABSOLUTE, TypedValue.applyDimension(unit, size, resources.getDisplayMetrics())); + } + + /** + * Sets the text size to one derived from {@link CaptioningManager#getFontScale()}, or to a + * default size before API level 19. + */ + public void setUserDefaultTextSize() { + float fontScale = Util.SDK_INT >= 19 && !isInEditMode() ? getUserCaptionFontScaleV19() : 1f; + setFractionalTextSize(DEFAULT_TEXT_SIZE_FRACTION * fontScale); + } + + /** + * Sets the text size to be a fraction of the view's remaining height after its top and bottom + * padding have been subtracted. + *

+ * Equivalent to {@code #setFractionalTextSize(fractionOfHeight, false)}. + * + * @param fractionOfHeight A fraction between 0 and 1. + */ + public void setFractionalTextSize(float fractionOfHeight) { + setFractionalTextSize(fractionOfHeight, false); + } + + /** + * Sets the text size to be a fraction of the height of this view. + * + * @param fractionOfHeight A fraction between 0 and 1. + * @param ignorePadding Set to true if {@code fractionOfHeight} should be interpreted as a + * fraction of this view's height ignoring any top and bottom padding. Set to false if + * {@code fractionOfHeight} should be interpreted as a fraction of this view's remaining + * height after the top and bottom padding has been subtracted. + */ + public void setFractionalTextSize(float fractionOfHeight, boolean ignorePadding) { + setTextSize(ignorePadding ? FRACTIONAL_IGNORE_PADDING : FRACTIONAL, fractionOfHeight); + } + + private void setTextSize(int textSizeType, float textSize) { + if (this.textSizeType == textSizeType && this.textSize == textSize) { + return; + } + this.textSizeType = textSizeType; + this.textSize = textSize; + // Invalidate to trigger drawing. + invalidate(); + } + + /** + * Sets whether styling embedded within the cues should be applied. Enabled by default. + * + * @param applyEmbeddedStyles Whether styling embedded within the cues should be applied. + */ + public void setApplyEmbeddedStyles(boolean applyEmbeddedStyles) { + if (this.applyEmbeddedStyles == applyEmbeddedStyles) { + return; + } + this.applyEmbeddedStyles = applyEmbeddedStyles; + // Invalidate to trigger drawing. + invalidate(); + } + + /** + * Sets the caption style to be equivalent to the one returned by + * {@link CaptioningManager#getUserStyle()}, or to a default style before API level 19. + */ + public void setUserDefaultStyle() { + setStyle(Util.SDK_INT >= 19 && !isInEditMode() + ? getUserCaptionStyleV19() : CaptionStyleCompat.DEFAULT); + } + + /** + * Sets the caption style. + * + * @param style A style for the view. + */ + public void setStyle(CaptionStyleCompat style) { + if (this.style == style) { + return; + } + this.style = style; + // Invalidate to trigger drawing. + invalidate(); + } + + /** + * Sets the bottom padding fraction to apply when {@link Cue#line} is {@link Cue#DIMEN_UNSET}, + * as a fraction of the view's remaining height after its top and bottom padding have been + * subtracted. + *

+ * Note that this padding is applied in addition to any standard view padding. + * + * @param bottomPaddingFraction The bottom padding fraction. + */ + public void setBottomPaddingFraction(float bottomPaddingFraction) { + if (this.bottomPaddingFraction == bottomPaddingFraction) { + return; + } + this.bottomPaddingFraction = bottomPaddingFraction; + // Invalidate to trigger drawing. + invalidate(); + } + + @Override + public void dispatchDraw(Canvas canvas) { + int cueCount = (cues == null) ? 0 : cues.size(); + int rawTop = getTop(); + int rawBottom = getBottom(); + + // Calculate the bounds after padding is taken into account. + int left = getLeft() + getPaddingLeft(); + int top = rawTop + getPaddingTop(); + int right = getRight() + getPaddingRight(); + int bottom = rawBottom - getPaddingBottom(); + if (bottom <= top || right <= left) { + // No space to draw subtitles. + return; + } + + float textSizePx = textSizeType == ABSOLUTE ? textSize + : textSize * (textSizeType == FRACTIONAL ? (bottom - top) : (rawBottom - rawTop)); + if (textSizePx <= 0) { + // Text has no height. + return; + } + + for (int i = 0; i < cueCount; i++) { + painters.get(i).draw(cues.get(i), applyEmbeddedStyles, style, textSizePx, + bottomPaddingFraction, canvas, left, top, right, bottom); + } + } + + @TargetApi(19) + private float getUserCaptionFontScaleV19() { + CaptioningManager captioningManager = + (CaptioningManager) getContext().getSystemService(Context.CAPTIONING_SERVICE); + return captioningManager.getFontScale(); + } + + @TargetApi(19) + private CaptionStyleCompat getUserCaptionStyleV19() { + CaptioningManager captioningManager = + (CaptioningManager) getContext().getSystemService(Context.CAPTIONING_SERVICE); + return CaptionStyleCompat.createFromCaptionStyle(captioningManager.getUserStyle()); + } + +} diff --git a/library/ui/src/main/res/drawable-anydpi-v21/exo_controls_fastforward.xml b/library/ui/src/main/res/drawable-anydpi-v21/exo_controls_fastforward.xml new file mode 100644 index 0000000000..4b86e109e9 --- /dev/null +++ b/library/ui/src/main/res/drawable-anydpi-v21/exo_controls_fastforward.xml @@ -0,0 +1,25 @@ + + + + + + diff --git a/library/ui/src/main/res/drawable-anydpi-v21/exo_controls_next.xml b/library/ui/src/main/res/drawable-anydpi-v21/exo_controls_next.xml new file mode 100644 index 0000000000..6305bcbc90 --- /dev/null +++ b/library/ui/src/main/res/drawable-anydpi-v21/exo_controls_next.xml @@ -0,0 +1,25 @@ + + + + + + diff --git a/library/ui/src/main/res/drawable-anydpi-v21/exo_controls_pause.xml b/library/ui/src/main/res/drawable-anydpi-v21/exo_controls_pause.xml new file mode 100644 index 0000000000..45cd68bed6 --- /dev/null +++ b/library/ui/src/main/res/drawable-anydpi-v21/exo_controls_pause.xml @@ -0,0 +1,25 @@ + + + + + + diff --git a/library/ui/src/main/res/drawable-anydpi-v21/exo_controls_play.xml b/library/ui/src/main/res/drawable-anydpi-v21/exo_controls_play.xml new file mode 100644 index 0000000000..c8c4cdb127 --- /dev/null +++ b/library/ui/src/main/res/drawable-anydpi-v21/exo_controls_play.xml @@ -0,0 +1,25 @@ + + + + + + diff --git a/library/ui/src/main/res/drawable-anydpi-v21/exo_controls_previous.xml b/library/ui/src/main/res/drawable-anydpi-v21/exo_controls_previous.xml new file mode 100644 index 0000000000..9564a2a350 --- /dev/null +++ b/library/ui/src/main/res/drawable-anydpi-v21/exo_controls_previous.xml @@ -0,0 +1,25 @@ + + + + + + diff --git a/library/ui/src/main/res/drawable-anydpi-v21/exo_controls_rewind.xml b/library/ui/src/main/res/drawable-anydpi-v21/exo_controls_rewind.xml new file mode 100644 index 0000000000..976b706170 --- /dev/null +++ b/library/ui/src/main/res/drawable-anydpi-v21/exo_controls_rewind.xml @@ -0,0 +1,25 @@ + + + + + + diff --git a/library/ui/src/main/res/drawable-hdpi/exo_controls_fastforward.png b/library/ui/src/main/res/drawable-hdpi/exo_controls_fastforward.png new file mode 100644 index 0000000000000000000000000000000000000000..843df84091bb7e43f85088e2985cb17712eb4b12 GIT binary patch literal 354 zcmV-o0iFJdP)~5QPzl6$m6^1rmWoAW4u24uIeQ2qc0dZ~z1cKp+tuU<=Tl1wDU|KjmESlLKwAWuDy0?Ju3$lDWgaLQ6r z0eDtE9z+44UOeG~t54wP|l{5gJor@<%0p%hYWnGgfPpkqdf0AlUPwWE9 zNzwy&j_*7c1yUB0GQcyx_gEHC_L3ITV_iT$r#{kSS>WME4DhUjerS&a^(q`cUxg@7 z>;fMjBM(p0`Wnp~2fpykpksx@fldP%%7ZT8JQ6#2el3T&ua}Yx&(M0D_F4;3p3nl` zi}@SRkL60~|CGOa+SW_`B50Tbdk7(f5JJd}D`jHlVpJacasU7T07*qoM6N<$f{G`F Aj{pDw literal 0 HcmV?d00001 diff --git a/library/ui/src/main/res/drawable-hdpi/exo_controls_next.png b/library/ui/src/main/res/drawable-hdpi/exo_controls_next.png new file mode 100644 index 0000000000000000000000000000000000000000..c37541472ebb20e052a5c4e68f82852d4a489ec0 GIT binary patch literal 323 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA3?vioaBc-s;Q>A&uI>ds|7if^nwEqEt&k}R z@(X5g;PIDueVZlw_jHBT=RXUWJnsHpeOP02w9fW;RW^Pzpt^cb7srr_xVM)qg$@}A zuwG>3G3w^Ik@o)g{K>04IW(I1PF(rP?e^C6Z?e~t}8mc^KVEOXY<)RyS%oA nDVdY8h==XT9hM(YvbWwlo}!Q;Ydn(!=oAJ|S3j3^P6E)*( literal 0 HcmV?d00001 diff --git a/library/ui/src/main/res/drawable-hdpi/exo_controls_pause.png b/library/ui/src/main/res/drawable-hdpi/exo_controls_pause.png new file mode 100644 index 0000000000000000000000000000000000000000..0a23452746c0f33622ff4d909e2213b98ce990b9 GIT binary patch literal 108 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDH3?y^UWFG-iYymzYu0Z<#|Nl#G&c6#}F_r}R z1v5B2yO9RuhnjGkCiCxvXAbpmr5JpHO}HTOB5@xyCL>^BiEHX8zeV3eCC}QlkKsiLyr`#VO({s-JnDN;}FLM6npO-wE_EgQu&X%Q~loCID~aZ8HD> literal 0 HcmV?d00001 diff --git a/library/ui/src/main/res/drawable-hdpi/exo_controls_previous.png b/library/ui/src/main/res/drawable-hdpi/exo_controls_previous.png new file mode 100644 index 0000000000000000000000000000000000000000..3eae5c883bf2c1d8b950e35307ca7e0bf0fc664b GIT binary patch literal 292 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA3?vioaBc-st^qzFuI>ds|0xFHS4_484HPR0 z@(X4#xE%a&;{M-UA61GMy32^HPd(Xe_1wSeC_hj|g{O;SNJZS+OB=}UWAyQc>(V?C+-zHvv;vr9<^9_ b@df)-Q^{poR@z^IZeZ|q^>bP0l+XkKj~kZ6 literal 0 HcmV?d00001 diff --git a/library/ui/src/main/res/drawable-hdpi/exo_controls_rewind.png b/library/ui/src/main/res/drawable-hdpi/exo_controls_rewind.png new file mode 100644 index 0000000000000000000000000000000000000000..36537d3b7320d0e2b352527ae374bc8c6f3a1d64 GIT binary patch literal 347 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA3?vioaBc-sfdM`tuI>ds|EUU+r44zwK8TlpZ=V`AX~Kb@xBLUpBqutG}z@yPb#Hr0Npiu4KG(s4v}T&!I9w zksXaJ*N)%W2b^$lV+00^T7r*g}8FYn}2J|fI5tI_Y)qkwje@(&0_aOxv z<9#Jszxs2`G_3UgsrOxvSk=Qd V9_(4)c?IZH22WQ%mvv4FO#p)T!5#nr literal 0 HcmV?d00001 diff --git a/library/ui/src/main/res/drawable-ldpi/exo_controls_fastforward.png b/library/ui/src/main/res/drawable-ldpi/exo_controls_fastforward.png new file mode 100644 index 0000000000000000000000000000000000000000..19b9e6015c7cfe517037032e34ef1fd2de721475 GIT binary patch literal 192 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1|%Pp+x`Gj?Vc`@3FFZCs1}JDH z@L6DS8&IL6qH~W_kS>F3nw?5rI|NsA8 zhciwsRt5^knFK8Ok63Nl!oaeRxzJ2kx1sT@gN~y&x5EQdf$Gbbm?UQK9kJQ6CBa(Y z^o2uA0WHrjYjtc;i_D5-I8`FE#ba524kRmlTeg(dV1rbLYDOj_#~$`aDN#`j%)c8WnnBX#3hUUC#G)b@n5Q@1XqqOr zhwV{8RHT7?2Wy6)%l5__jbao3NO#hahox3Je0A~ do~wX~q2W~A`8Aye6Mzn3@O1TaS?83{1OTu`L7V^p literal 0 HcmV?d00001 diff --git a/library/ui/src/main/res/drawable-ldpi/exo_controls_previous.png b/library/ui/src/main/res/drawable-ldpi/exo_controls_previous.png new file mode 100644 index 0000000000000000000000000000000000000000..930534d3127ceef161e086437aab19a725009e09 GIT binary patch literal 187 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1|%Pp+x`GjO`a}}Ar-fh6C_v{H>5qV7ZF(4 z$KLV$@)4#5TX+TTs~=NTiDVE-V|%Z zT*VkP1r_4{%kwX|!nEd~(^Z8gw;b!DBSE%&7bdIu>NK308E)g3vbV?ft1c)I$ztaD0e0sshPLKpx5 literal 0 HcmV?d00001 diff --git a/library/ui/src/main/res/drawable-ldpi/exo_controls_rewind.png b/library/ui/src/main/res/drawable-ldpi/exo_controls_rewind.png new file mode 100644 index 0000000000000000000000000000000000000000..83d71782f65462fbc78127f99fa99dd10cbee9ef GIT binary patch literal 214 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1|%Pp+x`Gjb3I)gLn>}1CrGd^ZqR%1|Nnm* z2b=jpraMfc7(Ui2{Ofn`o9owS^-t^h%r9;`u`(yQx*s7=}aaufe`1SBa5=zR%ISv-ZrD6LEYMBqE7&? z;mmA9yR#08j80h{8#N?4?hBvDc-V49V3P}5$M@h>X(ByLpBNY{e!iaao+04@(4h>T Lu6{1-oD!MFWREOXtO~A2?8{a7`g_FQ2I_1JnIRi`g+>uM{xwds|EUK$mVS={nk-oo zMzKY@y3JY5_^D&pQ=bmwa@5MXu) z@^pItmdKI;Vst0BZD- A)c^nh literal 0 HcmV?d00001 diff --git a/library/ui/src/main/res/drawable-mdpi/exo_controls_pause.png b/library/ui/src/main/res/drawable-mdpi/exo_controls_pause.png new file mode 100644 index 0000000000000000000000000000000000000000..f54c942201a83c6b866d651cb6dda51ab06d5cc9 GIT binary patch literal 153 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnF3?v&v(vJfv(Ey(iSN8&+{}^EVDaKns1#Bfj ze!&d!4iW+?+N{j~fFin{E{-7@=aUl_NCyN3B_#` zyzvFT4)#Z99dwK*@G&%=cbLO_M2Gn)gMTnjll#U zebYe)NHa7(caX6G@)=*HMHgTe~DWM4f@A*$c literal 0 HcmV?d00001 diff --git a/library/ui/src/main/res/drawable-mdpi/exo_controls_previous.png b/library/ui/src/main/res/drawable-mdpi/exo_controls_previous.png new file mode 100644 index 0000000000000000000000000000000000000000..950e213d2f550cf8022cc7ee063e509e6903d393 GIT binary patch literal 227 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=ffJRh}-6Ar-fh6C_v{Cy4YgF+H$n zI-?#a&%d&2#WRKlQPK+g_%FGHvol=Xz))x`@V_l{(QM|1Pg)K&zwD(JbTc;;y>$5V z(_YLWn4RIMOGCxqdYgtcK88gMUIs4~z z1~5loTbylnB<#n9y)q8^b?iqNJwz`&0!mMsm!ME*pTO7fYBJ-azl(Wi1sZUmdKI;Vst07C3gL;wH) literal 0 HcmV?d00001 diff --git a/library/ui/src/main/res/drawable-mdpi/exo_controls_rewind.png b/library/ui/src/main/res/drawable-mdpi/exo_controls_rewind.png new file mode 100644 index 0000000000000000000000000000000000000000..e75efae1893e7c53fce2d4c8809bbbe12e577923 GIT binary patch literal 273 zcmV+s0q*{ZP)`R z%w)4WEs7urf)7yDIa2=0w~Cd>9TCEByne3G6glVI|tLyqc0l7I60o&l_5><&Qr%*Cs`1zG>w zrB)~q1X z@^Hn92fydexcT4i@{=WsXErogKMntLwbk8b_SrOP_g7WAYW}h80aLG(GCX`b<2Azu zT_CF(DAcfg`8sBc@K;t0GVj*6uYb+(ta}%q!WE!whSe*ph9r+4Gb9<*{B6`5R{2z> zF+42WIN4qJ{oCMqq8e>mp0mvA+rif|YbW0s@nTsEnL=5M@Ml&Zz8on{crSeKS*^xx zrx^A_Z#!N)gc@CMJbC<|b*-!h+cT>RB}cygnzduj*|g2I4C0r(-V6Fk^8x+D;OXk; Jvd$@?2>^VR(}n;5 literal 0 HcmV?d00001 diff --git a/library/ui/src/main/res/drawable-xhdpi/exo_controls_next.png b/library/ui/src/main/res/drawable-xhdpi/exo_controls_next.png new file mode 100644 index 0000000000000000000000000000000000000000..bc1ebf83c58c0f6b983817f62a24213cf25c2c27 GIT binary patch literal 334 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7T#lEU=;OqaSW-r_4cNskYk`od!eHM zOQ3*`gNTcyt{r}#)c+<@MXM4-f zhpq$~2m~TiQg7Z__N)26WP14@XNK*YUuv)yq|fdwYDmbv9K!dY&&>HL!@)z#*2x@r zo4e~2SL}&66gRYYcTuay8?+egzqa?iKrg>VC>bmL1i7;T#`6T@CAEmyH<6wnL?Pgg&ebxsLQ05&!_aR2}S literal 0 HcmV?d00001 diff --git a/library/ui/src/main/res/drawable-xhdpi/exo_controls_play.png b/library/ui/src/main/res/drawable-xhdpi/exo_controls_play.png new file mode 100644 index 0000000000000000000000000000000000000000..f2f934413e8580fe363938e427fbc11905441893 GIT binary patch literal 343 zcmV-d0jU0oP)G|d_tc@}WR?%2|^fWQwsU}LWWBG2$FJPU}Nu&HMOfj3rr z77)2$bI$?-UuZ~Y$daY09(oIDZoxL2MW-i%tQk8CNrS`oyp7~Kvyy|3DA?w z%mU;mvw#5E$t);9W-|XE;D#+c3ix1KuTS8N%{+btU)aWL29Map<016GRvu5`aQ^Pq z{V_@;Kvvoc3Xqw$LIUKbEwcdmY0D@;N7^z7(3Q4?0(7P=kpR7E8z?}3+6D-)k+vQJ pY^ANEfZANz2qA?qY_z{C1* z%d}ffN-Gw;|0n*{`1iWB*_H?Pd(P|aTBLDl|7zYWcIF^a#%HNQjC$ss4B6t0r}P99 zes?jvRTO;iQ;aEYGDpBBmxgL_##@Sl3ZaY#;^i6s{o?QtWxD3a_Jdb3LDl%ce+kBY zDl9*yaX!eE=ZLt;`CzRq#uN5$XW0pK OCWEJ|pUXO@geCy!sjGbe literal 0 HcmV?d00001 diff --git a/library/ui/src/main/res/drawable-xhdpi/exo_controls_rewind.png b/library/ui/src/main/res/drawable-xhdpi/exo_controls_rewind.png new file mode 100644 index 0000000000000000000000000000000000000000..3340ef9bd227be28c3641fc1a111522d9d0af362 GIT binary patch literal 400 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I3?%1nZ+ru!LIQk3T-^(N{!<$?d%wI4G+(+T z$S;_|kmEGV1`&r(zgw*zPI6BVmD#t5ulUMy6^-P8+MT9AWfweM978JN-cGUPYjzM| zsZHRQ;Mw=V@&Et%eCjWBGvzmkPSbM8s46!#k?K9RN&olRnu(u18}~K+T%2U#?{!{g zT~OPTnSY}t7o}fwl$@0Qj$hOH)0ws5K*8(5o(tTMd5SG6-*sAn`=a&z#VLwa=`TZh z%s-bra+}b8S-^DMt#f7jXWpDl6QVfyONCe??d>Y3Y7#J8s zJY5_^D&pQwKb?2TK*U9MQUaF=r?1atr}!^V|NlRvw`Yd$(w$QI?<3`3>g4pk`C#Ao z*aC?Bq<#DR?#c0-D|c0R&pvrEdy!q=ou880Oh4b5k76Y3*kztw*42IAQQ~^>bP0 Hl+XkKRE;tn literal 0 HcmV?d00001 diff --git a/library/ui/src/main/res/drawable-xxhdpi/exo_controls_next.png b/library/ui/src/main/res/drawable-xxhdpi/exo_controls_next.png new file mode 100644 index 0000000000000000000000000000000000000000..232f09e910954a5a56b32d2cb807f2e68f8de781 GIT binary patch literal 391 zcmeAS@N?(olHy`uVBq!ia0vp^2_VeD3?#3*wSy#_0(?ST-3xsFQwF}~PUr`kCR!5Y z7tG*y)Qt7M!Y{seD`%c}?5@(zm5SXa?clKjsNlY*i(^Pd+}o>XxeggHuq6JTyzJt( zZP#l5hksRi<0~@f_BqQBjvTX3KM(TKTsq~FSzWo``#QEQZwwYx9ccJcbD$xLo%vF3 z8{`>6vf8#!6LBX zI49c`KOpO1!znK2w<(QvP3unRPh~m9U>m%imAT@*z=3XGrmqdpZtYj&`f;C~p`V$d z;vUC=%gqe)FVdQ&MBb@0Q^V4E&u=k literal 0 HcmV?d00001 diff --git a/library/ui/src/main/res/drawable-xxhdpi/exo_controls_pause.png b/library/ui/src/main/res/drawable-xxhdpi/exo_controls_pause.png new file mode 100644 index 0000000000000000000000000000000000000000..50a545db4d67f51ccc127aa98582a2d5236e127f GIT binary patch literal 113 zcmeAS@N?(olHy`uVBq!ia0vp^2_VeK3?y%aJ*@^(YymzYuI>ds|NsA=KV^|KP=v80 z$S;_|;n|HeAV<>E#WBR_ALEHZkC5TjW01WFdewB_m zpN&S#A|!*?^k{}16VXoYnre9T)QqH5!op(Rt?bj}tv}c;oS*SOslKRi@&Y&Ci%$dR zhP?2L$lEM(JEzsGdg0l)jLqV=-e6}st}o!u{dPy~NUjrvcs8ro@R-D7ev`ELYA)%6 Rr9gi&c)I$ztaD0e0ssTizK8$- literal 0 HcmV?d00001 diff --git a/library/ui/src/main/res/drawable-xxhdpi/exo_controls_previous.png b/library/ui/src/main/res/drawable-xxhdpi/exo_controls_previous.png new file mode 100644 index 0000000000000000000000000000000000000000..f71acc4875e1ee7375b4023d800abd6b1292e717 GIT binary patch literal 464 zcmeAS@N?(olHy`uVBq!ia0vp^2_VeD3?#3*wSy#z0(?ST-3xsF(-#!%=eQ5FU9}|0 zFPP!IK{1ES>EA3i_dlnLJYPI{zq`if>)y>g?E=9n>o14*?|%G!e#s*KRX`0dJY5_^ zD&pQ=wJc%^6mh=D?)&*RbHay~vpgC9qg57qNxBvp-{qa-v+DTLDM4PEs&`i}kBX0c z@HAA#;E&nF17%th5B#3(k+5yDM}nz1r;nYAki`2|rdVOlFW#LjX37%}R4FPpEN^GD zvE_O2(viWRpShySBcW7`)2B~}siG=i!R`*0Uz3k8%##y&A;oj$$84sWeuIFo#_f%> zY7-l#^Rc}u=3+MeW6Jd*{*c38eT4(EZUzB=h1jxQNU>GSQ8@6pg;7qO>w`sN!|_fg z8$XE$g^L*ag;^`+7#w))!YH@AD#A+>=<;2Rb!XSeIO>$-0R7D1>FVdQ&MBb@0G@yI AVgLXD literal 0 HcmV?d00001 diff --git a/library/ui/src/main/res/drawable-xxhdpi/exo_controls_rewind.png b/library/ui/src/main/res/drawable-xxhdpi/exo_controls_rewind.png new file mode 100644 index 0000000000000000000000000000000000000000..db0555f9e5c5f89cf29edafc7c5f277556f3cb74 GIT binary patch literal 571 zcmeAS@N?(olHy`uVBq!ia0vp^2_VeD3?#3*wSy$81AIbU-3xsF4-n8x+3yN;hDJ${ zUob=Q{m(M*4Sut5h%`U9nS7cjcz?S4VwLrmH8x-Op5K3bce23a@8K5hd){5)sP1bJzu zex8u|$?iP!?4_9w#fJS7cQ&kE{)6%CEt3bSG8KD_c_U(uuXp5qeV?iS_c@ce-x7|; z-<~o_C=+&n+17iHx!=9`a>eB599HvUX0hcgXFPu4!kQ!Tx8C?3zTAA=J0l@1WYxo$ zfmXI!hPGbI-Bp*lFO`}nxAgL2q05U)szm(z7xo@{SqNnL^e?}>_|KPy%d?c{#ufx6 zpLzFlhV=5-hlb|&7B(m69eBjox6yI8p^Wq4yz2#Lc=wxC+^KPNzF%O`)|UIN<_{O| z4Zoj14x9L->#~^|_!Zo7m|RnLreUU x-~XHn5bxna#>s3aK743!bbf5IM-v<<_wCJor+pLc2m?j~gQu&X%Q~loCIETRKIZ@c literal 0 HcmV?d00001 diff --git a/library/ui/src/main/res/layout/exo_playback_control_view.xml b/library/ui/src/main/res/layout/exo_playback_control_view.xml new file mode 100644 index 0000000000..f8ef5a6fdd --- /dev/null +++ b/library/ui/src/main/res/layout/exo_playback_control_view.xml @@ -0,0 +1,87 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/library/ui/src/main/res/layout/exo_simple_player_view.xml b/library/ui/src/main/res/layout/exo_simple_player_view.xml new file mode 100644 index 0000000000..1f59b7796d --- /dev/null +++ b/library/ui/src/main/res/layout/exo_simple_player_view.xml @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/library/ui/src/main/res/values-af/strings.xml b/library/ui/src/main/res/values-af/strings.xml new file mode 100644 index 0000000000..9f1bce53d9 --- /dev/null +++ b/library/ui/src/main/res/values-af/strings.xml @@ -0,0 +1,25 @@ + + + + "Vorige snit" + "Volgende snit" + "Wag" + "Speel" + "Stop" + "Spoel terug" + "Vinnig vorentoe" + diff --git a/library/ui/src/main/res/values-am/strings.xml b/library/ui/src/main/res/values-am/strings.xml new file mode 100644 index 0000000000..f06c2a664e --- /dev/null +++ b/library/ui/src/main/res/values-am/strings.xml @@ -0,0 +1,25 @@ + + + + "ቀዳሚ ትራክ" + "ቀጣይ ትራክ" + "ለአፍታ አቁም" + "አጫውት" + "አቁም" + "ወደኋላ አጠንጥን" + "በፍጥነት አሳልፍ" + diff --git a/library/ui/src/main/res/values-ar/strings.xml b/library/ui/src/main/res/values-ar/strings.xml new file mode 100644 index 0000000000..a40c961bf7 --- /dev/null +++ b/library/ui/src/main/res/values-ar/strings.xml @@ -0,0 +1,25 @@ + + + + "المقطع الصوتي السابق" + "المقطع الصوتي التالي" + "إيقاف مؤقت" + "تشغيل" + "إيقاف" + "إرجاع" + "تقديم سريع" + diff --git a/library/ui/src/main/res/values-az-rAZ/strings.xml b/library/ui/src/main/res/values-az-rAZ/strings.xml new file mode 100644 index 0000000000..7b3b9366b5 --- /dev/null +++ b/library/ui/src/main/res/values-az-rAZ/strings.xml @@ -0,0 +1,25 @@ + + + + "Öncəki trek" + "Növbəti trek" + "Pauza" + "Oyun" + "Dayandır" + "Geri sarıma" + "Sürətlə irəli" + diff --git a/library/ui/src/main/res/values-b+sr+Latn/strings.xml b/library/ui/src/main/res/values-b+sr+Latn/strings.xml new file mode 100644 index 0000000000..b5fdd74402 --- /dev/null +++ b/library/ui/src/main/res/values-b+sr+Latn/strings.xml @@ -0,0 +1,25 @@ + + + + "Prethodna pesma" + "Sledeća pesma" + "Pauza" + "Pusti" + "Zaustavi" + "Premotaj unazad" + "Premotaj unapred" + diff --git a/library/ui/src/main/res/values-be-rBY/strings.xml b/library/ui/src/main/res/values-be-rBY/strings.xml new file mode 100644 index 0000000000..890c23ebd5 --- /dev/null +++ b/library/ui/src/main/res/values-be-rBY/strings.xml @@ -0,0 +1,25 @@ + + + + "Папярэдні трэк" + "Наступны трэк" + "Прыпыніць" + "Прайграць" + "Спыніць" + "Перамотка назад" + "Перамотка ўперад" + diff --git a/library/ui/src/main/res/values-bg/strings.xml b/library/ui/src/main/res/values-bg/strings.xml new file mode 100644 index 0000000000..30b905fb8e --- /dev/null +++ b/library/ui/src/main/res/values-bg/strings.xml @@ -0,0 +1,25 @@ + + + + "Предишен запис" + "Следващ запис" + "Пауза" + "Пускане" + "Спиране" + "Превъртане назад" + "Превъртане напред" + diff --git a/library/ui/src/main/res/values-bn-rBD/strings.xml b/library/ui/src/main/res/values-bn-rBD/strings.xml new file mode 100644 index 0000000000..ca5d9461d3 --- /dev/null +++ b/library/ui/src/main/res/values-bn-rBD/strings.xml @@ -0,0 +1,25 @@ + + + + "পূর্ববর্তী ট্র্যাক" + "পরবর্তী ট্র্যাক" + "বিরাম দিন" + "প্লে করুন" + "থামান" + "গুটিয়ে নিন" + "দ্রুত সামনে এগোন" + diff --git a/library/ui/src/main/res/values-bs-rBA/strings.xml b/library/ui/src/main/res/values-bs-rBA/strings.xml new file mode 100644 index 0000000000..9cb0ca4d76 --- /dev/null +++ b/library/ui/src/main/res/values-bs-rBA/strings.xml @@ -0,0 +1,25 @@ + + + + "Prethodna numera" + "Sljedeća numera" + "Pauziraj" + "Reproduciraj" + "Zaustavi" + "Premotaj" + "Ubrzaj" + diff --git a/library/ui/src/main/res/values-ca/strings.xml b/library/ui/src/main/res/values-ca/strings.xml new file mode 100644 index 0000000000..0816c76b12 --- /dev/null +++ b/library/ui/src/main/res/values-ca/strings.xml @@ -0,0 +1,25 @@ + + + + "Ruta anterior" + "Ruta següent" + "Posa en pausa" + "Reprodueix" + "Atura" + "Rebobina" + "Avança ràpidament" + diff --git a/library/ui/src/main/res/values-cs/strings.xml b/library/ui/src/main/res/values-cs/strings.xml new file mode 100644 index 0000000000..22cff4041e --- /dev/null +++ b/library/ui/src/main/res/values-cs/strings.xml @@ -0,0 +1,25 @@ + + + + "Předchozí skladba" + "Další skladba" + "Pozastavit" + "Přehrát" + "Zastavit" + "Přetočit zpět" + "Přetočit vpřed" + diff --git a/library/ui/src/main/res/values-da/strings.xml b/library/ui/src/main/res/values-da/strings.xml new file mode 100644 index 0000000000..a6710bea50 --- /dev/null +++ b/library/ui/src/main/res/values-da/strings.xml @@ -0,0 +1,25 @@ + + + + "Forrige nummer" + "Næste nummer" + "Pause" + "Afspil" + "Stop" + "Spol tilbage" + "Spol frem" + diff --git a/library/ui/src/main/res/values-de/strings.xml b/library/ui/src/main/res/values-de/strings.xml new file mode 100644 index 0000000000..cdfd2d4baf --- /dev/null +++ b/library/ui/src/main/res/values-de/strings.xml @@ -0,0 +1,25 @@ + + + + "Vorheriger Titel" + "Nächster Titel" + "Pausieren" + "Wiedergabe" + "Beenden" + "Zurückspulen" + "Vorspulen" + diff --git a/library/ui/src/main/res/values-el/strings.xml b/library/ui/src/main/res/values-el/strings.xml new file mode 100644 index 0000000000..1e11df3b14 --- /dev/null +++ b/library/ui/src/main/res/values-el/strings.xml @@ -0,0 +1,25 @@ + + + + "Προηγούμενο κομμάτι" + "Επόμενο κομμάτι" + "Παύση" + "Αναπαραγωγή" + "Διακοπή" + "Επαναφορά" + "Γρήγορη προώθηση" + diff --git a/library/ui/src/main/res/values-en-rAU/strings.xml b/library/ui/src/main/res/values-en-rAU/strings.xml new file mode 100644 index 0000000000..5077cf2b94 --- /dev/null +++ b/library/ui/src/main/res/values-en-rAU/strings.xml @@ -0,0 +1,25 @@ + + + + "Previous track" + "Next track" + "Pause" + "Play" + "Stop" + "Rewind" + "Fast-forward" + diff --git a/library/ui/src/main/res/values-en-rGB/strings.xml b/library/ui/src/main/res/values-en-rGB/strings.xml new file mode 100644 index 0000000000..5077cf2b94 --- /dev/null +++ b/library/ui/src/main/res/values-en-rGB/strings.xml @@ -0,0 +1,25 @@ + + + + "Previous track" + "Next track" + "Pause" + "Play" + "Stop" + "Rewind" + "Fast-forward" + diff --git a/library/ui/src/main/res/values-en-rIN/strings.xml b/library/ui/src/main/res/values-en-rIN/strings.xml new file mode 100644 index 0000000000..5077cf2b94 --- /dev/null +++ b/library/ui/src/main/res/values-en-rIN/strings.xml @@ -0,0 +1,25 @@ + + + + "Previous track" + "Next track" + "Pause" + "Play" + "Stop" + "Rewind" + "Fast-forward" + diff --git a/library/ui/src/main/res/values-es-rUS/strings.xml b/library/ui/src/main/res/values-es-rUS/strings.xml new file mode 100644 index 0000000000..72b176e538 --- /dev/null +++ b/library/ui/src/main/res/values-es-rUS/strings.xml @@ -0,0 +1,25 @@ + + + + "Pista anterior" + "Siguiente pista" + "Pausar" + "Reproducir" + "Detener" + "Retroceder" + "Avanzar" + diff --git a/library/ui/src/main/res/values-es/strings.xml b/library/ui/src/main/res/values-es/strings.xml new file mode 100644 index 0000000000..3b188d266d --- /dev/null +++ b/library/ui/src/main/res/values-es/strings.xml @@ -0,0 +1,25 @@ + + + + "Canción anterior" + "Siguiente canción" + "Pausar" + "Reproducir" + "Detener" + "Rebobinar" + "Avance rápido" + diff --git a/library/ui/src/main/res/values-et-rEE/strings.xml b/library/ui/src/main/res/values-et-rEE/strings.xml new file mode 100644 index 0000000000..7a01bd9d5a --- /dev/null +++ b/library/ui/src/main/res/values-et-rEE/strings.xml @@ -0,0 +1,25 @@ + + + + "Eelmine lugu" + "Järgmine lugu" + "Peata" + "Esita" + "Peata" + "Keri tagasi" + "Keri edasi" + diff --git a/library/ui/src/main/res/values-eu-rES/strings.xml b/library/ui/src/main/res/values-eu-rES/strings.xml new file mode 100644 index 0000000000..3dd51d2138 --- /dev/null +++ b/library/ui/src/main/res/values-eu-rES/strings.xml @@ -0,0 +1,25 @@ + + + + "Aurreko pista" + "Hurrengo pista" + "Pausatu" + "Erreproduzitu" + "Gelditu" + "Atzeratu" + "Aurreratu" + diff --git a/library/ui/src/main/res/values-fa/strings.xml b/library/ui/src/main/res/values-fa/strings.xml new file mode 100644 index 0000000000..a8955ca2f3 --- /dev/null +++ b/library/ui/src/main/res/values-fa/strings.xml @@ -0,0 +1,25 @@ + + + + "آهنگ قبلی" + "آهنگ بعدی" + "مکث" + "پخش" + "توقف" + "عقب بردن" + "جلو بردن سریع" + diff --git a/library/ui/src/main/res/values-fi/strings.xml b/library/ui/src/main/res/values-fi/strings.xml new file mode 100644 index 0000000000..5f1352d1af --- /dev/null +++ b/library/ui/src/main/res/values-fi/strings.xml @@ -0,0 +1,25 @@ + + + + "Edellinen raita" + "Seuraava raita" + "Tauko" + "Toista" + "Seis" + "Kelaa taakse" + "Kelaa eteen" + diff --git a/library/ui/src/main/res/values-fr-rCA/strings.xml b/library/ui/src/main/res/values-fr-rCA/strings.xml new file mode 100644 index 0000000000..51ba11e0c0 --- /dev/null +++ b/library/ui/src/main/res/values-fr-rCA/strings.xml @@ -0,0 +1,25 @@ + + + + "Chanson précédente" + "Chanson suivante" + "Pause" + "Lecture" + "Arrêter" + "Reculer" + "Avance rapide" + diff --git a/library/ui/src/main/res/values-fr/strings.xml b/library/ui/src/main/res/values-fr/strings.xml new file mode 100644 index 0000000000..d55b32b6f7 --- /dev/null +++ b/library/ui/src/main/res/values-fr/strings.xml @@ -0,0 +1,25 @@ + + + + "Piste précédente" + "Piste suivante" + "Interrompre" + "Lire" + "Arrêter" + "Retour arrière" + "Avance rapide" + diff --git a/library/ui/src/main/res/values-gl-rES/strings.xml b/library/ui/src/main/res/values-gl-rES/strings.xml new file mode 100644 index 0000000000..99ae59c7f9 --- /dev/null +++ b/library/ui/src/main/res/values-gl-rES/strings.xml @@ -0,0 +1,25 @@ + + + + "Pista anterior" + "Seguinte pista" + "Pausar" + "Reproducir" + "Deter" + "Rebobinar" + "Avance rápido" + diff --git a/library/ui/src/main/res/values-gu-rIN/strings.xml b/library/ui/src/main/res/values-gu-rIN/strings.xml new file mode 100644 index 0000000000..6feab0a3a6 --- /dev/null +++ b/library/ui/src/main/res/values-gu-rIN/strings.xml @@ -0,0 +1,25 @@ + + + + "પહેલાનો ટ્રૅક" + "આગલો ટ્રૅક" + "થોભો" + "ચલાવો" + "રોકો" + "રીવાઇન્ડ કરો" + "ઝડપી ફોરવર્ડ કરો" + diff --git a/library/ui/src/main/res/values-hi/strings.xml b/library/ui/src/main/res/values-hi/strings.xml new file mode 100644 index 0000000000..5229b67d0e --- /dev/null +++ b/library/ui/src/main/res/values-hi/strings.xml @@ -0,0 +1,25 @@ + + + + "पिछला ट्रैक" + "अगला ट्रैक" + "रोकें" + "चलाएं" + "बंद करें" + "रिवाइंड करें" + "फ़ास्ट फ़ॉरवर्ड" + diff --git a/library/ui/src/main/res/values-hr/strings.xml b/library/ui/src/main/res/values-hr/strings.xml new file mode 100644 index 0000000000..c0b075edde --- /dev/null +++ b/library/ui/src/main/res/values-hr/strings.xml @@ -0,0 +1,25 @@ + + + + "Prethodna pjesma" + "Sljedeća pjesma" + "Pauziraj" + "Reproduciraj" + "Zaustavi" + "Unatrag" + "Brzo unaprijed" + diff --git a/library/ui/src/main/res/values-hu/strings.xml b/library/ui/src/main/res/values-hu/strings.xml new file mode 100644 index 0000000000..2a34684edb --- /dev/null +++ b/library/ui/src/main/res/values-hu/strings.xml @@ -0,0 +1,25 @@ + + + + "Előző szám" + "Következő szám" + "Szünet" + "Lejátszás" + "Leállítás" + "Visszatekerés" + "Előretekerés" + diff --git a/library/ui/src/main/res/values-hy-rAM/strings.xml b/library/ui/src/main/res/values-hy-rAM/strings.xml new file mode 100644 index 0000000000..05f9d04ab7 --- /dev/null +++ b/library/ui/src/main/res/values-hy-rAM/strings.xml @@ -0,0 +1,25 @@ + + + + "Նախորդը" + "Հաջորդը" + "Դադարեցնել" + "Նվագարկել" + "Դադարեցնել" + "Հետ փաթաթել" + "Արագ առաջ անցնել" + diff --git a/library/ui/src/main/res/values-in/strings.xml b/library/ui/src/main/res/values-in/strings.xml new file mode 100644 index 0000000000..062933a0a8 --- /dev/null +++ b/library/ui/src/main/res/values-in/strings.xml @@ -0,0 +1,25 @@ + + + + "Lagu sebelumnya" + "Lagu berikutnya" + "Jeda" + "Putar" + "Berhenti" + "Putar Ulang" + "Maju cepat" + diff --git a/library/ui/src/main/res/values-is-rIS/strings.xml b/library/ui/src/main/res/values-is-rIS/strings.xml new file mode 100644 index 0000000000..9c4421a272 --- /dev/null +++ b/library/ui/src/main/res/values-is-rIS/strings.xml @@ -0,0 +1,25 @@ + + + + "Fyrra lag" + "Næsta lag" + "Hlé" + "Spila" + "Stöðva" + "Spóla til baka" + "Spóla áfram" + diff --git a/library/ui/src/main/res/values-it/strings.xml b/library/ui/src/main/res/values-it/strings.xml new file mode 100644 index 0000000000..71525a2b3e --- /dev/null +++ b/library/ui/src/main/res/values-it/strings.xml @@ -0,0 +1,25 @@ + + + + "Traccia precedente" + "Traccia successiva" + "Metti in pausa" + "Riproduci" + "Interrompi" + "Riavvolgi" + "Avanti veloce" + diff --git a/library/ui/src/main/res/values-iw/strings.xml b/library/ui/src/main/res/values-iw/strings.xml new file mode 100644 index 0000000000..f33cc2adb0 --- /dev/null +++ b/library/ui/src/main/res/values-iw/strings.xml @@ -0,0 +1,25 @@ + + + + "הרצועה הקודמת" + "הרצועה הבאה" + "השהה" + "הפעל" + "הפסק" + "הרץ אחורה" + "הרץ קדימה" + diff --git a/library/ui/src/main/res/values-ja/strings.xml b/library/ui/src/main/res/values-ja/strings.xml new file mode 100644 index 0000000000..baa459aeca --- /dev/null +++ b/library/ui/src/main/res/values-ja/strings.xml @@ -0,0 +1,25 @@ + + + + "前のトラック" + "次のトラック" + "一時停止" + "再生" + "停止" + "巻き戻し" + "早送り" + diff --git a/library/ui/src/main/res/values-ka-rGE/strings.xml b/library/ui/src/main/res/values-ka-rGE/strings.xml new file mode 100644 index 0000000000..5b87f86c34 --- /dev/null +++ b/library/ui/src/main/res/values-ka-rGE/strings.xml @@ -0,0 +1,25 @@ + + + + "წინა ჩანაწერი" + "შემდეგი ჩანაწერი" + "პაუზა" + "დაკვრა" + "შეწყვეტა" + "უკან გადახვევა" + "წინ გადახვევა" + diff --git a/library/ui/src/main/res/values-kk-rKZ/strings.xml b/library/ui/src/main/res/values-kk-rKZ/strings.xml new file mode 100644 index 0000000000..c1bf5c8b4b --- /dev/null +++ b/library/ui/src/main/res/values-kk-rKZ/strings.xml @@ -0,0 +1,25 @@ + + + + "Алдыңғы трек" + "Келесі трек" + "Кідірту" + "Ойнату" + "Тоқтату" + "Кері айналдыру" + "Жылдам алға айналдыру" + diff --git a/library/ui/src/main/res/values-km-rKH/strings.xml b/library/ui/src/main/res/values-km-rKH/strings.xml new file mode 100644 index 0000000000..dbeeab60a6 --- /dev/null +++ b/library/ui/src/main/res/values-km-rKH/strings.xml @@ -0,0 +1,25 @@ + + + + "បទ​មុន" + "បទ​បន្ទាប់" + "ផ្អាក" + "ចាក់" + "បញ្ឈប់" + "ខា​ថយក្រោយ" + "ទៅ​មុខ​​​រហ័ស" + diff --git a/library/ui/src/main/res/values-kn-rIN/strings.xml b/library/ui/src/main/res/values-kn-rIN/strings.xml new file mode 100644 index 0000000000..b73cf0fdb0 --- /dev/null +++ b/library/ui/src/main/res/values-kn-rIN/strings.xml @@ -0,0 +1,25 @@ + + + + "ಹಿಂದಿನ ಟ್ರ್ಯಾಕ್" + "ಮುಂದಿನ ಟ್ರ್ಯಾಕ್" + "ವಿರಾಮಗೊಳಿಸು" + "ಪ್ಲೇ ಮಾಡು" + "ನಿಲ್ಲಿಸು" + "ರಿವೈಂಡ್ ಮಾಡು" + "ವೇಗವಾಗಿ ಮುಂದಕ್ಕೆ" + diff --git a/library/ui/src/main/res/values-ko/strings.xml b/library/ui/src/main/res/values-ko/strings.xml new file mode 100644 index 0000000000..7097e2d9f7 --- /dev/null +++ b/library/ui/src/main/res/values-ko/strings.xml @@ -0,0 +1,25 @@ + + + + "이전 트랙" + "다음 트랙" + "일시중지" + "재생" + "중지" + "되감기" + "빨리 감기" + diff --git a/library/ui/src/main/res/values-ky-rKG/strings.xml b/library/ui/src/main/res/values-ky-rKG/strings.xml new file mode 100644 index 0000000000..7090c178c3 --- /dev/null +++ b/library/ui/src/main/res/values-ky-rKG/strings.xml @@ -0,0 +1,25 @@ + + + + "Мурунку трек" + "Кийинки трек" + "Тындыруу" + "Ойнотуу" + "Токтотуу" + "Артка түрүү" + "Алдыга түрүү" + diff --git a/library/ui/src/main/res/values-lo-rLA/strings.xml b/library/ui/src/main/res/values-lo-rLA/strings.xml new file mode 100644 index 0000000000..44095e4323 --- /dev/null +++ b/library/ui/src/main/res/values-lo-rLA/strings.xml @@ -0,0 +1,25 @@ + + + + "​ເພງ​ກ່ອນ​ໜ້າ" + "​ເພງ​ຕໍ່​ໄປ" + "ຢຸດຊົ່ວຄາວ" + "ຫຼິ້ນ" + "ຢຸດ" + "​ຣີ​​ວາຍກັບ" + "ເລື່ອນ​ໄປ​ໜ້າ" + diff --git a/library/ui/src/main/res/values-lt/strings.xml b/library/ui/src/main/res/values-lt/strings.xml new file mode 100644 index 0000000000..138caec322 --- /dev/null +++ b/library/ui/src/main/res/values-lt/strings.xml @@ -0,0 +1,25 @@ + + + + "Ankstesnis takelis" + "Kitas takelis" + "Pristabdyti" + "Leisti" + "Stabdyti" + "Sukti atgal" + "Sukti pirmyn" + diff --git a/library/ui/src/main/res/values-lv/strings.xml b/library/ui/src/main/res/values-lv/strings.xml new file mode 100644 index 0000000000..4c91da86cc --- /dev/null +++ b/library/ui/src/main/res/values-lv/strings.xml @@ -0,0 +1,25 @@ + + + + "Iepriekšējais ieraksts" + "Nākamais ieraksts" + "Pārtraukt" + "Atskaņot" + "Apturēt" + "Attīt atpakaļ" + "Ātri patīt" + diff --git a/library/ui/src/main/res/values-mk-rMK/strings.xml b/library/ui/src/main/res/values-mk-rMK/strings.xml new file mode 100644 index 0000000000..e9fedf689f --- /dev/null +++ b/library/ui/src/main/res/values-mk-rMK/strings.xml @@ -0,0 +1,25 @@ + + + + "Претходна песна" + "Следна песна" + "Пауза" + "Пушти" + "Запри" + "Премотај назад" + "Брзо премотај напред" + diff --git a/library/ui/src/main/res/values-ml-rIN/strings.xml b/library/ui/src/main/res/values-ml-rIN/strings.xml new file mode 100644 index 0000000000..acc33934fb --- /dev/null +++ b/library/ui/src/main/res/values-ml-rIN/strings.xml @@ -0,0 +1,25 @@ + + + + "മുമ്പത്തെ ട്രാക്ക്" + "അടുത്ത ട്രാക്ക്" + "താൽക്കാലികമായി നിർത്തുക" + "പ്ലേ ചെയ്യുക" + "നിര്‍ത്തുക" + "റിവൈൻഡുചെയ്യുക" + "വേഗത്തിലുള്ള കൈമാറൽ" + diff --git a/library/ui/src/main/res/values-mn-rMN/strings.xml b/library/ui/src/main/res/values-mn-rMN/strings.xml new file mode 100644 index 0000000000..6434e9ea16 --- /dev/null +++ b/library/ui/src/main/res/values-mn-rMN/strings.xml @@ -0,0 +1,25 @@ + + + + "Өмнөх трек" + "Дараагийн трек" + "Түр зогсоох" + "Тоглуулах" + "Зогсоох" + "Буцааж хураах" + "Хурдан урагшлуулах" + diff --git a/library/ui/src/main/res/values-mr-rIN/strings.xml b/library/ui/src/main/res/values-mr-rIN/strings.xml new file mode 100644 index 0000000000..8f4d0d75b1 --- /dev/null +++ b/library/ui/src/main/res/values-mr-rIN/strings.xml @@ -0,0 +1,25 @@ + + + + "मागील ट्रॅक" + "पुढील ट्रॅक" + "विराम द्या" + "प्ले करा" + "थांबा" + "रिवाईँड करा" + "फास्ट फॉरवर्ड करा" + diff --git a/library/ui/src/main/res/values-ms-rMY/strings.xml b/library/ui/src/main/res/values-ms-rMY/strings.xml new file mode 100644 index 0000000000..91f74bbc1c --- /dev/null +++ b/library/ui/src/main/res/values-ms-rMY/strings.xml @@ -0,0 +1,25 @@ + + + + "Lagu sebelumnya" + "Lagu seterusnya" + "Jeda" + "Main" + "Berhenti" + "Gulung semula" + "Mara laju" + diff --git a/library/ui/src/main/res/values-my-rMM/strings.xml b/library/ui/src/main/res/values-my-rMM/strings.xml new file mode 100644 index 0000000000..4b68e6e950 --- /dev/null +++ b/library/ui/src/main/res/values-my-rMM/strings.xml @@ -0,0 +1,25 @@ + + + + "ယခင် တစ်ပုဒ်" + "နောက် တစ်ပုဒ်" + "ခဏရပ်ရန်" + "ဖွင့်ရန်" + "ရပ်ရန်" + "ပြန်ရစ်ရန်" + "ရှေ့သို့ သွားရန်" + diff --git a/library/ui/src/main/res/values-nb/strings.xml b/library/ui/src/main/res/values-nb/strings.xml new file mode 100644 index 0000000000..37454235ad --- /dev/null +++ b/library/ui/src/main/res/values-nb/strings.xml @@ -0,0 +1,25 @@ + + + + "Forrige spor" + "Neste spor" + "Sett på pause" + "Spill av" + "Stopp" + "Tilbakespoling" + "Fremoverspoling" + diff --git a/library/ui/src/main/res/values-ne-rNP/strings.xml b/library/ui/src/main/res/values-ne-rNP/strings.xml new file mode 100644 index 0000000000..375e44afce --- /dev/null +++ b/library/ui/src/main/res/values-ne-rNP/strings.xml @@ -0,0 +1,25 @@ + + + + "अघिल्लो ट्रयाक" + "अर्को ट्रयाक" + "रोक्नुहोस्" + "चलाउनुहोस्" + "रोक्नुहोस्" + "दोहोर्याउनुहोस्" + "फास्ट फर्वार्ड" + diff --git a/library/ui/src/main/res/values-nl/strings.xml b/library/ui/src/main/res/values-nl/strings.xml new file mode 100644 index 0000000000..2bdbf0bdae --- /dev/null +++ b/library/ui/src/main/res/values-nl/strings.xml @@ -0,0 +1,25 @@ + + + + "Vorig nummer" + "Volgend nummer" + "Onderbreken" + "Afspelen" + "Stoppen" + "Terugspoelen" + "Vooruitspoelen" + diff --git a/library/ui/src/main/res/values-pa-rIN/strings.xml b/library/ui/src/main/res/values-pa-rIN/strings.xml new file mode 100644 index 0000000000..143508e071 --- /dev/null +++ b/library/ui/src/main/res/values-pa-rIN/strings.xml @@ -0,0 +1,25 @@ + + + + "ਪਿਛਲਾ ਟਰੈਕ" + "ਅਗਲਾ ਟਰੈਕ" + "ਰੋਕੋ" + "ਪਲੇ ਕਰੋ" + "ਰੋਕੋ" + "ਰੀਵਾਈਂਡ ਕਰੋ" + "ਅੱਗੇ ਭੇਜੋ" + diff --git a/library/ui/src/main/res/values-pl/strings.xml b/library/ui/src/main/res/values-pl/strings.xml new file mode 100644 index 0000000000..64f52d5d09 --- /dev/null +++ b/library/ui/src/main/res/values-pl/strings.xml @@ -0,0 +1,25 @@ + + + + "Poprzedni utwór" + "Następny utwór" + "Wstrzymaj" + "Odtwórz" + "Zatrzymaj" + "Przewiń do tyłu" + "Przewiń do przodu" + diff --git a/library/ui/src/main/res/values-pt-rBR/strings.xml b/library/ui/src/main/res/values-pt-rBR/strings.xml new file mode 100644 index 0000000000..51bcf4d723 --- /dev/null +++ b/library/ui/src/main/res/values-pt-rBR/strings.xml @@ -0,0 +1,25 @@ + + + + "Faixa anterior" + "Próxima faixa" + "Pausar" + "Reproduzir" + "Parar" + "Retroceder" + "Avançar" + diff --git a/library/ui/src/main/res/values-pt-rPT/strings.xml b/library/ui/src/main/res/values-pt-rPT/strings.xml new file mode 100644 index 0000000000..5b3c9131d0 --- /dev/null +++ b/library/ui/src/main/res/values-pt-rPT/strings.xml @@ -0,0 +1,25 @@ + + + + "Faixa anterior" + "Faixa seguinte" + "Interromper" + "Reproduzir" + "Parar" + "Rebobinar" + "Avançar" + diff --git a/library/ui/src/main/res/values-pt/strings.xml b/library/ui/src/main/res/values-pt/strings.xml new file mode 100644 index 0000000000..51bcf4d723 --- /dev/null +++ b/library/ui/src/main/res/values-pt/strings.xml @@ -0,0 +1,25 @@ + + + + "Faixa anterior" + "Próxima faixa" + "Pausar" + "Reproduzir" + "Parar" + "Retroceder" + "Avançar" + diff --git a/library/ui/src/main/res/values-ro/strings.xml b/library/ui/src/main/res/values-ro/strings.xml new file mode 100644 index 0000000000..5a7feda78c --- /dev/null +++ b/library/ui/src/main/res/values-ro/strings.xml @@ -0,0 +1,25 @@ + + + + "Melodia anterioară" + "Melodia următoare" + "Pauză" + "Redați" + "Opriți" + "Derulați" + "Derulați rapid înainte" + diff --git a/library/ui/src/main/res/values-ru/strings.xml b/library/ui/src/main/res/values-ru/strings.xml new file mode 100644 index 0000000000..da47546a8b --- /dev/null +++ b/library/ui/src/main/res/values-ru/strings.xml @@ -0,0 +1,25 @@ + + + + "Предыдущий трек" + "Следующий трек" + "Приостановить" + "Воспроизвести" + "Остановить" + "Перемотать назад" + "Перемотать вперед" + diff --git a/library/ui/src/main/res/values-si-rLK/strings.xml b/library/ui/src/main/res/values-si-rLK/strings.xml new file mode 100644 index 0000000000..0b579240e8 --- /dev/null +++ b/library/ui/src/main/res/values-si-rLK/strings.xml @@ -0,0 +1,25 @@ + + + + "පෙර ගීතය" + "ඊළඟ ගීතය" + "විරාමය" + "ධාවනය කරන්න" + "නතර කරන්න" + "නැවත ඔතන්න" + "වේගයෙන් ඉදිරියට යන" + diff --git a/library/ui/src/main/res/values-sk/strings.xml b/library/ui/src/main/res/values-sk/strings.xml new file mode 100644 index 0000000000..7596497e06 --- /dev/null +++ b/library/ui/src/main/res/values-sk/strings.xml @@ -0,0 +1,25 @@ + + + + "Predchádzajúca stopa" + "Ďalšia stopa" + "Pozastaviť" + "Prehrať" + "Zastaviť" + "Pretočiť späť" + "Pretočiť dopredu" + diff --git a/library/ui/src/main/res/values-sl/strings.xml b/library/ui/src/main/res/values-sl/strings.xml new file mode 100644 index 0000000000..a77586b50c --- /dev/null +++ b/library/ui/src/main/res/values-sl/strings.xml @@ -0,0 +1,25 @@ + + + + "Prejšnja skladba" + "Naslednja skladba" + "Zaustavi" + "Predvajaj" + "Ustavi" + "Previj nazaj" + "Previj naprej" + diff --git a/library/ui/src/main/res/values-sq-rAL/strings.xml b/library/ui/src/main/res/values-sq-rAL/strings.xml new file mode 100644 index 0000000000..1fb824366d --- /dev/null +++ b/library/ui/src/main/res/values-sq-rAL/strings.xml @@ -0,0 +1,25 @@ + + + + "Kënga e mëparshme" + "Kënga tjetër" + "Pauzë" + "Luaj" + "Ndalo" + "Kthehu pas" + "Përparo me shpejtësi" + diff --git a/library/ui/src/main/res/values-sr/strings.xml b/library/ui/src/main/res/values-sr/strings.xml new file mode 100644 index 0000000000..175ad4fe7f --- /dev/null +++ b/library/ui/src/main/res/values-sr/strings.xml @@ -0,0 +1,25 @@ + + + + "Претходна песма" + "Следећа песма" + "Пауза" + "Пусти" + "Заустави" + "Премотај уназад" + "Премотај унапред" + diff --git a/library/ui/src/main/res/values-sv/strings.xml b/library/ui/src/main/res/values-sv/strings.xml new file mode 100644 index 0000000000..e6a8960458 --- /dev/null +++ b/library/ui/src/main/res/values-sv/strings.xml @@ -0,0 +1,25 @@ + + + + "Föregående spår" + "Nästa spår" + "Pausa" + "Spela upp" + "Avbryt" + "Spola tillbaka" + "Snabbspola framåt" + diff --git a/library/ui/src/main/res/values-sw/strings.xml b/library/ui/src/main/res/values-sw/strings.xml new file mode 100644 index 0000000000..8055b7daff --- /dev/null +++ b/library/ui/src/main/res/values-sw/strings.xml @@ -0,0 +1,25 @@ + + + + "Wimbo uliotangulia" + "Wimbo unaofuata" + "Sitisha" + "Cheza" + "Simamisha" + "Rudisha nyuma" + "Peleka mbele kwa kasi" + diff --git a/library/ui/src/main/res/values-ta-rIN/strings.xml b/library/ui/src/main/res/values-ta-rIN/strings.xml new file mode 100644 index 0000000000..3eb995d467 --- /dev/null +++ b/library/ui/src/main/res/values-ta-rIN/strings.xml @@ -0,0 +1,25 @@ + + + + "முந்தைய ட்ராக்" + "அடுத்த ட்ராக்" + "இடைநிறுத்து" + "இயக்கு" + "நிறுத்து" + "மீண்டும் காட்டு" + "வேகமாக முன்செல்" + diff --git a/library/ui/src/main/res/values-te-rIN/strings.xml b/library/ui/src/main/res/values-te-rIN/strings.xml new file mode 100644 index 0000000000..fe7930455a --- /dev/null +++ b/library/ui/src/main/res/values-te-rIN/strings.xml @@ -0,0 +1,25 @@ + + + + "మునుపటి ట్రాక్" + "తదుపరి ట్రాక్" + "పాజ్ చేయి" + "ప్లే చేయి" + "ఆపివేయి" + "రివైండ్ చేయి" + "వేగంగా ఫార్వార్డ్ చేయి" + diff --git a/library/ui/src/main/res/values-th/strings.xml b/library/ui/src/main/res/values-th/strings.xml new file mode 100644 index 0000000000..deb2aac87d --- /dev/null +++ b/library/ui/src/main/res/values-th/strings.xml @@ -0,0 +1,25 @@ + + + + "แทร็กก่อนหน้า" + "แทร็กถัดไป" + "หยุดชั่วคราว" + "เล่น" + "หยุด" + "กรอกลับ" + "กรอไปข้างหน้า" + diff --git a/library/ui/src/main/res/values-tl/strings.xml b/library/ui/src/main/res/values-tl/strings.xml new file mode 100644 index 0000000000..28dcb3267e --- /dev/null +++ b/library/ui/src/main/res/values-tl/strings.xml @@ -0,0 +1,25 @@ + + + + "Nakaraang track" + "Susunod na track" + "I-pause" + "I-play" + "Ihinto" + "I-rewind" + "I-fast forward" + diff --git a/library/ui/src/main/res/values-tr/strings.xml b/library/ui/src/main/res/values-tr/strings.xml new file mode 100644 index 0000000000..4265d796fe --- /dev/null +++ b/library/ui/src/main/res/values-tr/strings.xml @@ -0,0 +1,25 @@ + + + + "Önceki parça" + "Sonraki parça" + "Duraklat" + "Çal" + "Durdur" + "Geri sar" + "İleri sar" + diff --git a/library/ui/src/main/res/values-uk/strings.xml b/library/ui/src/main/res/values-uk/strings.xml new file mode 100644 index 0000000000..487ca07556 --- /dev/null +++ b/library/ui/src/main/res/values-uk/strings.xml @@ -0,0 +1,25 @@ + + + + "Попередня композиція" + "Наступна композиція" + "Пауза" + "Відтворити" + "Зупинити" + "Перемотати назад" + "Перемотати вперед" + diff --git a/library/ui/src/main/res/values-ur-rPK/strings.xml b/library/ui/src/main/res/values-ur-rPK/strings.xml new file mode 100644 index 0000000000..55fa908bcd --- /dev/null +++ b/library/ui/src/main/res/values-ur-rPK/strings.xml @@ -0,0 +1,25 @@ + + + + "پچھلا ٹریک" + "اگلا ٹریک" + "موقوف کریں" + "چلائیں" + "روکیں" + "ریوائینڈ کریں" + "تیزی سے فارورڈ کریں" + diff --git a/library/ui/src/main/res/values-uz-rUZ/strings.xml b/library/ui/src/main/res/values-uz-rUZ/strings.xml new file mode 100644 index 0000000000..9cee926844 --- /dev/null +++ b/library/ui/src/main/res/values-uz-rUZ/strings.xml @@ -0,0 +1,25 @@ + + + + "Avvalgi musiqa" + "Keyingi musiqa" + "To‘xtatib turish" + "Ijro qilish" + "To‘xtatish" + "Orqaga o‘tkazish" + "Oldinga o‘tkazish" + diff --git a/library/ui/src/main/res/values-v11/styles.xml b/library/ui/src/main/res/values-v11/styles.xml new file mode 100644 index 0000000000..6f77440287 --- /dev/null +++ b/library/ui/src/main/res/values-v11/styles.xml @@ -0,0 +1,24 @@ + + + + + + + diff --git a/library/ui/src/main/res/values-vi/strings.xml b/library/ui/src/main/res/values-vi/strings.xml new file mode 100644 index 0000000000..917ec8e95c --- /dev/null +++ b/library/ui/src/main/res/values-vi/strings.xml @@ -0,0 +1,25 @@ + + + + "Bản nhạc trước" + "Bản nhạc tiếp theo" + "Tạm dừng" + "Phát" + "Ngừng" + "Tua lại" + "Tua đi" + diff --git a/library/ui/src/main/res/values-zh-rCN/strings.xml b/library/ui/src/main/res/values-zh-rCN/strings.xml new file mode 100644 index 0000000000..41e02409e2 --- /dev/null +++ b/library/ui/src/main/res/values-zh-rCN/strings.xml @@ -0,0 +1,25 @@ + + + + "上一曲" + "下一曲" + "暂停" + "播放" + "停止" + "快退" + "快进" + diff --git a/library/ui/src/main/res/values-zh-rHK/strings.xml b/library/ui/src/main/res/values-zh-rHK/strings.xml new file mode 100644 index 0000000000..a3244bcd70 --- /dev/null +++ b/library/ui/src/main/res/values-zh-rHK/strings.xml @@ -0,0 +1,25 @@ + + + + "上一首曲目" + "下一首曲目" + "暫停" + "播放" + "停止" + "倒帶" + "向前快轉" + diff --git a/library/ui/src/main/res/values-zh-rTW/strings.xml b/library/ui/src/main/res/values-zh-rTW/strings.xml new file mode 100644 index 0000000000..ee915c5d9d --- /dev/null +++ b/library/ui/src/main/res/values-zh-rTW/strings.xml @@ -0,0 +1,25 @@ + + + + "上一首曲目" + "下一首曲目" + "暫停" + "播放" + "停止" + "倒轉" + "快轉" + diff --git a/library/ui/src/main/res/values-zu/strings.xml b/library/ui/src/main/res/values-zu/strings.xml new file mode 100644 index 0000000000..e998846454 --- /dev/null +++ b/library/ui/src/main/res/values-zu/strings.xml @@ -0,0 +1,25 @@ + + + + "Ithrekhi yangaphambilini" + "Ithrekhi elandelayo" + "Misa isikhashana" + "Dlala" + "Misa" + "Buyisela emumva" + "Ukudlulisa ngokushesha" + diff --git a/library/ui/src/main/res/values/attrs.xml b/library/ui/src/main/res/values/attrs.xml new file mode 100644 index 0000000000..c73bfb0a3c --- /dev/null +++ b/library/ui/src/main/res/values/attrs.xml @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/library/ui/src/main/res/values/constants.xml b/library/ui/src/main/res/values/constants.xml new file mode 100644 index 0000000000..5c86696ea0 --- /dev/null +++ b/library/ui/src/main/res/values/constants.xml @@ -0,0 +1,21 @@ + + + + + 71dp + 52dp + + diff --git a/library/ui/src/main/res/values/ids.xml b/library/ui/src/main/res/values/ids.xml new file mode 100644 index 0000000000..61db83825e --- /dev/null +++ b/library/ui/src/main/res/values/ids.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/library/ui/src/main/res/values/strings.xml b/library/ui/src/main/res/values/strings.xml new file mode 100644 index 0000000000..1e652dddb3 --- /dev/null +++ b/library/ui/src/main/res/values/strings.xml @@ -0,0 +1,24 @@ + + + + Previous track + Next track + Pause + Play + Stop + Rewind + Fast forward + diff --git a/library/ui/src/main/res/values/styles.xml b/library/ui/src/main/res/values/styles.xml new file mode 100644 index 0000000000..a67cffe420 --- /dev/null +++ b/library/ui/src/main/res/values/styles.xml @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + + diff --git a/settings.gradle b/settings.gradle index 9578420959..4961921068 100644 --- a/settings.gradle +++ b/settings.gradle @@ -13,6 +13,7 @@ // limitations under the License. include ':library' include ':library-core' +include ':library-ui' include ':testutils' include ':demo' include ':playbacktests' @@ -27,6 +28,7 @@ include ':extension-vp9' project(':library').projectDir = new File(settingsDir, 'library/all') project(':library-core').projectDir = new File(settingsDir, 'library/core') +project(':library-ui').projectDir = new File(settingsDir, 'library/ui') project(':extension-ffmpeg').projectDir = new File(settingsDir, 'extensions/ffmpeg') project(':extension-flac').projectDir = new File(settingsDir, 'extensions/flac') project(':extension-gvr').projectDir = new File(settingsDir, 'extensions/gvr') From a9aca8dbf057a03f06292223f723c40b0f1fbded Mon Sep 17 00:00:00 2001 From: olly Date: Mon, 20 Mar 2017 09:33:02 -0700 Subject: [PATCH 008/119] Split SmoothStreaming into a separate module Issue: #2139 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=150634794 --- library/all/build.gradle | 1 + .../google/android/exoplayer2/ExoPlayer.java | 14 +++--- library/smoothstreaming/build.gradle | 44 +++++++++++++++++++ .../src/androidTest/AndroidManifest.xml | 34 ++++++++++++++ .../src/androidTest/assets}/sample_ismc_1 | 0 .../src/androidTest/assets}/sample_ismc_2 | 0 .../manifest/SsManifestParserTest.java | 4 +- .../src/main/AndroidManifest.xml | 17 +++++++ .../smoothstreaming/DefaultSsChunkSource.java | 0 .../source/smoothstreaming/SsChunkSource.java | 0 .../source/smoothstreaming/SsMediaPeriod.java | 0 .../source/smoothstreaming/SsMediaSource.java | 0 .../smoothstreaming/manifest/SsManifest.java | 0 .../manifest/SsManifestParser.java | 0 settings.gradle | 2 + 15 files changed, 105 insertions(+), 11 deletions(-) create mode 100644 library/smoothstreaming/build.gradle create mode 100644 library/smoothstreaming/src/androidTest/AndroidManifest.xml rename library/{core/src/androidTest/assets/smoothstreaming => smoothstreaming/src/androidTest/assets}/sample_ismc_1 (100%) rename library/{core/src/androidTest/assets/smoothstreaming => smoothstreaming/src/androidTest/assets}/sample_ismc_2 (100%) rename library/{core => smoothstreaming}/src/androidTest/java/com/google/android/exoplayer2/source/smoothstreaming/manifest/SsManifestParserTest.java (90%) create mode 100644 library/smoothstreaming/src/main/AndroidManifest.xml rename library/{core => smoothstreaming}/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/DefaultSsChunkSource.java (100%) rename library/{core => smoothstreaming}/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsChunkSource.java (100%) rename library/{core => smoothstreaming}/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsMediaPeriod.java (100%) rename library/{core => smoothstreaming}/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsMediaSource.java (100%) rename library/{core => smoothstreaming}/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/manifest/SsManifest.java (100%) rename library/{core => smoothstreaming}/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/manifest/SsManifestParser.java (100%) diff --git a/library/all/build.gradle b/library/all/build.gradle index 0b7753a9be..93a0c03da8 100644 --- a/library/all/build.gradle +++ b/library/all/build.gradle @@ -28,6 +28,7 @@ android { dependencies { compile project(':library-core') compile project(':library-ui') + compile project(':library-smoothstreaming') } android.libraryVariants.all { variant -> diff --git a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayer.java b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayer.java index 083569416c..f7dbea851a 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayer.java @@ -23,9 +23,6 @@ import com.google.android.exoplayer2.source.MediaSource; import com.google.android.exoplayer2.source.MergingMediaSource; import com.google.android.exoplayer2.source.SingleSampleMediaSource; import com.google.android.exoplayer2.source.TrackGroupArray; -import com.google.android.exoplayer2.source.dash.DashMediaSource; -import com.google.android.exoplayer2.source.hls.HlsMediaSource; -import com.google.android.exoplayer2.source.smoothstreaming.SsMediaSource; import com.google.android.exoplayer2.text.TextRenderer; import com.google.android.exoplayer2.trackselection.DefaultTrackSelector; import com.google.android.exoplayer2.trackselection.TrackSelectionArray; @@ -47,12 +44,11 @@ import com.google.android.exoplayer2.video.MediaCodecVideoRenderer; *

    *
  • A {@link MediaSource} that defines the media to be played, loads the media, and from * which the loaded media can be read. A MediaSource is injected via {@link #prepare} at the start - * of playback. The library provides default implementations for regular media files - * ({@link ExtractorMediaSource}), DASH ({@link DashMediaSource}), SmoothStreaming - * ({@link SsMediaSource}) and HLS ({@link HlsMediaSource}), implementations for merging - * ({@link MergingMediaSource}) and concatenating ({@link ConcatenatingMediaSource}) other - * MediaSources, and an implementation for loading single samples - * ({@link SingleSampleMediaSource}) most often used for side-loaded subtitle and closed + * of playback. The library modules provide default implementations for regular media files + * ({@link ExtractorMediaSource}), DASH (DashMediaSource), SmoothStreaming (SsMediaSource) and HLS + * (HlsMediaSource), implementations for merging ({@link MergingMediaSource}) and concatenating + * ({@link ConcatenatingMediaSource}) other MediaSources, and an implementation for loading single + * samples ({@link SingleSampleMediaSource}) most often used for side-loaded subtitle and closed * caption files.
  • *
  • {@link Renderer}s that render individual components of the media. The library * provides default implementations for common media types ({@link MediaCodecVideoRenderer}, diff --git a/library/smoothstreaming/build.gradle b/library/smoothstreaming/build.gradle new file mode 100644 index 0000000000..bcec8f5319 --- /dev/null +++ b/library/smoothstreaming/build.gradle @@ -0,0 +1,44 @@ +// Copyright (C) 2017 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +apply plugin: 'com.android.library' + +android { + compileSdkVersion project.ext.compileSdkVersion + buildToolsVersion project.ext.buildToolsVersion + + defaultConfig { + minSdkVersion project.ext.minSdkVersion + targetSdkVersion project.ext.targetSdkVersion + } + + sourceSets { + androidTest { + java.srcDirs += "../../testutils/src/main/java/" + } + } +} + +dependencies { + compile project(':library-core') + compile 'com.android.support:support-annotations:25.2.0' + androidTestCompile 'com.google.dexmaker:dexmaker:1.2' + androidTestCompile 'com.google.dexmaker:dexmaker-mockito:1.2' + androidTestCompile 'org.mockito:mockito-core:1.9.5' +} + +ext { + releaseArtifact = 'exoplayer-smoothstreaming' + releaseDescription = 'The ExoPlayer library SmoothStreaming module.' +} +apply from: '../../publish.gradle' diff --git a/library/smoothstreaming/src/androidTest/AndroidManifest.xml b/library/smoothstreaming/src/androidTest/AndroidManifest.xml new file mode 100644 index 0000000000..9f62e26867 --- /dev/null +++ b/library/smoothstreaming/src/androidTest/AndroidManifest.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + diff --git a/library/core/src/androidTest/assets/smoothstreaming/sample_ismc_1 b/library/smoothstreaming/src/androidTest/assets/sample_ismc_1 similarity index 100% rename from library/core/src/androidTest/assets/smoothstreaming/sample_ismc_1 rename to library/smoothstreaming/src/androidTest/assets/sample_ismc_1 diff --git a/library/core/src/androidTest/assets/smoothstreaming/sample_ismc_2 b/library/smoothstreaming/src/androidTest/assets/sample_ismc_2 similarity index 100% rename from library/core/src/androidTest/assets/smoothstreaming/sample_ismc_2 rename to library/smoothstreaming/src/androidTest/assets/sample_ismc_2 diff --git a/library/core/src/androidTest/java/com/google/android/exoplayer2/source/smoothstreaming/manifest/SsManifestParserTest.java b/library/smoothstreaming/src/androidTest/java/com/google/android/exoplayer2/source/smoothstreaming/manifest/SsManifestParserTest.java similarity index 90% rename from library/core/src/androidTest/java/com/google/android/exoplayer2/source/smoothstreaming/manifest/SsManifestParserTest.java rename to library/smoothstreaming/src/androidTest/java/com/google/android/exoplayer2/source/smoothstreaming/manifest/SsManifestParserTest.java index 8116755a0a..4663f014ff 100644 --- a/library/core/src/androidTest/java/com/google/android/exoplayer2/source/smoothstreaming/manifest/SsManifestParserTest.java +++ b/library/smoothstreaming/src/androidTest/java/com/google/android/exoplayer2/source/smoothstreaming/manifest/SsManifestParserTest.java @@ -25,8 +25,8 @@ import java.io.IOException; */ public final class SsManifestParserTest extends InstrumentationTestCase { - private static final String SAMPLE_ISMC_1 = "smoothstreaming/sample_ismc_1"; - private static final String SAMPLE_ISMC_2 = "smoothstreaming/sample_ismc_2"; + private static final String SAMPLE_ISMC_1 = "sample_ismc_1"; + private static final String SAMPLE_ISMC_2 = "sample_ismc_2"; /** * Simple test to ensure the sample manifests parse without any exceptions being thrown. diff --git a/library/smoothstreaming/src/main/AndroidManifest.xml b/library/smoothstreaming/src/main/AndroidManifest.xml new file mode 100644 index 0000000000..49b47f0916 --- /dev/null +++ b/library/smoothstreaming/src/main/AndroidManifest.xml @@ -0,0 +1,17 @@ + + + + diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/DefaultSsChunkSource.java b/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/DefaultSsChunkSource.java similarity index 100% rename from library/core/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/DefaultSsChunkSource.java rename to library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/DefaultSsChunkSource.java diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsChunkSource.java b/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsChunkSource.java similarity index 100% rename from library/core/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsChunkSource.java rename to library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsChunkSource.java diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsMediaPeriod.java b/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsMediaPeriod.java similarity index 100% rename from library/core/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsMediaPeriod.java rename to library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsMediaPeriod.java diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsMediaSource.java b/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsMediaSource.java similarity index 100% rename from library/core/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsMediaSource.java rename to library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsMediaSource.java diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/manifest/SsManifest.java b/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/manifest/SsManifest.java similarity index 100% rename from library/core/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/manifest/SsManifest.java rename to library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/manifest/SsManifest.java diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/manifest/SsManifestParser.java b/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/manifest/SsManifestParser.java similarity index 100% rename from library/core/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/manifest/SsManifestParser.java rename to library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/manifest/SsManifestParser.java diff --git a/settings.gradle b/settings.gradle index 4961921068..f76909864e 100644 --- a/settings.gradle +++ b/settings.gradle @@ -13,6 +13,7 @@ // limitations under the License. include ':library' include ':library-core' +include ':library-smoothstreaming' include ':library-ui' include ':testutils' include ':demo' @@ -28,6 +29,7 @@ include ':extension-vp9' project(':library').projectDir = new File(settingsDir, 'library/all') project(':library-core').projectDir = new File(settingsDir, 'library/core') +project(':library-smoothstreaming').projectDir = new File(settingsDir, 'library/smoothstreaming') project(':library-ui').projectDir = new File(settingsDir, 'library/ui') project(':extension-ffmpeg').projectDir = new File(settingsDir, 'extensions/ffmpeg') project(':extension-flac').projectDir = new File(settingsDir, 'extensions/flac') From 25a093b37c75ecd1853ede3570df6e3cbfac038e Mon Sep 17 00:00:00 2001 From: olly Date: Mon, 20 Mar 2017 10:34:26 -0700 Subject: [PATCH 009/119] Split HLS into a separate module Issue: #2139 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=150643456 --- library/all/build.gradle | 3 +- library/hls/build.gradle | 38 +++++++++++++++++++ .../hls/src/androidTest/AndroidManifest.xml | 34 +++++++++++++++++ .../playlist/HlsMasterPlaylistParserTest.java | 0 .../playlist/HlsMediaPlaylistParserTest.java | 0 library/hls/src/main/AndroidManifest.xml | 17 +++++++++ .../source/hls/Aes128DataSource.java | 0 .../hls/DefaultHlsDataSourceFactory.java | 0 .../exoplayer2/source/hls/HlsChunkSource.java | 0 .../source/hls/HlsDataSourceFactory.java | 0 .../exoplayer2/source/hls/HlsManifest.java | 0 .../exoplayer2/source/hls/HlsMediaChunk.java | 0 .../exoplayer2/source/hls/HlsMediaPeriod.java | 0 .../exoplayer2/source/hls/HlsMediaSource.java | 0 .../source/hls/HlsSampleStream.java | 0 .../source/hls/HlsSampleStreamWrapper.java | 0 .../source/hls/TimestampAdjusterProvider.java | 0 .../source/hls/WebvttExtractor.java | 0 .../hls/playlist/HlsMasterPlaylist.java | 0 .../source/hls/playlist/HlsMediaPlaylist.java | 0 .../source/hls/playlist/HlsPlaylist.java | 0 .../hls/playlist/HlsPlaylistParser.java | 0 .../hls/playlist/HlsPlaylistTracker.java | 0 playbacktests/build.gradle | 3 +- settings.gradle | 2 + 25 files changed, 95 insertions(+), 2 deletions(-) create mode 100644 library/hls/build.gradle create mode 100644 library/hls/src/androidTest/AndroidManifest.xml rename library/{core => hls}/src/androidTest/java/com/google/android/exoplayer2/source/hls/playlist/HlsMasterPlaylistParserTest.java (100%) rename library/{core => hls}/src/androidTest/java/com/google/android/exoplayer2/source/hls/playlist/HlsMediaPlaylistParserTest.java (100%) create mode 100644 library/hls/src/main/AndroidManifest.xml rename library/{core => hls}/src/main/java/com/google/android/exoplayer2/source/hls/Aes128DataSource.java (100%) rename library/{core => hls}/src/main/java/com/google/android/exoplayer2/source/hls/DefaultHlsDataSourceFactory.java (100%) rename library/{core => hls}/src/main/java/com/google/android/exoplayer2/source/hls/HlsChunkSource.java (100%) rename library/{core => hls}/src/main/java/com/google/android/exoplayer2/source/hls/HlsDataSourceFactory.java (100%) rename library/{core => hls}/src/main/java/com/google/android/exoplayer2/source/hls/HlsManifest.java (100%) rename library/{core => hls}/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaChunk.java (100%) rename library/{core => hls}/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaPeriod.java (100%) rename library/{core => hls}/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaSource.java (100%) rename library/{core => hls}/src/main/java/com/google/android/exoplayer2/source/hls/HlsSampleStream.java (100%) rename library/{core => hls}/src/main/java/com/google/android/exoplayer2/source/hls/HlsSampleStreamWrapper.java (100%) rename library/{core => hls}/src/main/java/com/google/android/exoplayer2/source/hls/TimestampAdjusterProvider.java (100%) rename library/{core => hls}/src/main/java/com/google/android/exoplayer2/source/hls/WebvttExtractor.java (100%) rename library/{core => hls}/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsMasterPlaylist.java (100%) rename library/{core => hls}/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsMediaPlaylist.java (100%) rename library/{core => hls}/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylist.java (100%) rename library/{core => hls}/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylistParser.java (100%) rename library/{core => hls}/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylistTracker.java (100%) diff --git a/library/all/build.gradle b/library/all/build.gradle index 93a0c03da8..c0874352b3 100644 --- a/library/all/build.gradle +++ b/library/all/build.gradle @@ -27,8 +27,9 @@ android { dependencies { compile project(':library-core') - compile project(':library-ui') + compile project(':library-hls') compile project(':library-smoothstreaming') + compile project(':library-ui') } android.libraryVariants.all { variant -> diff --git a/library/hls/build.gradle b/library/hls/build.gradle new file mode 100644 index 0000000000..bf19de9abd --- /dev/null +++ b/library/hls/build.gradle @@ -0,0 +1,38 @@ +// Copyright (C) 2017 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +apply plugin: 'com.android.library' + +android { + compileSdkVersion project.ext.compileSdkVersion + buildToolsVersion project.ext.buildToolsVersion + + defaultConfig { + minSdkVersion project.ext.minSdkVersion + targetSdkVersion project.ext.targetSdkVersion + } +} + +dependencies { + compile project(':library-core') + compile 'com.android.support:support-annotations:25.2.0' + androidTestCompile 'com.google.dexmaker:dexmaker:1.2' + androidTestCompile 'com.google.dexmaker:dexmaker-mockito:1.2' + androidTestCompile 'org.mockito:mockito-core:1.9.5' +} + +ext { + releaseArtifact = 'exoplayer-hls' + releaseDescription = 'The ExoPlayer library HLS module.' +} +apply from: '../../publish.gradle' diff --git a/library/hls/src/androidTest/AndroidManifest.xml b/library/hls/src/androidTest/AndroidManifest.xml new file mode 100644 index 0000000000..ac0857fc3f --- /dev/null +++ b/library/hls/src/androidTest/AndroidManifest.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + diff --git a/library/core/src/androidTest/java/com/google/android/exoplayer2/source/hls/playlist/HlsMasterPlaylistParserTest.java b/library/hls/src/androidTest/java/com/google/android/exoplayer2/source/hls/playlist/HlsMasterPlaylistParserTest.java similarity index 100% rename from library/core/src/androidTest/java/com/google/android/exoplayer2/source/hls/playlist/HlsMasterPlaylistParserTest.java rename to library/hls/src/androidTest/java/com/google/android/exoplayer2/source/hls/playlist/HlsMasterPlaylistParserTest.java diff --git a/library/core/src/androidTest/java/com/google/android/exoplayer2/source/hls/playlist/HlsMediaPlaylistParserTest.java b/library/hls/src/androidTest/java/com/google/android/exoplayer2/source/hls/playlist/HlsMediaPlaylistParserTest.java similarity index 100% rename from library/core/src/androidTest/java/com/google/android/exoplayer2/source/hls/playlist/HlsMediaPlaylistParserTest.java rename to library/hls/src/androidTest/java/com/google/android/exoplayer2/source/hls/playlist/HlsMediaPlaylistParserTest.java diff --git a/library/hls/src/main/AndroidManifest.xml b/library/hls/src/main/AndroidManifest.xml new file mode 100644 index 0000000000..1ed28f4c7a --- /dev/null +++ b/library/hls/src/main/AndroidManifest.xml @@ -0,0 +1,17 @@ + + + + diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/hls/Aes128DataSource.java b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/Aes128DataSource.java similarity index 100% rename from library/core/src/main/java/com/google/android/exoplayer2/source/hls/Aes128DataSource.java rename to library/hls/src/main/java/com/google/android/exoplayer2/source/hls/Aes128DataSource.java diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/hls/DefaultHlsDataSourceFactory.java b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/DefaultHlsDataSourceFactory.java similarity index 100% rename from library/core/src/main/java/com/google/android/exoplayer2/source/hls/DefaultHlsDataSourceFactory.java rename to library/hls/src/main/java/com/google/android/exoplayer2/source/hls/DefaultHlsDataSourceFactory.java diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/hls/HlsChunkSource.java b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsChunkSource.java similarity index 100% rename from library/core/src/main/java/com/google/android/exoplayer2/source/hls/HlsChunkSource.java rename to library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsChunkSource.java diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/hls/HlsDataSourceFactory.java b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsDataSourceFactory.java similarity index 100% rename from library/core/src/main/java/com/google/android/exoplayer2/source/hls/HlsDataSourceFactory.java rename to library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsDataSourceFactory.java diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/hls/HlsManifest.java b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsManifest.java similarity index 100% rename from library/core/src/main/java/com/google/android/exoplayer2/source/hls/HlsManifest.java rename to library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsManifest.java diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaChunk.java b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaChunk.java similarity index 100% rename from library/core/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaChunk.java rename to library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaChunk.java diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaPeriod.java b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaPeriod.java similarity index 100% rename from library/core/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaPeriod.java rename to library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaPeriod.java diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaSource.java b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaSource.java similarity index 100% rename from library/core/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaSource.java rename to library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaSource.java diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/hls/HlsSampleStream.java b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsSampleStream.java similarity index 100% rename from library/core/src/main/java/com/google/android/exoplayer2/source/hls/HlsSampleStream.java rename to library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsSampleStream.java diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/hls/HlsSampleStreamWrapper.java b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsSampleStreamWrapper.java similarity index 100% rename from library/core/src/main/java/com/google/android/exoplayer2/source/hls/HlsSampleStreamWrapper.java rename to library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsSampleStreamWrapper.java diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/hls/TimestampAdjusterProvider.java b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/TimestampAdjusterProvider.java similarity index 100% rename from library/core/src/main/java/com/google/android/exoplayer2/source/hls/TimestampAdjusterProvider.java rename to library/hls/src/main/java/com/google/android/exoplayer2/source/hls/TimestampAdjusterProvider.java diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/hls/WebvttExtractor.java b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/WebvttExtractor.java similarity index 100% rename from library/core/src/main/java/com/google/android/exoplayer2/source/hls/WebvttExtractor.java rename to library/hls/src/main/java/com/google/android/exoplayer2/source/hls/WebvttExtractor.java diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsMasterPlaylist.java b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsMasterPlaylist.java similarity index 100% rename from library/core/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsMasterPlaylist.java rename to library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsMasterPlaylist.java diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsMediaPlaylist.java b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsMediaPlaylist.java similarity index 100% rename from library/core/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsMediaPlaylist.java rename to library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsMediaPlaylist.java diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylist.java b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylist.java similarity index 100% rename from library/core/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylist.java rename to library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylist.java diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylistParser.java b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylistParser.java similarity index 100% rename from library/core/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylistParser.java rename to library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylistParser.java diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylistTracker.java b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylistTracker.java similarity index 100% rename from library/core/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylistTracker.java rename to library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylistTracker.java diff --git a/playbacktests/build.gradle b/playbacktests/build.gradle index cb82d0a466..fa3c930c4a 100644 --- a/playbacktests/build.gradle +++ b/playbacktests/build.gradle @@ -24,5 +24,6 @@ android { } dependencies { - compile project(':library') + compile project(':library-core') + androidTestCompile project(':library-hls') } diff --git a/settings.gradle b/settings.gradle index f76909864e..45e2a1a1f1 100644 --- a/settings.gradle +++ b/settings.gradle @@ -13,6 +13,7 @@ // limitations under the License. include ':library' include ':library-core' +include ':library-hls' include ':library-smoothstreaming' include ':library-ui' include ':testutils' @@ -29,6 +30,7 @@ include ':extension-vp9' project(':library').projectDir = new File(settingsDir, 'library/all') project(':library-core').projectDir = new File(settingsDir, 'library/core') +project(':library-hls').projectDir = new File(settingsDir, 'library/hls') project(':library-smoothstreaming').projectDir = new File(settingsDir, 'library/smoothstreaming') project(':library-ui').projectDir = new File(settingsDir, 'library/ui') project(':extension-ffmpeg').projectDir = new File(settingsDir, 'extensions/ffmpeg') From c8b9c7fe68588d72495ba01265c26d0a779b3c24 Mon Sep 17 00:00:00 2001 From: vigneshv Date: Mon, 20 Mar 2017 11:08:53 -0700 Subject: [PATCH 010/119] extensions/vp9: Control symbol visibility with libvpx.ver This change reduces the final .so size by ~9% for armv7/arm64. Arch : Size_before : Size_after armeabi-v7a : 300548 : 271876 arm64-v8a : 247632 : 223056 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=150648668 --- extensions/vp9/src/main/jni/generate_libvpx_android_configs.sh | 2 +- extensions/vp9/src/main/jni/libvpx.mk | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/extensions/vp9/src/main/jni/generate_libvpx_android_configs.sh b/extensions/vp9/src/main/jni/generate_libvpx_android_configs.sh index 566396e0bf..15dbabdb1f 100755 --- a/extensions/vp9/src/main/jni/generate_libvpx_android_configs.sh +++ b/extensions/vp9/src/main/jni/generate_libvpx_android_configs.sh @@ -68,7 +68,7 @@ limit=$((${#arch[@]} - 1)) # everything else will be removed. allowed_files="libvpx_srcs.txt vpx_config.c vpx_config.h vpx_scale_rtcd.h" allowed_files+=" vp8_rtcd.h vp9_rtcd.h vpx_version.h vpx_config.asm" -allowed_files+=" vpx_dsp_rtcd.h" +allowed_files+=" vpx_dsp_rtcd.h libvpx.ver" remove_trailing_whitespace() { perl -pi -e 's/\s\+$//' "$@" diff --git a/extensions/vp9/src/main/jni/libvpx.mk b/extensions/vp9/src/main/jni/libvpx.mk index 887de56218..3307c0769c 100644 --- a/extensions/vp9/src/main/jni/libvpx.mk +++ b/extensions/vp9/src/main/jni/libvpx.mk @@ -54,4 +54,5 @@ LOCAL_SRC_FILES := $(sort $(LOCAL_SRC_FILES)) LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/libvpx \ $(LOCAL_PATH)/libvpx/vpx +LOCAL_LDFLAGS := -Wl,--version-script=$(CONFIG_DIR)/libvpx.ver include $(BUILD_SHARED_LIBRARY) From b6a1d0a55dc036d20aa67822b44e2955d73930ce Mon Sep 17 00:00:00 2001 From: vigneshv Date: Tue, 21 Mar 2017 01:39:52 -0700 Subject: [PATCH 011/119] extensions/vp9: Update filter & remove sort in libvpx.mk Update the filter that selects %.asm.[sS] and remove the "remove duplicates" step in the android makefile. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=150728021 --- extensions/vp9/src/main/jni/libvpx.mk | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/extensions/vp9/src/main/jni/libvpx.mk b/extensions/vp9/src/main/jni/libvpx.mk index 3307c0769c..005f49fcc6 100644 --- a/extensions/vp9/src/main/jni/libvpx.mk +++ b/extensions/vp9/src/main/jni/libvpx.mk @@ -37,9 +37,7 @@ LOCAL_SRC_FILES += $(addprefix libvpx/, $(filter-out vpx_config.c, \ # include assembly files if they exist # "%.asm.[sS]" covers neon assembly and "%.asm" covers x86 assembly LOCAL_SRC_FILES += $(addprefix libvpx/, \ - $(filter %.asm.s %.asm, $(libvpx_codec_srcs))) -LOCAL_SRC_FILES += $(addprefix libvpx/, \ - $(filter %.asm.S %.asm, $(libvpx_codec_srcs))) + $(filter %.asm.s %.asm.S %.asm, $(libvpx_codec_srcs))) ifneq ($(findstring armeabi-v7a, $(TARGET_ARCH_ABI)),) # append .neon to *_neon.c and *.[sS] @@ -48,9 +46,6 @@ LOCAL_SRC_FILES := $(subst .s,.s.neon,$(LOCAL_SRC_FILES)) LOCAL_SRC_FILES := $(subst .S,.S.neon,$(LOCAL_SRC_FILES)) endif -# remove duplicates -LOCAL_SRC_FILES := $(sort $(LOCAL_SRC_FILES)) - LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/libvpx \ $(LOCAL_PATH)/libvpx/vpx From f2e5c3b4c9677d902dd967de89bbb933129e7a56 Mon Sep 17 00:00:00 2001 From: andrewlewis Date: Tue, 21 Mar 2017 03:01:16 -0700 Subject: [PATCH 012/119] Fix imports. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=150732864 --- .../google/android/exoplayer2/extractor/mp4/AtomParsersTest.java | 1 - .../com/google/android/exoplayer2/extractor/ts/Ac3Extractor.java | 1 - .../google/android/exoplayer2/metadata/emsg/EventMessage.java | 1 - .../google/android/exoplayer2/source/chunk/BaseMediaChunk.java | 1 - .../exoplayer2/source/hls/playlist/HlsMasterPlaylist.java | 1 - .../com/google/android/exoplayer2/ui/AspectRatioFrameLayout.java | 1 - 6 files changed, 6 deletions(-) diff --git a/library/core/src/androidTest/java/com/google/android/exoplayer2/extractor/mp4/AtomParsersTest.java b/library/core/src/androidTest/java/com/google/android/exoplayer2/extractor/mp4/AtomParsersTest.java index 2d32839c51..d0213337b8 100644 --- a/library/core/src/androidTest/java/com/google/android/exoplayer2/extractor/mp4/AtomParsersTest.java +++ b/library/core/src/androidTest/java/com/google/android/exoplayer2/extractor/mp4/AtomParsersTest.java @@ -17,7 +17,6 @@ package com.google.android.exoplayer2.extractor.mp4; import com.google.android.exoplayer2.util.ParsableByteArray; import com.google.android.exoplayer2.util.Util; - import junit.framework.TestCase; /** 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 e714928c20..9c9536beec 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 @@ -26,7 +26,6 @@ import com.google.android.exoplayer2.extractor.SeekMap; import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.TrackIdGenerator; import com.google.android.exoplayer2.util.ParsableByteArray; import com.google.android.exoplayer2.util.Util; - import java.io.IOException; /** diff --git a/library/core/src/main/java/com/google/android/exoplayer2/metadata/emsg/EventMessage.java b/library/core/src/main/java/com/google/android/exoplayer2/metadata/emsg/EventMessage.java index 9d6d0af60c..fbe3184c0d 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/metadata/emsg/EventMessage.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/metadata/emsg/EventMessage.java @@ -17,7 +17,6 @@ package com.google.android.exoplayer2.metadata.emsg; import android.os.Parcel; import android.os.Parcelable; - import com.google.android.exoplayer2.metadata.Metadata; import com.google.android.exoplayer2.util.Util; import java.util.Arrays; diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/BaseMediaChunk.java b/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/BaseMediaChunk.java index 7a5aeabeb6..62c07ee248 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/BaseMediaChunk.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/BaseMediaChunk.java @@ -16,7 +16,6 @@ package com.google.android.exoplayer2.source.chunk; import com.google.android.exoplayer2.Format; -import com.google.android.exoplayer2.extractor.DefaultTrackOutput; import com.google.android.exoplayer2.upstream.DataSource; import com.google.android.exoplayer2.upstream.DataSpec; diff --git a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsMasterPlaylist.java b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsMasterPlaylist.java index 5580017805..bd0a2db316 100644 --- a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsMasterPlaylist.java +++ b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsMasterPlaylist.java @@ -17,7 +17,6 @@ package com.google.android.exoplayer2.source.hls.playlist; import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.util.MimeTypes; - import java.util.Collections; import java.util.List; diff --git a/library/ui/src/main/java/com/google/android/exoplayer2/ui/AspectRatioFrameLayout.java b/library/ui/src/main/java/com/google/android/exoplayer2/ui/AspectRatioFrameLayout.java index 38294f5208..b0df16b484 100644 --- a/library/ui/src/main/java/com/google/android/exoplayer2/ui/AspectRatioFrameLayout.java +++ b/library/ui/src/main/java/com/google/android/exoplayer2/ui/AspectRatioFrameLayout.java @@ -20,7 +20,6 @@ import android.content.res.TypedArray; import android.support.annotation.IntDef; import android.util.AttributeSet; import android.widget.FrameLayout; - import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; From c32533cad8294c8f217340f796aaf226d0f58000 Mon Sep 17 00:00:00 2001 From: aquilescanta Date: Tue, 21 Mar 2017 04:52:43 -0700 Subject: [PATCH 013/119] Fixed the edit mode view for SimpleExoPlayerView This CL makes SimpleExoPlayerView show a mock representation when in the layout editor of AndroidStudio. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=150739442 --- .../exoplayer2/ui/SimpleExoPlayerView.java | 34 ++++++++++++++ .../main/res/drawable/exo_edit_mode_logo.xml | 44 +++++++++++++++++++ library/ui/src/main/res/values/constants.xml | 2 + 3 files changed, 80 insertions(+) create mode 100644 library/ui/src/main/res/drawable/exo_edit_mode_logo.xml diff --git a/library/ui/src/main/java/com/google/android/exoplayer2/ui/SimpleExoPlayerView.java b/library/ui/src/main/java/com/google/android/exoplayer2/ui/SimpleExoPlayerView.java index 08cb2536dc..2fa9c29fce 100644 --- a/library/ui/src/main/java/com/google/android/exoplayer2/ui/SimpleExoPlayerView.java +++ b/library/ui/src/main/java/com/google/android/exoplayer2/ui/SimpleExoPlayerView.java @@ -17,6 +17,7 @@ package com.google.android.exoplayer2.ui; import android.annotation.TargetApi; import android.content.Context; +import android.content.res.Resources; import android.content.res.TypedArray; import android.graphics.Bitmap; import android.graphics.BitmapFactory; @@ -45,6 +46,7 @@ import com.google.android.exoplayer2.trackselection.TrackSelectionArray; import com.google.android.exoplayer2.ui.AspectRatioFrameLayout.ResizeMode; import com.google.android.exoplayer2.ui.PlaybackControlView.SeekDispatcher; import com.google.android.exoplayer2.util.Assertions; +import com.google.android.exoplayer2.util.Util; import java.util.List; /** @@ -199,6 +201,25 @@ public final class SimpleExoPlayerView extends FrameLayout { public SimpleExoPlayerView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); + if (isInEditMode()) { + contentFrame = null; + shutterView = null; + surfaceView = null; + artworkView = null; + subtitleView = null; + controller = null; + componentListener = null; + overlayFrameLayout = null; + ImageView logo = new ImageView(context, attrs); + if (Util.SDK_INT >= 23) { + configureEditModeLogoV23(getResources(), logo); + } else { + configureEditModeLogo(getResources(), logo); + } + addView(logo); + return; + } + int playerLayoutId = R.layout.exo_simple_player_view; boolean useArtwork = true; int defaultArtworkId = 0; @@ -645,6 +666,19 @@ public final class SimpleExoPlayerView extends FrameLayout { } } + @TargetApi(23) + private static void configureEditModeLogoV23(Resources resources, ImageView logo) { + logo.setImageDrawable(resources.getDrawable(R.drawable.exo_edit_mode_logo, null)); + logo.setBackgroundColor(resources.getColor(R.color.exo_edit_mode_background_color, null)); + } + + @SuppressWarnings("deprecation") + private static void configureEditModeLogo(Resources resources, ImageView logo) { + logo.setImageDrawable(resources.getDrawable(R.drawable.exo_edit_mode_logo)); + logo.setBackgroundColor(resources.getColor(R.color.exo_edit_mode_background_color)); + } + + @SuppressWarnings("ResourceType") private static void setResizeModeRaw(AspectRatioFrameLayout aspectRatioFrame, int resizeMode) { aspectRatioFrame.setResizeMode(resizeMode); diff --git a/library/ui/src/main/res/drawable/exo_edit_mode_logo.xml b/library/ui/src/main/res/drawable/exo_edit_mode_logo.xml new file mode 100644 index 0000000000..80ff1844cd --- /dev/null +++ b/library/ui/src/main/res/drawable/exo_edit_mode_logo.xml @@ -0,0 +1,44 @@ + + + + + + + + + + + diff --git a/library/ui/src/main/res/values/constants.xml b/library/ui/src/main/res/values/constants.xml index 5c86696ea0..eb94cacadd 100644 --- a/library/ui/src/main/res/values/constants.xml +++ b/library/ui/src/main/res/values/constants.xml @@ -18,4 +18,6 @@ 71dp 52dp + #FFF4F3F0 + From 065d3dc5233f5291f56eee06d54c8ea3ccfe6f78 Mon Sep 17 00:00:00 2001 From: eguven Date: Tue, 21 Mar 2017 07:05:53 -0700 Subject: [PATCH 014/119] Make FakeDataSource able to serve multiple fake files ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=150748155 --- .../extractor/DefaultExtractorInputTest.java | 31 ++-- .../upstream/DataSourceInputStreamTest.java | 6 +- .../upstream/cache/CacheDataSourceTest.java | 9 +- .../upstream/cache/CacheDataSourceTest2.java | 4 +- .../exoplayer2/testutil/FakeDataSource.java | 163 +++++++++++++----- 5 files changed, 151 insertions(+), 62 deletions(-) diff --git a/library/core/src/androidTest/java/com/google/android/exoplayer2/extractor/DefaultExtractorInputTest.java b/library/core/src/androidTest/java/com/google/android/exoplayer2/extractor/DefaultExtractorInputTest.java index dcab0c6275..6abd116086 100644 --- a/library/core/src/androidTest/java/com/google/android/exoplayer2/extractor/DefaultExtractorInputTest.java +++ b/library/core/src/androidTest/java/com/google/android/exoplayer2/extractor/DefaultExtractorInputTest.java @@ -269,9 +269,8 @@ public class DefaultExtractorInputTest extends TestCase { public void testSkipFullyLarge() throws Exception { // Tests skipping an amount of data that's larger than any internal scratch space. int largeSkipSize = 1024 * 1024; - FakeDataSource.Builder builder = new FakeDataSource.Builder(); - builder.appendReadData(new byte[largeSkipSize]); - FakeDataSource testDataSource = builder.build(); + FakeDataSource testDataSource = new FakeDataSource(); + testDataSource.getDataSet().newDefaultData().appendReadData(new byte[largeSkipSize]); testDataSource.open(new DataSpec(Uri.parse(TEST_URI))); DefaultExtractorInput input = new DefaultExtractorInput(testDataSource, 0, C.LENGTH_UNSET); @@ -397,29 +396,29 @@ public class DefaultExtractorInputTest extends TestCase { } private static FakeDataSource buildDataSource() throws Exception { - FakeDataSource.Builder builder = new FakeDataSource.Builder(); - builder.appendReadData(Arrays.copyOfRange(TEST_DATA, 0, 3)); - builder.appendReadData(Arrays.copyOfRange(TEST_DATA, 3, 6)); - builder.appendReadData(Arrays.copyOfRange(TEST_DATA, 6, 9)); - FakeDataSource testDataSource = builder.build(); + FakeDataSource testDataSource = new FakeDataSource(); + testDataSource.getDataSet().newDefaultData() + .appendReadData(Arrays.copyOfRange(TEST_DATA, 0, 3)) + .appendReadData(Arrays.copyOfRange(TEST_DATA, 3, 6)) + .appendReadData(Arrays.copyOfRange(TEST_DATA, 6, 9)); testDataSource.open(new DataSpec(Uri.parse(TEST_URI))); return testDataSource; } private static FakeDataSource buildFailingDataSource() throws Exception { - FakeDataSource.Builder builder = new FakeDataSource.Builder(); - builder.appendReadData(Arrays.copyOfRange(TEST_DATA, 0, 6)); - builder.appendReadError(new IOException()); - builder.appendReadData(Arrays.copyOfRange(TEST_DATA, 6, 9)); - FakeDataSource testDataSource = builder.build(); + FakeDataSource testDataSource = new FakeDataSource(); + testDataSource.getDataSet().newDefaultData() + .appendReadData(Arrays.copyOfRange(TEST_DATA, 0, 6)) + .appendReadError(new IOException()) + .appendReadData(Arrays.copyOfRange(TEST_DATA, 6, 9)); testDataSource.open(new DataSpec(Uri.parse(TEST_URI))); return testDataSource; } private static FakeDataSource buildLargeDataSource() throws Exception { - FakeDataSource.Builder builder = new FakeDataSource.Builder(); - builder.appendReadData(new byte[LARGE_TEST_DATA_LENGTH]); - FakeDataSource testDataSource = builder.build(); + FakeDataSource testDataSource = new FakeDataSource(); + testDataSource.getDataSet().newDefaultData() + .appendReadData(new byte[LARGE_TEST_DATA_LENGTH]); testDataSource.open(new DataSpec(Uri.parse(TEST_URI))); return testDataSource; } diff --git a/library/core/src/androidTest/java/com/google/android/exoplayer2/upstream/DataSourceInputStreamTest.java b/library/core/src/androidTest/java/com/google/android/exoplayer2/upstream/DataSourceInputStreamTest.java index 3200e9d6a3..38797ede66 100644 --- a/library/core/src/androidTest/java/com/google/android/exoplayer2/upstream/DataSourceInputStreamTest.java +++ b/library/core/src/androidTest/java/com/google/android/exoplayer2/upstream/DataSourceInputStreamTest.java @@ -15,6 +15,7 @@ */ package com.google.android.exoplayer2.upstream; +import android.net.Uri; import android.test.MoreAsserts; import com.google.android.exoplayer2.testutil.FakeDataSource; import com.google.android.exoplayer2.testutil.TestUtil; @@ -88,12 +89,13 @@ public class DataSourceInputStreamTest extends TestCase { } private static DataSourceInputStream buildTestInputStream() { - FakeDataSource.Builder fakeDataSourceBuilder = new FakeDataSource.Builder() + FakeDataSource fakeDataSource = new FakeDataSource(); + fakeDataSource.getDataSet().newDefaultData() .appendReadData(Arrays.copyOfRange(TEST_DATA, 0, 5)) .appendReadData(Arrays.copyOfRange(TEST_DATA, 5, 10)) .appendReadData(Arrays.copyOfRange(TEST_DATA, 10, 15)) .appendReadData(Arrays.copyOfRange(TEST_DATA, 15, TEST_DATA.length)); - return new DataSourceInputStream(fakeDataSourceBuilder.build(), new DataSpec(null)); + return new DataSourceInputStream(fakeDataSource, new DataSpec(Uri.EMPTY)); } } diff --git a/library/core/src/androidTest/java/com/google/android/exoplayer2/upstream/cache/CacheDataSourceTest.java b/library/core/src/androidTest/java/com/google/android/exoplayer2/upstream/cache/CacheDataSourceTest.java index 6689d73ff1..c76e4989d8 100644 --- a/library/core/src/androidTest/java/com/google/android/exoplayer2/upstream/cache/CacheDataSourceTest.java +++ b/library/core/src/androidTest/java/com/google/android/exoplayer2/upstream/cache/CacheDataSourceTest.java @@ -20,6 +20,7 @@ import android.test.InstrumentationTestCase; import android.test.MoreAsserts; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.testutil.FakeDataSource; +import com.google.android.exoplayer2.testutil.FakeDataSource.FakeData; import com.google.android.exoplayer2.upstream.DataSpec; import com.google.android.exoplayer2.upstream.FileDataSource; import com.google.android.exoplayer2.util.Util; @@ -197,12 +198,12 @@ public class CacheDataSourceTest extends InstrumentationTestCase { private CacheDataSource createCacheDataSource(boolean setReadException, boolean simulateUnknownLength, @CacheDataSource.Flags int flags, CacheDataSink cacheWriteDataSink) { - FakeDataSource.Builder builder = new FakeDataSource.Builder(); + FakeDataSource upstream = new FakeDataSource(); + FakeData fakeData = upstream.getDataSet().newDefaultData() + .setSimulateUnknownLength(simulateUnknownLength).appendReadData(TEST_DATA); if (setReadException) { - builder.appendReadError(new IOException("Shouldn't read from upstream")); + fakeData.appendReadError(new IOException("Shouldn't read from upstream")); } - FakeDataSource upstream = - builder.setSimulateUnknownLength(simulateUnknownLength).appendReadData(TEST_DATA).build(); return new CacheDataSource(simpleCache, upstream, new FileDataSource(), cacheWriteDataSink, flags, null); } diff --git a/library/core/src/androidTest/java/com/google/android/exoplayer2/upstream/cache/CacheDataSourceTest2.java b/library/core/src/androidTest/java/com/google/android/exoplayer2/upstream/cache/CacheDataSourceTest2.java index 70a7d797c1..7e8088f3be 100644 --- a/library/core/src/androidTest/java/com/google/android/exoplayer2/upstream/cache/CacheDataSourceTest2.java +++ b/library/core/src/androidTest/java/com/google/android/exoplayer2/upstream/cache/CacheDataSourceTest2.java @@ -139,7 +139,9 @@ public class CacheDataSourceTest2 extends AndroidTestCase { } private static FakeDataSource buildFakeUpstreamSource() { - return new FakeDataSource.Builder().appendReadData(DATA).build(); + FakeDataSource fakeDataSource = new FakeDataSource(); + fakeDataSource.getDataSet().newDefaultData().appendReadData(DATA); + return fakeDataSource; } private static CacheDataSource buildCacheDataSource(Context context, DataSource upstreamSource, diff --git a/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeDataSource.java b/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeDataSource.java index 37947bb1ab..a7b849f233 100644 --- a/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeDataSource.java +++ b/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeDataSource.java @@ -23,43 +23,79 @@ import com.google.android.exoplayer2.upstream.DataSpec; import com.google.android.exoplayer2.util.Assertions; import java.io.IOException; import java.util.ArrayList; +import java.util.HashMap; /** - * A fake {@link DataSource} capable of simulating various scenarios. - *

    - * The data that will be read from the source can be constructed by calling - * {@link Builder#appendReadData(byte[])}. Calls to {@link #read(byte[], int, int)} will not span - * the boundaries between arrays passed to successive calls, and hence the boundaries control the + * A fake {@link DataSource} capable of simulating various scenarios. It uses a {@link FakeDataSet} + * instance which determines the response to data access calls. + * + *

    Multiple fake data can be defined by {@link FakeDataSet#setData(String, byte[])} and {@link + * FakeDataSet#newData(String)} methods. It's also possible to define a default data by {@link + * FakeDataSet#newDefaultData()}. + * + *

    {@link FakeDataSet#newData(String)} and {@link FakeDataSet#newDefaultData()} return a {@link + * FakeData} instance which can be used to define specific results during {@link #read(byte[], int, + * int)} calls. + * + *

    The data that will be read from the source can be constructed by calling {@link + * FakeData#appendReadData(byte[])} Calls to {@link #read(byte[], int, int)} will not span the + * boundaries between arrays passed to successive calls, and hence the boundaries control the * positions at which read requests to the source may only be partially satisfied. - *

    - * Errors can be inserted by calling {@link Builder#appendReadError(IOException)}. An inserted error - * will be thrown from the first call to {@link #read(byte[], int, int)} that attempts to read from - * the corresponding position, and from all subsequent calls to {@link #read(byte[], int, int)} + * + *

    Errors can be inserted by calling {@link FakeData#appendReadError(IOException)}. An inserted + * error will be thrown from the first call to {@link #read(byte[], int, int)} that attempts to read + * from the corresponding position, and from all subsequent calls to {@link #read(byte[], int, int)} * until the source is closed. If the source is closed and re-opened having encountered an error, * that error will not be thrown again. + * + *

    Example usage: + * + *

    + *   // Create a FakeDataSource then add default data and two FakeData
    + *   // "test_file" throws an IOException when tried to be read until closed and reopened.
    + *   FakeDataSource fakeDataSource = new FakeDataSource();
    + *   fakeDataSource.getDataSet()
    + *       .newDefaultData()
    + *         .appendReadData(defaultData)
    + *         .endData()
    + *       .setData("http:///1", data1)
    + *       .newData("test_file")
    + *         .appendReadError(new IOException())
    + *         .appendReadData(data2);
    + *    // No need to call endData at the end
    + * 
    */ public final class FakeDataSource implements DataSource { - private final ArrayList segments; + private final FakeDataSet fakeDataSet; private final ArrayList openedDataSpecs; - private final boolean simulateUnknownLength; - private final long totalLength; - private Uri uri; private boolean opened; + private FakeData fakeData; private int currentSegmentIndex; private long bytesRemaining; - private FakeDataSource(boolean simulateUnknownLength, ArrayList segments) { - this.simulateUnknownLength = simulateUnknownLength; - this.segments = segments; - long totalLength = 0; - for (Segment segment : segments) { - totalLength += segment.length; - } - this.totalLength = totalLength; - openedDataSpecs = new ArrayList<>(); + public static Factory newFactory(final FakeDataSet fakeDataSet) { + return new Factory() { + @Override + public DataSource createDataSource() { + return new FakeDataSource(fakeDataSet); + } + }; + } + + public FakeDataSource() { + this(new FakeDataSet()); + } + + public FakeDataSource(FakeDataSet fakeDataSet) { + this.fakeDataSet = fakeDataSet; + this.openedDataSpecs = new ArrayList<>(); + } + + public FakeDataSet getDataSet() { + return fakeDataSet; } @Override @@ -69,6 +105,21 @@ public final class FakeDataSource implements DataSource { opened = true; uri = dataSpec.uri; openedDataSpecs.add(dataSpec); + + fakeData = fakeDataSet.getData(uri.toString()); + if (fakeData == null) { + throw new IOException("Data not found: " + dataSpec.uri); + } + + long totalLength = 0; + for (Segment segment : fakeData.segments) { + totalLength += segment.length; + } + + if (totalLength == 0) { + throw new IOException("Data is empty: " + dataSpec.uri); + } + // If the source knows that the request is unsatisfiable then fail. if (dataSpec.position >= totalLength || (dataSpec.length != C.LENGTH_UNSET && (dataSpec.position + dataSpec.length > totalLength))) { @@ -78,7 +129,7 @@ public final class FakeDataSource implements DataSource { boolean findingCurrentSegmentIndex = true; currentSegmentIndex = 0; int scannedLength = 0; - for (Segment segment : segments) { + for (Segment segment : fakeData.segments) { segment.bytesRead = (int) Math.min(Math.max(0, dataSpec.position - scannedLength), segment.length); scannedLength += segment.length; @@ -91,7 +142,7 @@ public final class FakeDataSource implements DataSource { // Configure bytesRemaining, and return. if (dataSpec.length == C.LENGTH_UNSET) { bytesRemaining = totalLength - dataSpec.position; - return simulateUnknownLength ? C.LENGTH_UNSET : bytesRemaining; + return fakeData.simulateUnknownLength ? C.LENGTH_UNSET : bytesRemaining; } else { bytesRemaining = dataSpec.length; return bytesRemaining; @@ -102,10 +153,10 @@ public final class FakeDataSource implements DataSource { public int read(byte[] buffer, int offset, int readLength) throws IOException { Assertions.checkState(opened); while (true) { - if (currentSegmentIndex == segments.size() || bytesRemaining == 0) { + if (currentSegmentIndex == fakeData.segments.size() || bytesRemaining == 0) { return C.RESULT_END_OF_INPUT; } - Segment current = segments.get(currentSegmentIndex); + Segment current = fakeData.segments.get(currentSegmentIndex); if (current.isErrorSegment()) { if (!current.exceptionCleared) { current.exceptionThrown = true; @@ -140,12 +191,13 @@ public final class FakeDataSource implements DataSource { Assertions.checkState(opened); opened = false; uri = null; - if (currentSegmentIndex < segments.size()) { - Segment current = segments.get(currentSegmentIndex); + if (currentSegmentIndex < fakeData.segments.size()) { + Segment current = fakeData.segments.get(currentSegmentIndex); if (current.isErrorSegment() && current.exceptionThrown) { current.exceptionCleared = true; } } + fakeData = null; } /** @@ -181,16 +233,21 @@ public final class FakeDataSource implements DataSource { } - /** - * Builder of {@link FakeDataSource} instances. - */ - public static final class Builder { + /** Container of fake data to be served by a {@link FakeDataSource}. */ + public static final class FakeData { private final ArrayList segments; + private final FakeDataSet dataSet; private boolean simulateUnknownLength; - public Builder() { - segments = new ArrayList<>(); + public FakeData(FakeDataSet dataSet) { + this.segments = new ArrayList<>(); + this.dataSet = dataSet; + } + + /** Returns the {@link FakeDataSet} this FakeData belongs to. */ + public FakeDataSet endData() { + return dataSet; } /** @@ -199,7 +256,7 @@ public final class FakeDataSource implements DataSource { * the {@link DataSpec#length} of the argument, including the case where the length is equal to * {@link C#LENGTH_UNSET}. */ - public Builder setSimulateUnknownLength(boolean simulateUnknownLength) { + public FakeData setSimulateUnknownLength(boolean simulateUnknownLength) { this.simulateUnknownLength = simulateUnknownLength; return this; } @@ -207,7 +264,7 @@ public final class FakeDataSource implements DataSource { /** * Appends to the underlying data. */ - public Builder appendReadData(byte[] data) { + public FakeData appendReadData(byte[] data) { Assertions.checkState(data != null && data.length > 0); segments.add(new Segment(data, null)); return this; @@ -216,13 +273,41 @@ public final class FakeDataSource implements DataSource { /** * Appends an error in the underlying data. */ - public Builder appendReadError(IOException exception) { + public FakeData appendReadError(IOException exception) { segments.add(new Segment(null, exception)); return this; } + } - public FakeDataSource build() { - return new FakeDataSource(simulateUnknownLength, segments); + /** A set of {@link FakeData} instances. */ + public static final class FakeDataSet { + + private FakeData defaultData; + private final HashMap dataMap; + + public FakeDataSet() { + dataMap = new HashMap<>(); + } + + public FakeData newDefaultData() { + defaultData = new FakeData(this); + return defaultData; + } + + public FakeData newData(String uri) { + FakeData data = new FakeData(this); + dataMap.put(uri, data); + return data; + } + + public FakeDataSet setData(String uri, byte[] data) { + newData(uri).appendReadData(data); + return this; + } + + public FakeData getData(String uri) { + FakeData data = dataMap.get(uri); + return data != null ? data : defaultData; } } From b1a2ae1856d3637b3a852258e1828cc91eb808b6 Mon Sep 17 00:00:00 2001 From: olly Date: Tue, 21 Mar 2017 08:10:42 -0700 Subject: [PATCH 015/119] Remove DRM->DASH dependency in prep for DASH module split Also renamed releaseResources->release to be consistent with the rest of the library, and added some synchronization to ensure correct usage. Issue: #2139 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=150753414 --- .../drm/OfflineLicenseHelperTest.java | 111 +++--------------- .../exoplayer2/source/dash/DashUtilTest.java | 91 ++++++++++++++ .../exoplayer2/drm/OfflineLicenseHelper.java | 98 ++++------------ .../android/exoplayer2/drm/WidevineUtil.java | 2 +- .../exoplayer2/source/dash/DashUtil.java | 46 +++++++- .../gts/DashWidevineOfflineTest.java | 17 ++- 6 files changed, 190 insertions(+), 175 deletions(-) create mode 100644 library/core/src/androidTest/java/com/google/android/exoplayer2/source/dash/DashUtilTest.java diff --git a/library/core/src/androidTest/java/com/google/android/exoplayer2/drm/OfflineLicenseHelperTest.java b/library/core/src/androidTest/java/com/google/android/exoplayer2/drm/OfflineLicenseHelperTest.java index 985e93404a..afd690762b 100644 --- a/library/core/src/androidTest/java/com/google/android/exoplayer2/drm/OfflineLicenseHelperTest.java +++ b/library/core/src/androidTest/java/com/google/android/exoplayer2/drm/OfflineLicenseHelperTest.java @@ -23,17 +23,9 @@ import android.test.InstrumentationTestCase; import android.test.MoreAsserts; import android.util.Pair; import com.google.android.exoplayer2.C; -import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.drm.DrmInitData.SchemeData; -import com.google.android.exoplayer2.source.dash.manifest.AdaptationSet; -import com.google.android.exoplayer2.source.dash.manifest.DashManifest; -import com.google.android.exoplayer2.source.dash.manifest.Period; -import com.google.android.exoplayer2.source.dash.manifest.Representation; -import com.google.android.exoplayer2.source.dash.manifest.SegmentBase.SingleSegmentBase; import com.google.android.exoplayer2.testutil.TestUtil; import com.google.android.exoplayer2.upstream.HttpDataSource; -import com.google.android.exoplayer2.util.MimeTypes; -import java.util.Arrays; import java.util.HashMap; import org.mockito.Mock; @@ -50,89 +42,57 @@ public class OfflineLicenseHelperTest extends InstrumentationTestCase { @Override protected void setUp() throws Exception { TestUtil.setUpMockito(this); - when(mediaDrm.openSession()).thenReturn(new byte[] {1, 2, 3}); - offlineLicenseHelper = new OfflineLicenseHelper<>(mediaDrm, mediaDrmCallback, null); } @Override protected void tearDown() throws Exception { - offlineLicenseHelper.releaseResources(); + offlineLicenseHelper.release(); + offlineLicenseHelper = null; } public void testDownloadRenewReleaseKey() throws Exception { - DashManifest manifest = newDashManifestWithAllElements(); setStubLicenseAndPlaybackDurationValues(1000, 200); byte[] keySetId = {2, 5, 8}; setStubKeySetId(keySetId); - byte[] offlineLicenseKeySetId = offlineLicenseHelper.download(httpDataSource, manifest); + byte[] offlineLicenseKeySetId = offlineLicenseHelper.downloadLicense(newDrmInitData()); assertOfflineLicenseKeySetIdEqual(keySetId, offlineLicenseKeySetId); byte[] keySetId2 = {6, 7, 0, 1, 4}; setStubKeySetId(keySetId2); - byte[] offlineLicenseKeySetId2 = offlineLicenseHelper.renew(offlineLicenseKeySetId); + byte[] offlineLicenseKeySetId2 = offlineLicenseHelper.renewLicense(offlineLicenseKeySetId); assertOfflineLicenseKeySetIdEqual(keySetId2, offlineLicenseKeySetId2); - offlineLicenseHelper.release(offlineLicenseKeySetId2); + offlineLicenseHelper.releaseLicense(offlineLicenseKeySetId2); } - public void testDownloadFailsIfThereIsNoInitData() throws Exception { - setDefaultStubValues(); - DashManifest manifest = - newDashManifest(newPeriods(newAdaptationSets(newRepresentations(null /*no init data*/)))); - - byte[] offlineLicenseKeySetId = offlineLicenseHelper.download(httpDataSource, manifest); - - assertNull(offlineLicenseKeySetId); + public void testDownloadLicenseFailsIfNullInitData() throws Exception { + try { + offlineLicenseHelper.downloadLicense(null); + fail(); + } catch (IllegalArgumentException e) { + // Expected. + } } - public void testDownloadFailsIfThereIsNoRepresentation() throws Exception { - setDefaultStubValues(); - DashManifest manifest = newDashManifest(newPeriods(newAdaptationSets(/*no representation*/))); - - byte[] offlineLicenseKeySetId = offlineLicenseHelper.download(httpDataSource, manifest); - - assertNull(offlineLicenseKeySetId); - } - - public void testDownloadFailsIfThereIsNoAdaptationSet() throws Exception { - setDefaultStubValues(); - DashManifest manifest = newDashManifest(newPeriods(/*no adaptation set*/)); - - byte[] offlineLicenseKeySetId = offlineLicenseHelper.download(httpDataSource, manifest); - - assertNull(offlineLicenseKeySetId); - } - - public void testDownloadFailsIfThereIsNoPeriod() throws Exception { - setDefaultStubValues(); - DashManifest manifest = newDashManifest(/*no periods*/); - - byte[] offlineLicenseKeySetId = offlineLicenseHelper.download(httpDataSource, manifest); - - assertNull(offlineLicenseKeySetId); - } - - public void testDownloadFailsIfNoKeySetIdIsReturned() throws Exception { + public void testDownloadLicenseFailsIfNoKeySetIdIsReturned() throws Exception { setStubLicenseAndPlaybackDurationValues(1000, 200); - DashManifest manifest = newDashManifestWithAllElements(); - byte[] offlineLicenseKeySetId = offlineLicenseHelper.download(httpDataSource, manifest); + byte[] offlineLicenseKeySetId = offlineLicenseHelper.downloadLicense(newDrmInitData()); assertNull(offlineLicenseKeySetId); } - public void testDownloadDoesNotFailIfDurationNotAvailable() throws Exception { + public void testDownloadLicenseDoesNotFailIfDurationNotAvailable() throws Exception { setDefaultStubKeySetId(); - DashManifest manifest = newDashManifestWithAllElements(); - byte[] offlineLicenseKeySetId = offlineLicenseHelper.download(httpDataSource, manifest); + byte[] offlineLicenseKeySetId = offlineLicenseHelper.downloadLicense(newDrmInitData()); assertNotNull(offlineLicenseKeySetId); } @@ -142,9 +102,8 @@ public class OfflineLicenseHelperTest extends InstrumentationTestCase { int playbackDuration = 200; setStubLicenseAndPlaybackDurationValues(licenseDuration, playbackDuration); setDefaultStubKeySetId(); - DashManifest manifest = newDashManifestWithAllElements(); - byte[] offlineLicenseKeySetId = offlineLicenseHelper.download(httpDataSource, manifest); + byte[] offlineLicenseKeySetId = offlineLicenseHelper.downloadLicense(newDrmInitData()); Pair licenseDurationRemainingSec = offlineLicenseHelper .getLicenseDurationRemainingSec(offlineLicenseKeySetId); @@ -158,9 +117,8 @@ public class OfflineLicenseHelperTest extends InstrumentationTestCase { int playbackDuration = 0; setStubLicenseAndPlaybackDurationValues(licenseDuration, playbackDuration); setDefaultStubKeySetId(); - DashManifest manifest = newDashManifestWithAllElements(); - byte[] offlineLicenseKeySetId = offlineLicenseHelper.download(httpDataSource, manifest); + byte[] offlineLicenseKeySetId = offlineLicenseHelper.downloadLicense(newDrmInitData()); Pair licenseDurationRemainingSec = offlineLicenseHelper .getLicenseDurationRemainingSec(offlineLicenseKeySetId); @@ -169,12 +127,6 @@ public class OfflineLicenseHelperTest extends InstrumentationTestCase { assertEquals(playbackDuration, (long) licenseDurationRemainingSec.second); } - private void setDefaultStubValues() - throws android.media.NotProvisionedException, android.media.DeniedByServerException { - setDefaultStubKeySetId(); - setStubLicenseAndPlaybackDurationValues(1000, 200); - } - private void setDefaultStubKeySetId() throws android.media.NotProvisionedException, android.media.DeniedByServerException { setStubKeySetId(new byte[] {2, 5, 8}); @@ -201,34 +153,9 @@ public class OfflineLicenseHelperTest extends InstrumentationTestCase { when(mediaDrm.queryKeyStatus(any(byte[].class))).thenReturn(keyStatus); } - private static DashManifest newDashManifestWithAllElements() { - return newDashManifest(newPeriods(newAdaptationSets(newRepresentations(newDrmInitData())))); - } - - private static DashManifest newDashManifest(Period... periods) { - return new DashManifest(0, 0, 0, false, 0, 0, 0, null, null, Arrays.asList(periods)); - } - - private static Period newPeriods(AdaptationSet... adaptationSets) { - return new Period("", 0, Arrays.asList(adaptationSets)); - } - - private static AdaptationSet newAdaptationSets(Representation... representations) { - return new AdaptationSet(0, C.TRACK_TYPE_VIDEO, Arrays.asList(representations), null); - } - - private static Representation newRepresentations(DrmInitData drmInitData) { - Format format = Format.createVideoContainerFormat("id", MimeTypes.VIDEO_MP4, - MimeTypes.VIDEO_H264, "", Format.NO_VALUE, 1024, 768, Format.NO_VALUE, null, 0); - if (drmInitData != null) { - format = format.copyWithDrmInitData(drmInitData); - } - return Representation.newInstance("", 0, format, "", new SingleSegmentBase()); - } - private static DrmInitData newDrmInitData() { return new DrmInitData(new SchemeData(C.WIDEVINE_UUID, "mimeType", - new byte[]{1, 4, 7, 0, 3, 6})); + new byte[] {1, 4, 7, 0, 3, 6})); } } diff --git a/library/core/src/androidTest/java/com/google/android/exoplayer2/source/dash/DashUtilTest.java b/library/core/src/androidTest/java/com/google/android/exoplayer2/source/dash/DashUtilTest.java new file mode 100644 index 0000000000..3ee76f43bb --- /dev/null +++ b/library/core/src/androidTest/java/com/google/android/exoplayer2/source/dash/DashUtilTest.java @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.exoplayer2.source.dash; + +import com.google.android.exoplayer2.C; +import com.google.android.exoplayer2.Format; +import com.google.android.exoplayer2.drm.DrmInitData; +import com.google.android.exoplayer2.drm.DrmInitData.SchemeData; +import com.google.android.exoplayer2.source.dash.manifest.AdaptationSet; +import com.google.android.exoplayer2.source.dash.manifest.Period; +import com.google.android.exoplayer2.source.dash.manifest.Representation; +import com.google.android.exoplayer2.source.dash.manifest.SegmentBase.SingleSegmentBase; +import com.google.android.exoplayer2.testutil.FakeDataSource; +import com.google.android.exoplayer2.upstream.DataSource; +import com.google.android.exoplayer2.util.MimeTypes; +import java.io.IOException; +import java.util.Arrays; +import junit.framework.TestCase; + +/** + * Unit tests for {@link DashUtil}. + */ +public final class DashUtilTest extends TestCase { + + public void testLoadDrmInitDataFromManifest() throws Exception { + Period period = newPeriod(newAdaptationSets(newRepresentations(newDrmInitData()))); + DrmInitData drmInitData = DashUtil.loadDrmInitData(newDataSource(), period); + assertEquals(newDrmInitData(), drmInitData); + } + + public void testLoadDrmInitDataMissing() throws Exception { + Period period = newPeriod(newAdaptationSets(newRepresentations(null /* no init data */))); + DrmInitData drmInitData = DashUtil.loadDrmInitData(newDataSource(), period); + assertNull(drmInitData); + } + + public void testLoadDrmInitDataNoRepresentations() throws Exception { + Period period = newPeriod(newAdaptationSets(/* no representation */)); + DrmInitData drmInitData = DashUtil.loadDrmInitData(newDataSource(), period); + assertNull(drmInitData); + } + + public void testLoadDrmInitDataNoAdaptationSets() throws Exception { + Period period = newPeriod(/* no adaptation set */); + DrmInitData drmInitData = DashUtil.loadDrmInitData(newDataSource(), period); + assertNull(drmInitData); + } + + private static Period newPeriod(AdaptationSet... adaptationSets) { + return new Period("", 0, Arrays.asList(adaptationSets)); + } + + private static AdaptationSet newAdaptationSets(Representation... representations) { + return new AdaptationSet(0, C.TRACK_TYPE_VIDEO, Arrays.asList(representations), null); + } + + private static Representation newRepresentations(DrmInitData drmInitData) { + Format format = Format.createVideoContainerFormat("id", MimeTypes.VIDEO_MP4, + MimeTypes.VIDEO_H264, "", Format.NO_VALUE, 1024, 768, Format.NO_VALUE, null, 0); + if (drmInitData != null) { + format = format.copyWithDrmInitData(drmInitData); + } + return Representation.newInstance("", 0, format, "", new SingleSegmentBase()); + } + + private static DrmInitData newDrmInitData() { + return new DrmInitData(new SchemeData(C.WIDEVINE_UUID, "mimeType", + new byte[]{1, 4, 7, 0, 3, 6})); + } + + private static DataSource newDataSource() { + // TODO: Use DummyDataSource when available. + FakeDataSource fakeDataSource = new FakeDataSource(); + fakeDataSource.getDataSet().newDefaultData().appendReadError(new IOException("Unexpected")); + return fakeDataSource; + } + +} diff --git a/library/core/src/main/java/com/google/android/exoplayer2/drm/OfflineLicenseHelper.java b/library/core/src/main/java/com/google/android/exoplayer2/drm/OfflineLicenseHelper.java index ad44574af9..93f3b396c8 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/drm/OfflineLicenseHelper.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/drm/OfflineLicenseHelper.java @@ -22,15 +22,9 @@ import android.os.Handler; import android.os.HandlerThread; import android.util.Pair; import com.google.android.exoplayer2.C; -import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.drm.DefaultDrmSessionManager.EventListener; import com.google.android.exoplayer2.drm.DefaultDrmSessionManager.Mode; import com.google.android.exoplayer2.drm.DrmSession.DrmSessionException; -import com.google.android.exoplayer2.source.dash.DashUtil; -import com.google.android.exoplayer2.source.dash.manifest.AdaptationSet; -import com.google.android.exoplayer2.source.dash.manifest.DashManifest; -import com.google.android.exoplayer2.source.dash.manifest.Period; -import com.google.android.exoplayer2.source.dash.manifest.Representation; import com.google.android.exoplayer2.upstream.HttpDataSource; import com.google.android.exoplayer2.upstream.HttpDataSource.Factory; import com.google.android.exoplayer2.util.Assertions; @@ -38,8 +32,7 @@ import java.io.IOException; import java.util.HashMap; /** - * Helper class to download, renew and release offline licenses. It utilizes {@link - * DefaultDrmSessionManager}. + * Helper class to download, renew and release offline licenses. */ public final class OfflineLicenseHelper { @@ -48,8 +41,8 @@ public final class OfflineLicenseHelper { private final HandlerThread handlerThread; /** - * Instantiates a new instance which uses Widevine CDM. Call {@link #releaseResources()} when - * you're done with the helper instance. + * Instantiates a new instance which uses Widevine CDM. Call {@link #release()} when the instance + * is no longer required. * * @param licenseUrl The default license URL. * @param httpDataSourceFactory A factory from which to obtain {@link HttpDataSource} instances. @@ -64,8 +57,8 @@ public final class OfflineLicenseHelper { } /** - * Instantiates a new instance which uses Widevine CDM. Call {@link #releaseResources()} when - * you're done with the helper instance. + * Instantiates a new instance which uses Widevine CDM. Call {@link #release()} when the instance + * is no longer required. * * @param callback Performs key and provisioning requests. * @param optionalKeyRequestParameters An optional map of parameters to pass as the last argument @@ -84,7 +77,7 @@ public final class OfflineLicenseHelper { } /** - * Constructs an instance. Call {@link #releaseResources()} when you're done with it. + * Constructs an instance. Call {@link #release()} when the instance is no longer required. * * @param mediaDrm An underlying {@link ExoMediaDrm} for use by the manager. * @param callback Performs key and provisioning requests. @@ -97,7 +90,6 @@ public final class OfflineLicenseHelper { HashMap optionalKeyRequestParameters) { handlerThread = new HandlerThread("OfflineLicenseHelper"); handlerThread.start(); - conditionVariable = new ConditionVariable(); EventListener eventListener = new EventListener() { @Override @@ -124,67 +116,23 @@ public final class OfflineLicenseHelper { optionalKeyRequestParameters, new Handler(handlerThread.getLooper()), eventListener); } - /** Releases the used resources. */ - public void releaseResources() { + /** Releases the helper. Should be called when the helper is no longer required. */ + public void release() { handlerThread.quit(); } /** * Downloads an offline license. * - * @param dataSource The {@link HttpDataSource} to be used for download. - * @param manifestUriString The URI of the manifest to be read. - * @return The downloaded offline license key set id. + * @param drmInitData The {@link DrmInitData} for the content whose license is to be downloaded. + * @return The key set id for the downloaded license. * @throws IOException If an error occurs reading data from the stream. * @throws InterruptedException If the thread has been interrupted. - * @throws DrmSessionException Thrown when there is an error during DRM session. + * @throws DrmSessionException Thrown when a DRM session error occurs. */ - public byte[] download(HttpDataSource dataSource, String manifestUriString) - throws IOException, InterruptedException, DrmSessionException { - return download(dataSource, DashUtil.loadManifest(dataSource, manifestUriString)); - } - - /** - * Downloads an offline license. - * - * @param dataSource The {@link HttpDataSource} to be used for download. - * @param dashManifest The {@link DashManifest} of the DASH content. - * @return The downloaded offline license key set id. - * @throws IOException If an error occurs reading data from the stream. - * @throws InterruptedException If the thread has been interrupted. - * @throws DrmSessionException Thrown when there is an error during DRM session. - */ - public byte[] download(HttpDataSource dataSource, DashManifest dashManifest) - throws IOException, InterruptedException, DrmSessionException { - // Get DrmInitData - // Prefer drmInitData obtained from the manifest over drmInitData obtained from the stream, - // as per DASH IF Interoperability Recommendations V3.0, 7.5.3. - if (dashManifest.getPeriodCount() < 1) { - return null; - } - Period period = dashManifest.getPeriod(0); - int adaptationSetIndex = period.getAdaptationSetIndex(C.TRACK_TYPE_VIDEO); - if (adaptationSetIndex == C.INDEX_UNSET) { - adaptationSetIndex = period.getAdaptationSetIndex(C.TRACK_TYPE_AUDIO); - if (adaptationSetIndex == C.INDEX_UNSET) { - return null; - } - } - AdaptationSet adaptationSet = period.adaptationSets.get(adaptationSetIndex); - if (adaptationSet.representations.isEmpty()) { - return null; - } - Representation representation = adaptationSet.representations.get(0); - DrmInitData drmInitData = representation.format.drmInitData; - if (drmInitData == null) { - Format sampleFormat = DashUtil.loadSampleFormat(dataSource, representation); - if (sampleFormat != null) { - drmInitData = sampleFormat.drmInitData; - } - if (drmInitData == null) { - return null; - } - } + public synchronized byte[] downloadLicense(DrmInitData drmInitData) throws IOException, + InterruptedException, DrmSessionException { + Assertions.checkArgument(drmInitData != null); blockingKeyRequest(DefaultDrmSessionManager.MODE_DOWNLOAD, null, drmInitData); return drmSessionManager.getOfflineLicenseKeySetId(); } @@ -193,10 +141,11 @@ public final class OfflineLicenseHelper { * Renews an offline license. * * @param offlineLicenseKeySetId The key set id of the license to be renewed. - * @return Renewed offline license key set id. - * @throws DrmSessionException Thrown when there is an error during DRM session. + * @return The renewed offline license key set id. + * @throws DrmSessionException Thrown when a DRM session error occurs. */ - public byte[] renew(byte[] offlineLicenseKeySetId) throws DrmSessionException { + public synchronized byte[] renewLicense(byte[] offlineLicenseKeySetId) + throws DrmSessionException { Assertions.checkNotNull(offlineLicenseKeySetId); blockingKeyRequest(DefaultDrmSessionManager.MODE_DOWNLOAD, offlineLicenseKeySetId, null); return drmSessionManager.getOfflineLicenseKeySetId(); @@ -206,19 +155,22 @@ public final class OfflineLicenseHelper { * Releases an offline license. * * @param offlineLicenseKeySetId The key set id of the license to be released. - * @throws DrmSessionException Thrown when there is an error during DRM session. + * @throws DrmSessionException Thrown when a DRM session error occurs. */ - public void release(byte[] offlineLicenseKeySetId) throws DrmSessionException { + public synchronized void releaseLicense(byte[] offlineLicenseKeySetId) + throws DrmSessionException { Assertions.checkNotNull(offlineLicenseKeySetId); blockingKeyRequest(DefaultDrmSessionManager.MODE_RELEASE, offlineLicenseKeySetId, null); } /** - * Returns license and playback durations remaining in seconds of the given offline license. + * Returns the remaining license and playback durations in seconds, for an offline license. * * @param offlineLicenseKeySetId The key set id of the license. + * @return The remaining license and playback durations, in seconds. + * @throws DrmSessionException Thrown when a DRM session error occurs. */ - public Pair getLicenseDurationRemainingSec(byte[] offlineLicenseKeySetId) + public synchronized Pair getLicenseDurationRemainingSec(byte[] offlineLicenseKeySetId) throws DrmSessionException { Assertions.checkNotNull(offlineLicenseKeySetId); DrmSession session = openBlockingKeyRequest(DefaultDrmSessionManager.MODE_QUERY, diff --git a/library/core/src/main/java/com/google/android/exoplayer2/drm/WidevineUtil.java b/library/core/src/main/java/com/google/android/exoplayer2/drm/WidevineUtil.java index fc80cfb6fb..e5d014f102 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/drm/WidevineUtil.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/drm/WidevineUtil.java @@ -38,7 +38,7 @@ public final class WidevineUtil { * @throws IllegalStateException If called when a session isn't opened. * @param drmSession */ - public static Pair getLicenseDurationRemainingSec(DrmSession drmSession) { + public static Pair getLicenseDurationRemainingSec(DrmSession drmSession) { Map keyStatus = drmSession.queryKeyStatus(); return new Pair<>( getDurationRemainingSec(keyStatus, PROPERTY_LICENSE_DURATION_REMAINING), diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/dash/DashUtil.java b/library/core/src/main/java/com/google/android/exoplayer2/source/dash/DashUtil.java index 8fca21b2e0..0b8750e20a 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/dash/DashUtil.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/dash/DashUtil.java @@ -18,6 +18,7 @@ package com.google.android.exoplayer2.source.dash; import android.net.Uri; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.Format; +import com.google.android.exoplayer2.drm.DrmInitData; import com.google.android.exoplayer2.extractor.ChunkIndex; import com.google.android.exoplayer2.extractor.Extractor; import com.google.android.exoplayer2.extractor.mkv.MatroskaExtractor; @@ -26,6 +27,7 @@ import com.google.android.exoplayer2.source.chunk.ChunkExtractorWrapper; import com.google.android.exoplayer2.source.chunk.InitializationChunk; import com.google.android.exoplayer2.source.dash.manifest.DashManifest; import com.google.android.exoplayer2.source.dash.manifest.DashManifestParser; +import com.google.android.exoplayer2.source.dash.manifest.Period; import com.google.android.exoplayer2.source.dash.manifest.RangedUri; import com.google.android.exoplayer2.source.dash.manifest.Representation; import com.google.android.exoplayer2.upstream.DataSource; @@ -34,6 +36,7 @@ import com.google.android.exoplayer2.upstream.DataSpec; import com.google.android.exoplayer2.upstream.HttpDataSource; import com.google.android.exoplayer2.util.MimeTypes; import java.io.IOException; +import java.util.List; /** * Utility methods for DASH streams. @@ -46,8 +49,7 @@ public final class DashUtil { * @param dataSource The {@link HttpDataSource} from which the manifest should be read. * @param manifestUriString The URI of the manifest to be read. * @return An instance of {@link DashManifest}. - * @throws IOException If an error occurs reading data from the stream. - * @see DashManifestParser + * @throws IOException Thrown when there is an error while loading. */ public static DashManifest loadManifest(DataSource dataSource, String manifestUriString) throws IOException { @@ -63,8 +65,35 @@ public final class DashUtil { } /** - * Loads initialization data for the {@code representation} and returns the sample {@link - * Format}. + * Loads {@link DrmInitData} for a given period in a DASH manifest. + * + * @param dataSource The {@link HttpDataSource} from which data should be loaded. + * @param period The {@link Period}. + * @return The loaded {@link DrmInitData}, or null if none is defined. + * @throws IOException Thrown when there is an error while loading. + * @throws InterruptedException Thrown if the thread was interrupted. + */ + public static DrmInitData loadDrmInitData(DataSource dataSource, Period period) + throws IOException, InterruptedException { + Representation representation = getFirstRepresentation(period, C.TRACK_TYPE_VIDEO); + if (representation == null) { + representation = getFirstRepresentation(period, C.TRACK_TYPE_AUDIO); + if (representation == null) { + return null; + } + } + DrmInitData drmInitData = representation.format.drmInitData; + if (drmInitData != null) { + // Prefer drmInitData obtained from the manifest over drmInitData obtained from the stream, + // as per DASH IF Interoperability Recommendations V3.0, 7.5.3. + return drmInitData; + } + Format sampleFormat = DashUtil.loadSampleFormat(dataSource, representation); + return sampleFormat == null ? null : sampleFormat.drmInitData; + } + + /** + * Loads initialization data for the {@code representation} and returns the sample {@link Format}. * * @param dataSource The source from which the data should be loaded. * @param representation The representation which initialization chunk belongs to. @@ -155,6 +184,15 @@ public final class DashUtil { return new ChunkExtractorWrapper(extractor, format); } + private static Representation getFirstRepresentation(Period period, int type) { + int index = period.getAdaptationSetIndex(type); + if (index == C.INDEX_UNSET) { + return null; + } + List representations = period.adaptationSets.get(index).representations; + return representations.isEmpty() ? null : representations.get(0); + } + private DashUtil() {} } diff --git a/playbacktests/src/androidTest/java/com/google/android/exoplayer2/playbacktests/gts/DashWidevineOfflineTest.java b/playbacktests/src/androidTest/java/com/google/android/exoplayer2/playbacktests/gts/DashWidevineOfflineTest.java index 99a6f3bef5..44f25b49d9 100644 --- a/playbacktests/src/androidTest/java/com/google/android/exoplayer2/playbacktests/gts/DashWidevineOfflineTest.java +++ b/playbacktests/src/androidTest/java/com/google/android/exoplayer2/playbacktests/gts/DashWidevineOfflineTest.java @@ -18,11 +18,15 @@ package com.google.android.exoplayer2.playbacktests.gts; import android.media.MediaDrm.MediaDrmStateException; import android.test.ActivityInstrumentationTestCase2; import android.util.Pair; +import com.google.android.exoplayer2.drm.DrmInitData; import com.google.android.exoplayer2.drm.DrmSession.DrmSessionException; import com.google.android.exoplayer2.drm.FrameworkMediaCrypto; import com.google.android.exoplayer2.drm.OfflineLicenseHelper; import com.google.android.exoplayer2.playbacktests.util.ActionSchedule; import com.google.android.exoplayer2.playbacktests.util.HostActivity; +import com.google.android.exoplayer2.source.dash.DashUtil; +import com.google.android.exoplayer2.source.dash.manifest.DashManifest; +import com.google.android.exoplayer2.upstream.DataSource; import com.google.android.exoplayer2.upstream.DefaultHttpDataSourceFactory; import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.Util; @@ -72,7 +76,7 @@ public final class DashWidevineOfflineTest extends ActivityInstrumentationTestCa releaseLicense(); } if (offlineLicenseHelper != null) { - offlineLicenseHelper.releaseResources(); + offlineLicenseHelper.release(); } offlineLicenseHelper = null; httpDataSourceFactory = null; @@ -89,7 +93,7 @@ public final class DashWidevineOfflineTest extends ActivityInstrumentationTestCa testRunner.run(); // Renew license after playback should still work - offlineLicenseKeySetId = offlineLicenseHelper.renew(offlineLicenseKeySetId); + offlineLicenseKeySetId = offlineLicenseHelper.renewLicense(offlineLicenseKeySetId); Assert.assertNotNull(offlineLicenseKeySetId); } @@ -164,15 +168,18 @@ public final class DashWidevineOfflineTest extends ActivityInstrumentationTestCa } private void downloadLicense() throws InterruptedException, DrmSessionException, IOException { - offlineLicenseKeySetId = offlineLicenseHelper.download( - httpDataSourceFactory.createDataSource(), DashTestData.WIDEVINE_H264_MANIFEST); + DataSource dataSource = httpDataSourceFactory.createDataSource(); + DashManifest dashManifest = DashUtil.loadManifest(dataSource, + DashTestData.WIDEVINE_H264_MANIFEST); + DrmInitData drmInitData = DashUtil.loadDrmInitData(dataSource, dashManifest.getPeriod(0)); + offlineLicenseKeySetId = offlineLicenseHelper.downloadLicense(drmInitData); Assert.assertNotNull(offlineLicenseKeySetId); Assert.assertTrue(offlineLicenseKeySetId.length > 0); testRunner.setOfflineLicenseKeySetId(offlineLicenseKeySetId); } private void releaseLicense() throws DrmSessionException { - offlineLicenseHelper.release(offlineLicenseKeySetId); + offlineLicenseHelper.releaseLicense(offlineLicenseKeySetId); offlineLicenseKeySetId = null; } From 7ce8125194bffbc177008bdca2bfe2bc264961b6 Mon Sep 17 00:00:00 2001 From: olly Date: Tue, 21 Mar 2017 09:34:35 -0700 Subject: [PATCH 016/119] Split DASH into a separate module Issue: #2139 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=150762237 --- library/all/build.gradle | 1 + library/dash/build.gradle | 44 +++++++++++++++++++ .../dash/src/androidTest/AndroidManifest.xml | 34 ++++++++++++++ .../src/androidTest/assets}/sample_mpd_1 | 0 .../assets}/sample_mpd_2_unknown_mime_type | 0 .../assets}/sample_mpd_3_segment_template | 0 .../exoplayer2/source/dash/DashUtilTest.java | 0 .../dash/manifest/DashManifestParserTest.java | 8 ++-- .../dash/manifest/DashManifestTest.java | 0 .../source/dash/manifest/RangedUriTest.java | 0 .../dash/manifest/RepresentationTest.java | 0 .../source/dash/manifest/UrlTemplateTest.java | 0 library/dash/src/main/AndroidManifest.xml | 17 +++++++ .../source/dash/DashChunkSource.java | 0 .../source/dash/DashMediaPeriod.java | 0 .../source/dash/DashMediaSource.java | 0 .../source/dash/DashSegmentIndex.java | 0 .../exoplayer2/source/dash/DashUtil.java | 43 ++++++++++++++++++ .../source/dash/DashWrappingSegmentIndex.java | 0 .../source/dash/DefaultDashChunkSource.java | 0 .../source/dash/manifest/AdaptationSet.java | 0 .../source/dash/manifest/DashManifest.java | 0 .../dash/manifest/DashManifestParser.java | 0 .../source/dash/manifest/Period.java | 0 .../source/dash/manifest/RangedUri.java | 0 .../source/dash/manifest/Representation.java | 0 .../dash/manifest/RepresentationKey.java | 0 .../source/dash/manifest/SchemeValuePair.java | 0 .../source/dash/manifest/SegmentBase.java | 0 .../dash/manifest/SingleSegmentIndex.java | 0 .../source/dash/manifest/UrlTemplate.java | 0 .../dash/manifest/UtcTimingElement.java | 0 playbacktests/build.gradle | 1 + settings.gradle | 2 + 34 files changed, 145 insertions(+), 5 deletions(-) create mode 100644 library/dash/build.gradle create mode 100644 library/dash/src/androidTest/AndroidManifest.xml rename library/{core/src/androidTest/assets/dash => dash/src/androidTest/assets}/sample_mpd_1 (100%) rename library/{core/src/androidTest/assets/dash => dash/src/androidTest/assets}/sample_mpd_2_unknown_mime_type (100%) rename library/{core/src/androidTest/assets/dash => dash/src/androidTest/assets}/sample_mpd_3_segment_template (100%) rename library/{core => dash}/src/androidTest/java/com/google/android/exoplayer2/source/dash/DashUtilTest.java (100%) rename library/{core => dash}/src/androidTest/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestParserTest.java (95%) rename library/{core => dash}/src/androidTest/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestTest.java (100%) rename library/{core => dash}/src/androidTest/java/com/google/android/exoplayer2/source/dash/manifest/RangedUriTest.java (100%) rename library/{core => dash}/src/androidTest/java/com/google/android/exoplayer2/source/dash/manifest/RepresentationTest.java (100%) rename library/{core => dash}/src/androidTest/java/com/google/android/exoplayer2/source/dash/manifest/UrlTemplateTest.java (100%) create mode 100644 library/dash/src/main/AndroidManifest.xml rename library/{core => dash}/src/main/java/com/google/android/exoplayer2/source/dash/DashChunkSource.java (100%) rename library/{core => dash}/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaPeriod.java (100%) rename library/{core => dash}/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaSource.java (100%) rename library/{core => dash}/src/main/java/com/google/android/exoplayer2/source/dash/DashSegmentIndex.java (100%) rename library/{core => dash}/src/main/java/com/google/android/exoplayer2/source/dash/DashUtil.java (83%) rename library/{core => dash}/src/main/java/com/google/android/exoplayer2/source/dash/DashWrappingSegmentIndex.java (100%) rename library/{core => dash}/src/main/java/com/google/android/exoplayer2/source/dash/DefaultDashChunkSource.java (100%) rename library/{core => dash}/src/main/java/com/google/android/exoplayer2/source/dash/manifest/AdaptationSet.java (100%) rename library/{core => dash}/src/main/java/com/google/android/exoplayer2/source/dash/manifest/DashManifest.java (100%) rename library/{core => dash}/src/main/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestParser.java (100%) rename library/{core => dash}/src/main/java/com/google/android/exoplayer2/source/dash/manifest/Period.java (100%) rename library/{core => dash}/src/main/java/com/google/android/exoplayer2/source/dash/manifest/RangedUri.java (100%) rename library/{core => dash}/src/main/java/com/google/android/exoplayer2/source/dash/manifest/Representation.java (100%) rename library/{core => dash}/src/main/java/com/google/android/exoplayer2/source/dash/manifest/RepresentationKey.java (100%) rename library/{core => dash}/src/main/java/com/google/android/exoplayer2/source/dash/manifest/SchemeValuePair.java (100%) rename library/{core => dash}/src/main/java/com/google/android/exoplayer2/source/dash/manifest/SegmentBase.java (100%) rename library/{core => dash}/src/main/java/com/google/android/exoplayer2/source/dash/manifest/SingleSegmentIndex.java (100%) rename library/{core => dash}/src/main/java/com/google/android/exoplayer2/source/dash/manifest/UrlTemplate.java (100%) rename library/{core => dash}/src/main/java/com/google/android/exoplayer2/source/dash/manifest/UtcTimingElement.java (100%) diff --git a/library/all/build.gradle b/library/all/build.gradle index c0874352b3..79750a9420 100644 --- a/library/all/build.gradle +++ b/library/all/build.gradle @@ -27,6 +27,7 @@ android { dependencies { compile project(':library-core') + compile project(':library-dash') compile project(':library-hls') compile project(':library-smoothstreaming') compile project(':library-ui') diff --git a/library/dash/build.gradle b/library/dash/build.gradle new file mode 100644 index 0000000000..550b0e1549 --- /dev/null +++ b/library/dash/build.gradle @@ -0,0 +1,44 @@ +// Copyright (C) 2017 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +apply plugin: 'com.android.library' + +android { + compileSdkVersion project.ext.compileSdkVersion + buildToolsVersion project.ext.buildToolsVersion + + defaultConfig { + minSdkVersion project.ext.minSdkVersion + targetSdkVersion project.ext.targetSdkVersion + } + + sourceSets { + androidTest { + java.srcDirs += "../../testutils/src/main/java/" + } + } +} + +dependencies { + compile project(':library-core') + compile 'com.android.support:support-annotations:25.2.0' + androidTestCompile 'com.google.dexmaker:dexmaker:1.2' + androidTestCompile 'com.google.dexmaker:dexmaker-mockito:1.2' + androidTestCompile 'org.mockito:mockito-core:1.9.5' +} + +ext { + releaseArtifact = 'exoplayer-dash' + releaseDescription = 'The ExoPlayer library DASH module.' +} +apply from: '../../publish.gradle' diff --git a/library/dash/src/androidTest/AndroidManifest.xml b/library/dash/src/androidTest/AndroidManifest.xml new file mode 100644 index 0000000000..ac2511d3bd --- /dev/null +++ b/library/dash/src/androidTest/AndroidManifest.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + diff --git a/library/core/src/androidTest/assets/dash/sample_mpd_1 b/library/dash/src/androidTest/assets/sample_mpd_1 similarity index 100% rename from library/core/src/androidTest/assets/dash/sample_mpd_1 rename to library/dash/src/androidTest/assets/sample_mpd_1 diff --git a/library/core/src/androidTest/assets/dash/sample_mpd_2_unknown_mime_type b/library/dash/src/androidTest/assets/sample_mpd_2_unknown_mime_type similarity index 100% rename from library/core/src/androidTest/assets/dash/sample_mpd_2_unknown_mime_type rename to library/dash/src/androidTest/assets/sample_mpd_2_unknown_mime_type diff --git a/library/core/src/androidTest/assets/dash/sample_mpd_3_segment_template b/library/dash/src/androidTest/assets/sample_mpd_3_segment_template similarity index 100% rename from library/core/src/androidTest/assets/dash/sample_mpd_3_segment_template rename to library/dash/src/androidTest/assets/sample_mpd_3_segment_template diff --git a/library/core/src/androidTest/java/com/google/android/exoplayer2/source/dash/DashUtilTest.java b/library/dash/src/androidTest/java/com/google/android/exoplayer2/source/dash/DashUtilTest.java similarity index 100% rename from library/core/src/androidTest/java/com/google/android/exoplayer2/source/dash/DashUtilTest.java rename to library/dash/src/androidTest/java/com/google/android/exoplayer2/source/dash/DashUtilTest.java diff --git a/library/core/src/androidTest/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestParserTest.java b/library/dash/src/androidTest/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestParserTest.java similarity index 95% rename from library/core/src/androidTest/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestParserTest.java rename to library/dash/src/androidTest/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestParserTest.java index 4de0ae4081..5b8760f929 100644 --- a/library/core/src/androidTest/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestParserTest.java +++ b/library/dash/src/androidTest/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestParserTest.java @@ -28,11 +28,9 @@ import java.util.List; */ public class DashManifestParserTest extends InstrumentationTestCase { - private static final String SAMPLE_MPD_1 = "dash/sample_mpd_1"; - private static final String SAMPLE_MPD_2_UNKNOWN_MIME_TYPE = - "dash/sample_mpd_2_unknown_mime_type"; - private static final String SAMPLE_MPD_3_SEGMENT_TEMPLATE = - "dash/sample_mpd_3_segment_template"; + private static final String SAMPLE_MPD_1 = "sample_mpd_1"; + private static final String SAMPLE_MPD_2_UNKNOWN_MIME_TYPE = "sample_mpd_2_unknown_mime_type"; + private static final String SAMPLE_MPD_3_SEGMENT_TEMPLATE = "sample_mpd_3_segment_template"; /** * Simple test to ensure the sample manifests parse without any exceptions being thrown. diff --git a/library/core/src/androidTest/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestTest.java b/library/dash/src/androidTest/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestTest.java similarity index 100% rename from library/core/src/androidTest/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestTest.java rename to library/dash/src/androidTest/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestTest.java diff --git a/library/core/src/androidTest/java/com/google/android/exoplayer2/source/dash/manifest/RangedUriTest.java b/library/dash/src/androidTest/java/com/google/android/exoplayer2/source/dash/manifest/RangedUriTest.java similarity index 100% rename from library/core/src/androidTest/java/com/google/android/exoplayer2/source/dash/manifest/RangedUriTest.java rename to library/dash/src/androidTest/java/com/google/android/exoplayer2/source/dash/manifest/RangedUriTest.java diff --git a/library/core/src/androidTest/java/com/google/android/exoplayer2/source/dash/manifest/RepresentationTest.java b/library/dash/src/androidTest/java/com/google/android/exoplayer2/source/dash/manifest/RepresentationTest.java similarity index 100% rename from library/core/src/androidTest/java/com/google/android/exoplayer2/source/dash/manifest/RepresentationTest.java rename to library/dash/src/androidTest/java/com/google/android/exoplayer2/source/dash/manifest/RepresentationTest.java diff --git a/library/core/src/androidTest/java/com/google/android/exoplayer2/source/dash/manifest/UrlTemplateTest.java b/library/dash/src/androidTest/java/com/google/android/exoplayer2/source/dash/manifest/UrlTemplateTest.java similarity index 100% rename from library/core/src/androidTest/java/com/google/android/exoplayer2/source/dash/manifest/UrlTemplateTest.java rename to library/dash/src/androidTest/java/com/google/android/exoplayer2/source/dash/manifest/UrlTemplateTest.java diff --git a/library/dash/src/main/AndroidManifest.xml b/library/dash/src/main/AndroidManifest.xml new file mode 100644 index 0000000000..6c2d93510a --- /dev/null +++ b/library/dash/src/main/AndroidManifest.xml @@ -0,0 +1,17 @@ + + + + diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/dash/DashChunkSource.java b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashChunkSource.java similarity index 100% rename from library/core/src/main/java/com/google/android/exoplayer2/source/dash/DashChunkSource.java rename to library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashChunkSource.java diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaPeriod.java b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaPeriod.java similarity index 100% rename from library/core/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaPeriod.java rename to library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaPeriod.java diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaSource.java b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaSource.java similarity index 100% rename from library/core/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaSource.java rename to library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaSource.java diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/dash/DashSegmentIndex.java b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashSegmentIndex.java similarity index 100% rename from library/core/src/main/java/com/google/android/exoplayer2/source/dash/DashSegmentIndex.java rename to library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashSegmentIndex.java diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/dash/DashUtil.java b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashUtil.java similarity index 83% rename from library/core/src/main/java/com/google/android/exoplayer2/source/dash/DashUtil.java rename to library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashUtil.java index 0b8750e20a..3ec44c2f69 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/dash/DashUtil.java +++ b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashUtil.java @@ -25,6 +25,7 @@ import com.google.android.exoplayer2.extractor.mkv.MatroskaExtractor; import com.google.android.exoplayer2.extractor.mp4.FragmentedMp4Extractor; import com.google.android.exoplayer2.source.chunk.ChunkExtractorWrapper; import com.google.android.exoplayer2.source.chunk.InitializationChunk; +import com.google.android.exoplayer2.source.dash.manifest.AdaptationSet; import com.google.android.exoplayer2.source.dash.manifest.DashManifest; import com.google.android.exoplayer2.source.dash.manifest.DashManifestParser; import com.google.android.exoplayer2.source.dash.manifest.Period; @@ -65,6 +66,48 @@ public final class DashUtil { } /** + * Loads {@link DrmInitData} for a given manifest. + * + * @param dataSource The {@link HttpDataSource} from which data should be loaded. + * @param dashManifest The {@link DashManifest} of the DASH content. + * @return The loaded {@link DrmInitData}. + */ + public static DrmInitData loadDrmInitData(DataSource dataSource, DashManifest dashManifest) + throws IOException, InterruptedException { + // Prefer drmInitData obtained from the manifest over drmInitData obtained from the stream, + // as per DASH IF Interoperability Recommendations V3.0, 7.5.3. + if (dashManifest.getPeriodCount() < 1) { + return null; + } + Period period = dashManifest.getPeriod(0); + int adaptationSetIndex = period.getAdaptationSetIndex(C.TRACK_TYPE_VIDEO); + if (adaptationSetIndex == C.INDEX_UNSET) { + adaptationSetIndex = period.getAdaptationSetIndex(C.TRACK_TYPE_AUDIO); + if (adaptationSetIndex == C.INDEX_UNSET) { + return null; + } + } + AdaptationSet adaptationSet = period.adaptationSets.get(adaptationSetIndex); + if (adaptationSet.representations.isEmpty()) { + return null; + } + Representation representation = adaptationSet.representations.get(0); + DrmInitData drmInitData = representation.format.drmInitData; + if (drmInitData == null) { + Format sampleFormat = DashUtil.loadSampleFormat(dataSource, representation); + if (sampleFormat != null) { + drmInitData = sampleFormat.drmInitData; + } + if (drmInitData == null) { + return null; + } + } + return drmInitData; + } + + /** + * Loads initialization data for the {@code representation} and returns the sample {@link + * Format}. * Loads {@link DrmInitData} for a given period in a DASH manifest. * * @param dataSource The {@link HttpDataSource} from which data should be loaded. diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/dash/DashWrappingSegmentIndex.java b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashWrappingSegmentIndex.java similarity index 100% rename from library/core/src/main/java/com/google/android/exoplayer2/source/dash/DashWrappingSegmentIndex.java rename to library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashWrappingSegmentIndex.java diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/dash/DefaultDashChunkSource.java b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DefaultDashChunkSource.java similarity index 100% rename from library/core/src/main/java/com/google/android/exoplayer2/source/dash/DefaultDashChunkSource.java rename to library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DefaultDashChunkSource.java diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/dash/manifest/AdaptationSet.java b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/AdaptationSet.java similarity index 100% rename from library/core/src/main/java/com/google/android/exoplayer2/source/dash/manifest/AdaptationSet.java rename to library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/AdaptationSet.java diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/dash/manifest/DashManifest.java b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/DashManifest.java similarity index 100% rename from library/core/src/main/java/com/google/android/exoplayer2/source/dash/manifest/DashManifest.java rename to library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/DashManifest.java diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestParser.java b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestParser.java similarity index 100% rename from library/core/src/main/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestParser.java rename to library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestParser.java diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/dash/manifest/Period.java b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/Period.java similarity index 100% rename from library/core/src/main/java/com/google/android/exoplayer2/source/dash/manifest/Period.java rename to library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/Period.java diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/dash/manifest/RangedUri.java b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/RangedUri.java similarity index 100% rename from library/core/src/main/java/com/google/android/exoplayer2/source/dash/manifest/RangedUri.java rename to library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/RangedUri.java diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/dash/manifest/Representation.java b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/Representation.java similarity index 100% rename from library/core/src/main/java/com/google/android/exoplayer2/source/dash/manifest/Representation.java rename to library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/Representation.java diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/dash/manifest/RepresentationKey.java b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/RepresentationKey.java similarity index 100% rename from library/core/src/main/java/com/google/android/exoplayer2/source/dash/manifest/RepresentationKey.java rename to library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/RepresentationKey.java diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/dash/manifest/SchemeValuePair.java b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/SchemeValuePair.java similarity index 100% rename from library/core/src/main/java/com/google/android/exoplayer2/source/dash/manifest/SchemeValuePair.java rename to library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/SchemeValuePair.java diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/dash/manifest/SegmentBase.java b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/SegmentBase.java similarity index 100% rename from library/core/src/main/java/com/google/android/exoplayer2/source/dash/manifest/SegmentBase.java rename to library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/SegmentBase.java diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/dash/manifest/SingleSegmentIndex.java b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/SingleSegmentIndex.java similarity index 100% rename from library/core/src/main/java/com/google/android/exoplayer2/source/dash/manifest/SingleSegmentIndex.java rename to library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/SingleSegmentIndex.java diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/dash/manifest/UrlTemplate.java b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/UrlTemplate.java similarity index 100% rename from library/core/src/main/java/com/google/android/exoplayer2/source/dash/manifest/UrlTemplate.java rename to library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/UrlTemplate.java diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/dash/manifest/UtcTimingElement.java b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/UtcTimingElement.java similarity index 100% rename from library/core/src/main/java/com/google/android/exoplayer2/source/dash/manifest/UtcTimingElement.java rename to library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/UtcTimingElement.java diff --git a/playbacktests/build.gradle b/playbacktests/build.gradle index fa3c930c4a..6a09eac49e 100644 --- a/playbacktests/build.gradle +++ b/playbacktests/build.gradle @@ -25,5 +25,6 @@ android { dependencies { compile project(':library-core') + androidTestCompile project(':library-dash') androidTestCompile project(':library-hls') } diff --git a/settings.gradle b/settings.gradle index 45e2a1a1f1..544d2d4a21 100644 --- a/settings.gradle +++ b/settings.gradle @@ -13,6 +13,7 @@ // limitations under the License. include ':library' include ':library-core' +include ':library-dash' include ':library-hls' include ':library-smoothstreaming' include ':library-ui' @@ -30,6 +31,7 @@ include ':extension-vp9' project(':library').projectDir = new File(settingsDir, 'library/all') project(':library-core').projectDir = new File(settingsDir, 'library/core') +project(':library-dash').projectDir = new File(settingsDir, 'library/dash') project(':library-hls').projectDir = new File(settingsDir, 'library/hls') project(':library-smoothstreaming').projectDir = new File(settingsDir, 'library/smoothstreaming') project(':library-ui').projectDir = new File(settingsDir, 'library/ui') From 88e74ef381d109b0ac0ba4193335704cf59d8f01 Mon Sep 17 00:00:00 2001 From: olly Date: Wed, 22 Mar 2017 04:21:16 -0700 Subject: [PATCH 017/119] Generate Javadoc for each module individually Notes: - The VP9 one is failing claiming that the util package doesn't exist and that LibraryLoader cannot be found. Unsure why, since it appears to be setup exactly like other extensions (e.g. Opus) that does work. - @link across modules will not work when generating Javadoc for a single module. This is WAI. I subsequent change will add an aggregated Javadoc generator that will generate Javadoc for all modules together and apply cross module @link correctly. Issue: #2139 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=150864273 --- extensions/cronet/build.gradle | 5 ++++ extensions/ffmpeg/build.gradle | 5 ++++ extensions/flac/build.gradle | 4 ++++ extensions/gvr/build.gradle | 5 ++++ extensions/okhttp/build.gradle | 5 ++++ extensions/opus/build.gradle | 5 ++++ extensions/vp9/build.gradle | 4 ++++ library/all/build.gradle | 33 --------------------------- library/core/build.gradle | 5 ++++ library/dash/build.gradle | 5 ++++ library/hls/build.gradle | 5 ++++ library/smoothstreaming/build.gradle | 5 ++++ library/ui/build.gradle | 5 ++++ library_javadoc.gradle | 34 ++++++++++++++++++++++++++++ 14 files changed, 92 insertions(+), 33 deletions(-) create mode 100644 library_javadoc.gradle diff --git a/extensions/cronet/build.gradle b/extensions/cronet/build.gradle index 47d4cbef98..13c0c645de 100644 --- a/extensions/cronet/build.gradle +++ b/extensions/cronet/build.gradle @@ -39,3 +39,8 @@ dependencies { androidTestCompile project(':library') androidTestCompile 'com.android.support.test:runner:0.5' } + +ext { + javadocTitle = 'Cronet extension' +} +apply from: '../../library_javadoc.gradle' diff --git a/extensions/ffmpeg/build.gradle b/extensions/ffmpeg/build.gradle index be8ad761bb..b89b6deec1 100644 --- a/extensions/ffmpeg/build.gradle +++ b/extensions/ffmpeg/build.gradle @@ -32,3 +32,8 @@ android { dependencies { compile project(':library-core') } + +ext { + javadocTitle = 'FFmpeg extension' +} +apply from: '../../library_javadoc.gradle' diff --git a/extensions/flac/build.gradle b/extensions/flac/build.gradle index 33f8266deb..5ad0654149 100644 --- a/extensions/flac/build.gradle +++ b/extensions/flac/build.gradle @@ -34,3 +34,7 @@ dependencies { androidTestCompile project(':testutils') } +ext { + javadocTitle = 'FLAC extension' +} +apply from: '../../library_javadoc.gradle' diff --git a/extensions/gvr/build.gradle b/extensions/gvr/build.gradle index 0e7a478186..c65be14bd3 100644 --- a/extensions/gvr/build.gradle +++ b/extensions/gvr/build.gradle @@ -28,6 +28,11 @@ dependencies { compile 'com.google.vr:sdk-audio:1.30.0' } +ext { + javadocTitle = 'GVR extension' +} +apply from: '../../library_javadoc.gradle' + ext { releaseArtifact = 'extension-gvr' releaseDescription = 'Google VR extension for ExoPlayer.' diff --git a/extensions/okhttp/build.gradle b/extensions/okhttp/build.gradle index 3679ae8877..d06a17519e 100644 --- a/extensions/okhttp/build.gradle +++ b/extensions/okhttp/build.gradle @@ -35,6 +35,11 @@ dependencies { } } +ext { + javadocTitle = 'OkHttp extension' +} +apply from: '../../library_javadoc.gradle' + ext { releaseArtifact = 'extension-okhttp' releaseDescription = 'OkHttp extension for ExoPlayer.' diff --git a/extensions/opus/build.gradle b/extensions/opus/build.gradle index be8ad761bb..dd403d3de4 100644 --- a/extensions/opus/build.gradle +++ b/extensions/opus/build.gradle @@ -32,3 +32,8 @@ android { dependencies { compile project(':library-core') } + +ext { + javadocTitle = 'Opus extension' +} +apply from: '../../library_javadoc.gradle' diff --git a/extensions/vp9/build.gradle b/extensions/vp9/build.gradle index 5c72f46a9f..95fd1d3e67 100644 --- a/extensions/vp9/build.gradle +++ b/extensions/vp9/build.gradle @@ -33,3 +33,7 @@ dependencies { compile project(':library-core') } +ext { + javadocTitle = 'VP9 extension' +} +apply from: '../../library_javadoc.gradle' diff --git a/library/all/build.gradle b/library/all/build.gradle index 79750a9420..e88ee2721d 100644 --- a/library/all/build.gradle +++ b/library/all/build.gradle @@ -33,39 +33,6 @@ dependencies { compile project(':library-ui') } -android.libraryVariants.all { variant -> - def name = variant.buildType.name - if (name.equals(BuilderConstants.DEBUG)) { - return; // Skip debug builds. - } - def task = project.tasks.create "jar${name.capitalize()}", Jar - task.dependsOn variant.javaCompile - task.from variant.javaCompile.destinationDir - artifacts.add('archives', task); -} - -android.libraryVariants.all { variant -> - task("generate${variant.name.capitalize()}Javadoc", type: Javadoc) { - title = "ExoPlayer library" - description "Generates Javadoc for $variant.name." - source = variant.javaCompile.source - classpath = files(variant.javaCompile.classpath.files, project.android.getBootClasspath()) - options { - links "http://docs.oracle.com/javase/7/docs/api/" - linksOffline "https://developer.android.com/reference","${android.sdkDirectory}/docs/reference" - encoding = 'UTF-8' - } - exclude '**/BuildConfig.java' - exclude '**/R.java' - doLast { - copy { - from "src/main/javadoc" - into "$buildDir/docs/javadoc" - } - } - } -} - ext { releaseArtifact = 'exoplayer' releaseDescription = 'The ExoPlayer library (all modules).' diff --git a/library/core/build.gradle b/library/core/build.gradle index 980d5ffe2c..a2952e91ff 100644 --- a/library/core/build.gradle +++ b/library/core/build.gradle @@ -45,6 +45,11 @@ dependencies { androidTestCompile 'org.mockito:mockito-core:1.9.5' } +ext { + javadocTitle = 'Core' +} +apply from: '../../library_javadoc.gradle' + ext { releaseArtifact = 'exoplayer-core' releaseDescription = 'The ExoPlayer library core module.' diff --git a/library/dash/build.gradle b/library/dash/build.gradle index 550b0e1549..1030a58c56 100644 --- a/library/dash/build.gradle +++ b/library/dash/build.gradle @@ -37,6 +37,11 @@ dependencies { androidTestCompile 'org.mockito:mockito-core:1.9.5' } +ext { + javadocTitle = 'DASH' +} +apply from: '../../library_javadoc.gradle' + ext { releaseArtifact = 'exoplayer-dash' releaseDescription = 'The ExoPlayer library DASH module.' diff --git a/library/hls/build.gradle b/library/hls/build.gradle index bf19de9abd..9bcca92348 100644 --- a/library/hls/build.gradle +++ b/library/hls/build.gradle @@ -31,6 +31,11 @@ dependencies { androidTestCompile 'org.mockito:mockito-core:1.9.5' } +ext { + javadocTitle = 'HLS' +} +apply from: '../../library_javadoc.gradle' + ext { releaseArtifact = 'exoplayer-hls' releaseDescription = 'The ExoPlayer library HLS module.' diff --git a/library/smoothstreaming/build.gradle b/library/smoothstreaming/build.gradle index bcec8f5319..4085392f6f 100644 --- a/library/smoothstreaming/build.gradle +++ b/library/smoothstreaming/build.gradle @@ -37,6 +37,11 @@ dependencies { androidTestCompile 'org.mockito:mockito-core:1.9.5' } +ext { + javadocTitle = 'SmoothStreaming' +} +apply from: '../../library_javadoc.gradle' + ext { releaseArtifact = 'exoplayer-smoothstreaming' releaseDescription = 'The ExoPlayer library SmoothStreaming module.' diff --git a/library/ui/build.gradle b/library/ui/build.gradle index 4c47d44e26..4ecc759969 100644 --- a/library/ui/build.gradle +++ b/library/ui/build.gradle @@ -28,6 +28,11 @@ dependencies { compile 'com.android.support:support-annotations:25.2.0' } +ext { + javadocTitle = 'UI' +} +apply from: '../../library_javadoc.gradle' + ext { releaseArtifact = 'exoplayer-ui' releaseDescription = 'The ExoPlayer library UI module.' diff --git a/library_javadoc.gradle b/library_javadoc.gradle new file mode 100644 index 0000000000..ac33bae3dd --- /dev/null +++ b/library_javadoc.gradle @@ -0,0 +1,34 @@ +// Copyright (C) 2017 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +android.libraryVariants.all { variant -> + task("generate${variant.name.capitalize()}Javadoc", type: Javadoc) { + description "Generates Javadoc for $variant.name." + title = javadocTitle + source = variant.javaCompile.source + classpath = files(variant.javaCompile.classpath.files, project.android.getBootClasspath()) + options { + links "http://docs.oracle.com/javase/7/docs/api/" + linksOffline "https://developer.android.com/reference","${android.sdkDirectory}/docs/reference" + encoding = 'UTF-8' + } + exclude '**/BuildConfig.java' + exclude '**/R.java' + doLast { + copy { + from "src/main/javadoc" + into "$buildDir/docs/javadoc" + } + } + } +} From 382ba7ecf0907d07765e3c14d45692845a6e7621 Mon Sep 17 00:00:00 2001 From: olly Date: Wed, 22 Mar 2017 04:48:09 -0700 Subject: [PATCH 018/119] Generate combined Javadoc Notes: - Now only generating a single module Javadoc task for the release variant. - Combined Javadoc now includes extensions. VP9 is excluded for now since it's failing for an unknown reason. Issue: #2139 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=150865589 --- build.gradle | 2 + extensions/cronet/build.gradle | 2 +- extensions/ffmpeg/build.gradle | 2 +- extensions/flac/build.gradle | 2 +- extensions/gvr/build.gradle | 2 +- extensions/okhttp/build.gradle | 2 +- extensions/opus/build.gradle | 2 +- extensions/vp9/build.gradle | 2 +- javadoc_combined.gradle | 68 +++++++++++++++++++ ...y_javadoc.gradle => javadoc_library.gradle | 22 +++--- library/core/build.gradle | 4 +- library/dash/build.gradle | 4 +- library/hls/build.gradle | 4 +- library/smoothstreaming/build.gradle | 4 +- library/ui/build.gradle | 4 +- 15 files changed, 101 insertions(+), 25 deletions(-) create mode 100644 javadoc_combined.gradle rename library_javadoc.gradle => javadoc_library.gradle (64%) diff --git a/build.gradle b/build.gradle index f1901a1270..49c7d5c22a 100644 --- a/build.gradle +++ b/build.gradle @@ -47,3 +47,5 @@ def getBintrayRepo() { property('publicRepo').toBoolean() return publicRepo ? 'exoplayer' : 'exoplayer-test' } + +apply from: 'javadoc_combined.gradle' diff --git a/extensions/cronet/build.gradle b/extensions/cronet/build.gradle index 13c0c645de..5e3c2ab482 100644 --- a/extensions/cronet/build.gradle +++ b/extensions/cronet/build.gradle @@ -43,4 +43,4 @@ dependencies { ext { javadocTitle = 'Cronet extension' } -apply from: '../../library_javadoc.gradle' +apply from: '../../javadoc_library.gradle' diff --git a/extensions/ffmpeg/build.gradle b/extensions/ffmpeg/build.gradle index b89b6deec1..0eddd017a4 100644 --- a/extensions/ffmpeg/build.gradle +++ b/extensions/ffmpeg/build.gradle @@ -36,4 +36,4 @@ dependencies { ext { javadocTitle = 'FFmpeg extension' } -apply from: '../../library_javadoc.gradle' +apply from: '../../javadoc_library.gradle' diff --git a/extensions/flac/build.gradle b/extensions/flac/build.gradle index 5ad0654149..4a6b8e0e5a 100644 --- a/extensions/flac/build.gradle +++ b/extensions/flac/build.gradle @@ -37,4 +37,4 @@ dependencies { ext { javadocTitle = 'FLAC extension' } -apply from: '../../library_javadoc.gradle' +apply from: '../../javadoc_library.gradle' diff --git a/extensions/gvr/build.gradle b/extensions/gvr/build.gradle index c65be14bd3..f622a73758 100644 --- a/extensions/gvr/build.gradle +++ b/extensions/gvr/build.gradle @@ -31,7 +31,7 @@ dependencies { ext { javadocTitle = 'GVR extension' } -apply from: '../../library_javadoc.gradle' +apply from: '../../javadoc_library.gradle' ext { releaseArtifact = 'extension-gvr' diff --git a/extensions/okhttp/build.gradle b/extensions/okhttp/build.gradle index d06a17519e..f47f1a8556 100644 --- a/extensions/okhttp/build.gradle +++ b/extensions/okhttp/build.gradle @@ -38,7 +38,7 @@ dependencies { ext { javadocTitle = 'OkHttp extension' } -apply from: '../../library_javadoc.gradle' +apply from: '../../javadoc_library.gradle' ext { releaseArtifact = 'extension-okhttp' diff --git a/extensions/opus/build.gradle b/extensions/opus/build.gradle index dd403d3de4..31d5450fdd 100644 --- a/extensions/opus/build.gradle +++ b/extensions/opus/build.gradle @@ -36,4 +36,4 @@ dependencies { ext { javadocTitle = 'Opus extension' } -apply from: '../../library_javadoc.gradle' +apply from: '../../javadoc_library.gradle' diff --git a/extensions/vp9/build.gradle b/extensions/vp9/build.gradle index 95fd1d3e67..5068586a4a 100644 --- a/extensions/vp9/build.gradle +++ b/extensions/vp9/build.gradle @@ -36,4 +36,4 @@ dependencies { ext { javadocTitle = 'VP9 extension' } -apply from: '../../library_javadoc.gradle' +apply from: '../../javadoc_library.gradle' diff --git a/javadoc_combined.gradle b/javadoc_combined.gradle new file mode 100644 index 0000000000..1fec48ca25 --- /dev/null +++ b/javadoc_combined.gradle @@ -0,0 +1,68 @@ +// Copyright (C) 2017 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +class CombinedJavadocPlugin implements Plugin { + + static final String TASK_NAME = "generateCombinedJavadoc" + + @Override + void apply(Project project) { + project.gradle.projectsEvaluated { + Set libraryModules = getLibraryModules(project) + if (!libraryModules.isEmpty()) { + String sdkDirectory = getSdkDirectory(libraryModules) + project.task(TASK_NAME, type: Javadoc) { + description = "Generates combined Javadoc." + title = "ExoPlayer library" + source = libraryModules.generateJavadoc.source + classpath = project.files(libraryModules.generateJavadoc.classpath) + destinationDir = project.file("$project.buildDir/docs/javadoc") + options { + links "http://docs.oracle.com/javase/7/docs/api/" + linksOffline "https://developer.android.com/reference", + "${sdkDirectory}/docs/reference" + encoding = "UTF-8" + } + exclude "**/BuildConfig.java" + exclude "**/R.java" + destinationDir project.file("$project.buildDir/docs/javadoc") + doLast { + libraryModules.each { libraryModule -> + project.copy { + from "${libraryModule.projectDir}/src/main/javadoc" + into "${project.buildDir}/docs/javadoc" + } + } + } + } + } + } + } + + // Returns Android library modules that declare a generateJavadoc task. + private Set getLibraryModules(Project project) { + project.subprojects.findAll { + it.plugins.findPlugin("com.android.library") && + it.tasks.findByName("generateJavadoc") + } + } + + // Returns the Android SDK directory given a set of Android library modules. + private String getSdkDirectory(Set libraryModules) { + // We can retrieve the Android SDK directory from any module. + return libraryModules.iterator().next().android.sdkDirectory + } + +} + +apply plugin: CombinedJavadocPlugin diff --git a/library_javadoc.gradle b/javadoc_library.gradle similarity index 64% rename from library_javadoc.gradle rename to javadoc_library.gradle index ac33bae3dd..ea193e661c 100644 --- a/library_javadoc.gradle +++ b/javadoc_library.gradle @@ -12,18 +12,24 @@ // See the License for the specific language governing permissions and // limitations under the License. android.libraryVariants.all { variant -> - task("generate${variant.name.capitalize()}Javadoc", type: Javadoc) { - description "Generates Javadoc for $variant.name." - title = javadocTitle + def name = variant.buildType.name + if (!name.equals("release")) { + return; // Skip non-release builds. + } + task("generateJavadoc", type: Javadoc) { + description = "Generates Javadoc for the ${javadocTitle}." + title = "ExoPlayer ${javadocTitle}" source = variant.javaCompile.source - classpath = files(variant.javaCompile.classpath.files, project.android.getBootClasspath()) + classpath = files(variant.javaCompile.classpath.files, + project.android.getBootClasspath()) options { links "http://docs.oracle.com/javase/7/docs/api/" - linksOffline "https://developer.android.com/reference","${android.sdkDirectory}/docs/reference" - encoding = 'UTF-8' + linksOffline "https://developer.android.com/reference", + "${android.sdkDirectory}/docs/reference" + encoding = "UTF-8" } - exclude '**/BuildConfig.java' - exclude '**/R.java' + exclude "**/BuildConfig.java" + exclude "**/R.java" doLast { copy { from "src/main/javadoc" diff --git a/library/core/build.gradle b/library/core/build.gradle index a2952e91ff..d2ce499235 100644 --- a/library/core/build.gradle +++ b/library/core/build.gradle @@ -46,9 +46,9 @@ dependencies { } ext { - javadocTitle = 'Core' + javadocTitle = 'Core module' } -apply from: '../../library_javadoc.gradle' +apply from: '../../javadoc_library.gradle' ext { releaseArtifact = 'exoplayer-core' diff --git a/library/dash/build.gradle b/library/dash/build.gradle index 1030a58c56..93f75216db 100644 --- a/library/dash/build.gradle +++ b/library/dash/build.gradle @@ -38,9 +38,9 @@ dependencies { } ext { - javadocTitle = 'DASH' + javadocTitle = 'DASH module' } -apply from: '../../library_javadoc.gradle' +apply from: '../../javadoc_library.gradle' ext { releaseArtifact = 'exoplayer-dash' diff --git a/library/hls/build.gradle b/library/hls/build.gradle index 9bcca92348..a35142870c 100644 --- a/library/hls/build.gradle +++ b/library/hls/build.gradle @@ -32,9 +32,9 @@ dependencies { } ext { - javadocTitle = 'HLS' + javadocTitle = 'HLS module' } -apply from: '../../library_javadoc.gradle' +apply from: '../../javadoc_library.gradle' ext { releaseArtifact = 'exoplayer-hls' diff --git a/library/smoothstreaming/build.gradle b/library/smoothstreaming/build.gradle index 4085392f6f..c9196c1792 100644 --- a/library/smoothstreaming/build.gradle +++ b/library/smoothstreaming/build.gradle @@ -38,9 +38,9 @@ dependencies { } ext { - javadocTitle = 'SmoothStreaming' + javadocTitle = 'SmoothStreaming module' } -apply from: '../../library_javadoc.gradle' +apply from: '../../javadoc_library.gradle' ext { releaseArtifact = 'exoplayer-smoothstreaming' diff --git a/library/ui/build.gradle b/library/ui/build.gradle index 4ecc759969..6defb42078 100644 --- a/library/ui/build.gradle +++ b/library/ui/build.gradle @@ -29,9 +29,9 @@ dependencies { } ext { - javadocTitle = 'UI' + javadocTitle = 'UI module' } -apply from: '../../library_javadoc.gradle' +apply from: '../../javadoc_library.gradle' ext { releaseArtifact = 'exoplayer-ui' From 8f636991ad152bca610562b2374e1064ebfbc34b Mon Sep 17 00:00:00 2001 From: eguven Date: Wed, 22 Mar 2017 05:47:00 -0700 Subject: [PATCH 019/119] Add DummyDataSource A dummy DataSource which provides no data. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=150868950 --- .../exoplayer2/upstream/DummyDataSource.java | 58 +++++++++++++++++++ .../exoplayer2/source/dash/DashUtilTest.java | 19 ++---- 2 files changed, 63 insertions(+), 14 deletions(-) create mode 100644 library/core/src/main/java/com/google/android/exoplayer2/upstream/DummyDataSource.java diff --git a/library/core/src/main/java/com/google/android/exoplayer2/upstream/DummyDataSource.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/DummyDataSource.java new file mode 100644 index 0000000000..c20868ef00 --- /dev/null +++ b/library/core/src/main/java/com/google/android/exoplayer2/upstream/DummyDataSource.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.exoplayer2.upstream; + +import android.net.Uri; +import java.io.IOException; + +/** + * A dummy DataSource which provides no data. {@link #open(DataSpec)} throws {@link IOException}. + */ +public final class DummyDataSource implements DataSource { + + public static final DummyDataSource INSTANCE = new DummyDataSource(); + + /** A factory that that produces {@link DummyDataSource}. */ + public static final Factory FACTORY = new Factory() { + @Override + public DataSource createDataSource() { + return new DummyDataSource(); + } + }; + + private DummyDataSource() {} + + @Override + public long open(DataSpec dataSpec) throws IOException { + throw new IOException("Dummy source"); + } + + @Override + public int read(byte[] buffer, int offset, int readLength) throws IOException { + throw new UnsupportedOperationException(); + } + + @Override + public Uri getUri() { + return null; + } + + @Override + public void close() throws IOException { + // do nothing. + } + +} diff --git a/library/dash/src/androidTest/java/com/google/android/exoplayer2/source/dash/DashUtilTest.java b/library/dash/src/androidTest/java/com/google/android/exoplayer2/source/dash/DashUtilTest.java index 3ee76f43bb..c9f1ca1030 100644 --- a/library/dash/src/androidTest/java/com/google/android/exoplayer2/source/dash/DashUtilTest.java +++ b/library/dash/src/androidTest/java/com/google/android/exoplayer2/source/dash/DashUtilTest.java @@ -23,10 +23,8 @@ import com.google.android.exoplayer2.source.dash.manifest.AdaptationSet; import com.google.android.exoplayer2.source.dash.manifest.Period; import com.google.android.exoplayer2.source.dash.manifest.Representation; import com.google.android.exoplayer2.source.dash.manifest.SegmentBase.SingleSegmentBase; -import com.google.android.exoplayer2.testutil.FakeDataSource; -import com.google.android.exoplayer2.upstream.DataSource; +import com.google.android.exoplayer2.upstream.DummyDataSource; import com.google.android.exoplayer2.util.MimeTypes; -import java.io.IOException; import java.util.Arrays; import junit.framework.TestCase; @@ -37,25 +35,25 @@ public final class DashUtilTest extends TestCase { public void testLoadDrmInitDataFromManifest() throws Exception { Period period = newPeriod(newAdaptationSets(newRepresentations(newDrmInitData()))); - DrmInitData drmInitData = DashUtil.loadDrmInitData(newDataSource(), period); + DrmInitData drmInitData = DashUtil.loadDrmInitData(DummyDataSource.INSTANCE, period); assertEquals(newDrmInitData(), drmInitData); } public void testLoadDrmInitDataMissing() throws Exception { Period period = newPeriod(newAdaptationSets(newRepresentations(null /* no init data */))); - DrmInitData drmInitData = DashUtil.loadDrmInitData(newDataSource(), period); + DrmInitData drmInitData = DashUtil.loadDrmInitData(DummyDataSource.INSTANCE, period); assertNull(drmInitData); } public void testLoadDrmInitDataNoRepresentations() throws Exception { Period period = newPeriod(newAdaptationSets(/* no representation */)); - DrmInitData drmInitData = DashUtil.loadDrmInitData(newDataSource(), period); + DrmInitData drmInitData = DashUtil.loadDrmInitData(DummyDataSource.INSTANCE, period); assertNull(drmInitData); } public void testLoadDrmInitDataNoAdaptationSets() throws Exception { Period period = newPeriod(/* no adaptation set */); - DrmInitData drmInitData = DashUtil.loadDrmInitData(newDataSource(), period); + DrmInitData drmInitData = DashUtil.loadDrmInitData(DummyDataSource.INSTANCE, period); assertNull(drmInitData); } @@ -81,11 +79,4 @@ public final class DashUtilTest extends TestCase { new byte[]{1, 4, 7, 0, 3, 6})); } - private static DataSource newDataSource() { - // TODO: Use DummyDataSource when available. - FakeDataSource fakeDataSource = new FakeDataSource(); - fakeDataSource.getDataSet().newDefaultData().appendReadError(new IOException("Unexpected")); - return fakeDataSource; - } - } From e0a00502a72a6a1b8fe3d7143bba38b49dd7c776 Mon Sep 17 00:00:00 2001 From: sammon Date: Wed, 22 Mar 2017 12:01:06 -0700 Subject: [PATCH 020/119] Avoid exception when creating ChunkIndex with length = 0. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=150908893 --- .../com/google/android/exoplayer2/extractor/ChunkIndex.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/ChunkIndex.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ChunkIndex.java index 4ce430c5ff..baa5589f4b 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/ChunkIndex.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ChunkIndex.java @@ -61,7 +61,11 @@ public final class ChunkIndex implements SeekMap { this.durationsUs = durationsUs; this.timesUs = timesUs; length = sizes.length; - durationUs = durationsUs[length - 1] + timesUs[length - 1]; + if (length > 0) { + durationUs = durationsUs[length - 1] + timesUs[length - 1]; + } else { + durationUs = 0; + } } /** From 42629701f84645448d9f4b1c171018fec1281394 Mon Sep 17 00:00:00 2001 From: olly Date: Wed, 22 Mar 2017 13:05:33 -0700 Subject: [PATCH 021/119] Work around broken VP8 decoder on Note 2 Issue: #1355 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=150916986 --- .../android/exoplayer2/mediacodec/MediaCodecUtil.java | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) 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 a3a2543461..a09f6e26dd 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 @@ -228,10 +228,12 @@ public final class MediaCodecUtil { || "MP3Decoder".equals(name))) { return false; } + // Work around https://github.com/google/ExoPlayer/issues/398 if (Util.SDK_INT < 18 && "OMX.SEC.MP3.Decoder".equals(name)) { return false; } + // Work around https://github.com/google/ExoPlayer/issues/1528 if (Util.SDK_INT < 18 && "OMX.MTK.AUDIO.DECODER.AAC".equals(name) && "a70".equals(Util.DEVICE)) { @@ -268,13 +270,15 @@ public final class MediaCodecUtil { } // Work around https://github.com/google/ExoPlayer/issues/548 - // VP8 decoder on Samsung Galaxy S3/S4/S4 Mini/Tab 3 does not render video. + // VP8 decoder on Samsung Galaxy S3/S4/S4 Mini/Tab 3/Note 2 does not render video. if (Util.SDK_INT <= 19 + && "OMX.SEC.vp8.dec".equals(name) && "samsung".equals(Util.MANUFACTURER) && (Util.DEVICE.startsWith("d2") || Util.DEVICE.startsWith("serrano") - || Util.DEVICE.startsWith("jflte") || Util.DEVICE.startsWith("santos")) - && "samsung".equals(Util.MANUFACTURER) && "OMX.SEC.vp8.dec".equals(name)) { + || Util.DEVICE.startsWith("jflte") || Util.DEVICE.startsWith("santos") + || Util.DEVICE.startsWith("t0"))) { return false; } + // VP8 decoder on Samsung Galaxy S4 cannot be queried. if (Util.SDK_INT <= 19 && Util.DEVICE.startsWith("jflte") && "OMX.qcom.video.decoder.vp8".equals(name)) { From d8f61ad7e7090496a1d43e7a5cd31feb3abc478c Mon Sep 17 00:00:00 2001 From: Sergio Moreno Mozota Date: Thu, 23 Mar 2017 08:51:41 +0100 Subject: [PATCH 022/119] Add DVB subtitles support --- .../google/android/exoplayer2/text/Cue.java | 34 +- .../text/SubtitleDecoderFactory.java | 6 + .../text/dvbsubs/DvbSubsDecoder.java | 71 + .../text/dvbsubs/DvbSubsSubtitle.java | 59 + .../text/dvbsubs/DvbSubtitlesParser.java | 1561 +++++++++++++++++ .../android/exoplayer2/util/MimeTypes.java | 3 +- .../exoplayer2/ui/SubtitlePainter.java | 5 +- 7 files changed, 1733 insertions(+), 6 deletions(-) create mode 100644 library/core/src/main/java/com/google/android/exoplayer2/text/dvbsubs/DvbSubsDecoder.java create mode 100644 library/core/src/main/java/com/google/android/exoplayer2/text/dvbsubs/DvbSubsSubtitle.java create mode 100644 library/core/src/main/java/com/google/android/exoplayer2/text/dvbsubs/DvbSubtitlesParser.java diff --git a/library/core/src/main/java/com/google/android/exoplayer2/text/Cue.java b/library/core/src/main/java/com/google/android/exoplayer2/text/Cue.java index 176b8ea815..a13dbed624 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/text/Cue.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/text/Cue.java @@ -178,7 +178,11 @@ public class Cue { public final int windowColor; /** - * Creates an image cue. + * The Storage Aspect Ratio of the Cue + */ + public final float sar; + + /** * Creates an image cue. * * @param bitmap See {@link #bitmap}. * @param horizontalPosition The position of the horizontal anchor within the viewport, expressed @@ -194,7 +198,28 @@ public class Cue { public Cue(Bitmap bitmap, float horizontalPosition, @AnchorType int horizontalPositionAnchor, float verticalPosition, @AnchorType int verticalPositionAnchor, float width) { this(null, null, bitmap, verticalPosition, LINE_TYPE_FRACTION, verticalPositionAnchor, - horizontalPosition, horizontalPositionAnchor, width, false, Color.BLACK); + horizontalPosition, horizontalPositionAnchor, width, false, Color.BLACK, (float) 1.7777); + } + + /** + * Creates an image cue. + * + * @param bitmap See {@link #bitmap}. + * @param horizontalPosition The position of the horizontal anchor within the viewport, expressed + * as a fraction of the viewport width. + * @param horizontalPositionAnchor The horizontal anchor. One of {@link #ANCHOR_TYPE_START}, + * {@link #ANCHOR_TYPE_MIDDLE}, {@link #ANCHOR_TYPE_END} and {@link #TYPE_UNSET}. + * @param verticalPosition The position of the vertical anchor within the viewport, expressed as a + * fraction of the viewport height. + * @param verticalPositionAnchor The vertical anchor. One of {@link #ANCHOR_TYPE_START}, + * {@link #ANCHOR_TYPE_MIDDLE}, {@link #ANCHOR_TYPE_END} and {@link #TYPE_UNSET}. + * @param width The width of the cue, expressed as a fraction of the viewport width. + * @param sar The Storage Aspect Ratio of the cue, defaults to FHD SAR unless otherwise specified. + */ + public Cue(Bitmap bitmap, float horizontalPosition, @AnchorType int horizontalPositionAnchor, + float verticalPosition, @AnchorType int verticalPositionAnchor, float width, float sar) { + this(null, null, bitmap, verticalPosition, LINE_TYPE_FRACTION, verticalPositionAnchor, + horizontalPosition, horizontalPositionAnchor, width, false, Color.BLACK, sar); } /** @@ -243,12 +268,12 @@ public class Cue { @AnchorType int lineAnchor, float position, @AnchorType int positionAnchor, float size, boolean windowColorSet, int windowColor) { this(text, textAlignment, null, line, lineType, lineAnchor, position, positionAnchor, size, - windowColorSet, windowColor); + windowColorSet, windowColor, 1); } private Cue(CharSequence text, Alignment textAlignment, Bitmap bitmap, float line, @LineType int lineType, @AnchorType int lineAnchor, float position, - @AnchorType int positionAnchor, float size, boolean windowColorSet, int windowColor) { + @AnchorType int positionAnchor, float size, boolean windowColorSet, int windowColor, float sar) { this.text = text; this.textAlignment = textAlignment; this.bitmap = bitmap; @@ -260,6 +285,7 @@ public class Cue { this.size = size; this.windowColorSet = windowColorSet; this.windowColor = windowColor; + this.sar = sar; } } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/text/SubtitleDecoderFactory.java b/library/core/src/main/java/com/google/android/exoplayer2/text/SubtitleDecoderFactory.java index 077fc8848b..b13a267b32 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/text/SubtitleDecoderFactory.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/text/SubtitleDecoderFactory.java @@ -25,6 +25,8 @@ import com.google.android.exoplayer2.text.webvtt.Mp4WebvttDecoder; import com.google.android.exoplayer2.text.webvtt.WebvttDecoder; import com.google.android.exoplayer2.util.MimeTypes; +import java.util.List; + /** * A factory for {@link SubtitleDecoder} instances. */ @@ -83,6 +85,8 @@ public interface SubtitleDecoderFactory { } else if (format.sampleMimeType.equals(MimeTypes.APPLICATION_CEA708)) { return clazz.asSubclass(SubtitleDecoder.class).getConstructor(Integer.TYPE) .newInstance(format.accessibilityChannel); + } else if (format.sampleMimeType.equals(MimeTypes.APPLICATION_DVBSUBS) && format.initializationData != null) { + return clazz.asSubclass(SubtitleDecoder.class).getConstructor(List.class).newInstance(format.initializationData); } else { return clazz.asSubclass(SubtitleDecoder.class).getConstructor().newInstance(); } @@ -112,6 +116,8 @@ public interface SubtitleDecoderFactory { return Class.forName("com.google.android.exoplayer2.text.cea.Cea608Decoder"); case MimeTypes.APPLICATION_CEA708: return Class.forName("com.google.android.exoplayer2.text.cea.Cea708Decoder"); + case MimeTypes.APPLICATION_DVBSUBS: + return Class.forName("com.google.android.exoplayer2.text.dvbsubs.DvbSubsDecoder"); default: return null; } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/text/dvbsubs/DvbSubsDecoder.java b/library/core/src/main/java/com/google/android/exoplayer2/text/dvbsubs/DvbSubsDecoder.java new file mode 100644 index 0000000000..0f4811f339 --- /dev/null +++ b/library/core/src/main/java/com/google/android/exoplayer2/text/dvbsubs/DvbSubsDecoder.java @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.exoplayer2.text.dvbsubs; + +import com.google.android.exoplayer2.text.SimpleSubtitleDecoder; + +import java.util.List; + +public final class DvbSubsDecoder extends SimpleSubtitleDecoder { + private final String TAG = "DVBSubs Decoder"; + + private int subtitilingType; + private int subtitleCompositionPage; + private int subtitleAncillaryPage; + private String subtitleContainer; + + private int flags = 0; + + DvbSubtitlesParser parser; + + public DvbSubsDecoder() { + super("dvbsubs"); + parser = new DvbSubtitlesParser(); + } + + public DvbSubsDecoder(List initializationData) { + super("dvbsubs"); + + byte[] tempByteArray; + + tempByteArray = initializationData.get(0); + subtitilingType = tempByteArray != null ? tempByteArray[0] & 0xFF: -1; + + tempByteArray = initializationData.get(3); + if (tempByteArray != null ) { + subtitleContainer = new String(tempByteArray); + if (subtitleContainer.equals("mkv")) { + flags |= DvbSubtitlesParser.FLAG_PES_STRIPPED_DVBSUB; + } + } + + if ((tempByteArray = initializationData.get(1)) != null) { + this.subtitleCompositionPage = ((tempByteArray[0] & 0xFF) << 8) | (tempByteArray[1] & 0xFF); + if ((tempByteArray = initializationData.get(2)) != null) { + this.subtitleAncillaryPage = ((tempByteArray[0] & 0xFF) << 8) | (tempByteArray[1] & 0xFF); + parser = new DvbSubtitlesParser(this.subtitleCompositionPage, this.subtitleAncillaryPage, flags); + } + } else { + parser = new DvbSubtitlesParser(); + } + + } + + @Override + protected DvbSubsSubtitle decode(byte[] data, int length) { + return new DvbSubsSubtitle(parser.dvbSubsDecode(data, length)); + } +} \ No newline at end of file diff --git a/library/core/src/main/java/com/google/android/exoplayer2/text/dvbsubs/DvbSubsSubtitle.java b/library/core/src/main/java/com/google/android/exoplayer2/text/dvbsubs/DvbSubsSubtitle.java new file mode 100644 index 0000000000..7d845a24dc --- /dev/null +++ b/library/core/src/main/java/com/google/android/exoplayer2/text/dvbsubs/DvbSubsSubtitle.java @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.exoplayer2.text.dvbsubs; + + +import android.graphics.Bitmap; + +import com.google.android.exoplayer2.C; +import com.google.android.exoplayer2.text.Cue; +import com.google.android.exoplayer2.text.Subtitle; + +import java.util.Collections; +import java.util.List; + +final class DvbSubsSubtitle implements Subtitle { + private final List cues; + + public DvbSubsSubtitle(Bitmap data) { + if (data == null) { + this.cues = Collections.emptyList(); + } else { + Cue cue = new Cue(data, 0, Cue.ANCHOR_TYPE_START, 0, Cue.ANCHOR_TYPE_START, 1, (float) data.getWidth()/data.getHeight()); + this.cues = Collections.singletonList(cue); + } + } + + @Override + public int getNextEventTimeIndex(long timeUs) { + return C.INDEX_UNSET; + } + + @Override + public int getEventTimeCount() { + return 1; + } + + @Override + public long getEventTime(int index) { + return 0; + } + + @Override + public List getCues(long timeUs) { + return cues; + } +} \ No newline at end of file diff --git a/library/core/src/main/java/com/google/android/exoplayer2/text/dvbsubs/DvbSubtitlesParser.java b/library/core/src/main/java/com/google/android/exoplayer2/text/dvbsubs/DvbSubtitlesParser.java new file mode 100644 index 0000000000..37a22a3b8b --- /dev/null +++ b/library/core/src/main/java/com/google/android/exoplayer2/text/dvbsubs/DvbSubtitlesParser.java @@ -0,0 +1,1561 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.exoplayer2.text.dvbsubs; + +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.PorterDuff; +import android.graphics.DashPathEffect; +import android.graphics.PorterDuffXfermode; +import android.graphics.Region; +import android.support.annotation.IntDef; +import android.util.Log; +import android.util.SparseArray; + +import com.google.android.exoplayer2.core.BuildConfig; +import com.google.android.exoplayer2.util.ParsableBitArray; + + +public class DvbSubtitlesParser { + + private static final String TAG = "DVBSubs"; + + @IntDef(flag = true, value = {FLAG_PES_STRIPPED_DVBSUB}) + public @interface Flags { + } + public static final int FLAG_PES_STRIPPED_DVBSUB = 1; + + @Flags private final int flags; + + /* List of different SEGMENT TYPES */ + /* According to EN 300-743, table 2 */ + private final static int DVBSUB_ST_PAGE_COMPOSITION = 0x10; + private final static int DVBSUB_ST_REGION_COMPOSITION = 0x11; + private final static int DVBSUB_ST_CLUT_DEFINITION = 0x12; + private final static int DVBSUB_ST_OBJECT_DATA = 0x13; + private final static int DVBSUB_ST_DISPLAY_DEFINITION = 0x14; + private final static int DVBSUB_ST_ENDOFDISPLAY = 0x80; + private final static int DVBSUB_ST_STUFFING = 0xff; + + /* List of different Page Composition Segment state */ + /* According to EN 300-743, 7.2.1 table 3 */ + private final static int DVBSUB_PCS_STATE_NORMAL = 0b00; // Update. Only changed elements. + private final static int DVBSUB_PCS_STATE_ACQUISITION = 0b01; // Refresh. All subtitle elements. + private final static int DVBSUB_PCS_STATE_CHANGE = 0b10; // New. All subtitle elements. + + /* List of different Region Composition Segments CLUT level oc compatibility */ + /* According to EN 300-743, 7.2.1 table 4 */ + private final static int DVBSUB_RCS_CLUT_2 = 0x01; + private final static int DVBSUB_RCS_CLUT_4 = 0x02; + private final static int DVBSUB_RCS_CLUT_8 = 0x03; + + /* List of different Region Composition Segments bit depths */ + /* According to EN 300-743, 7.2.1 table 5 */ + private final static int DVBSUB_RCS_BITDEPTH_2 = 0x01; + private final static int DVBSUB_RCS_BITDEPTH_4 = 0x02; + private final static int DVBSUB_RCS_BITDEPTH_8 = 0x03; + + /* List of different object types in the Region Composition Segment */ + /* According to EN 300-743, table 6 */ + private final static int DVBSUB_OT_BASIC_BITMAP = 0x00; + private final static int DVBSUB_OT_BASIC_CHAR = 0x01; + private final static int DVBSUB_OT_COMPOSITE_STRING = 0x02; + + /* List of different object coding methods in the Object Data Segment */ + /* According to EN 300-743, table 8 */ + private static final int DVBSUB_ODS_PIXEL_CODED = 0x00; + private static final int DVBSUB_ODS_CHAR_CODED = 0x01; + + /* Pixel DATA TYPES */ + /* According to EN 300-743, table 9 */ + private final static int DVBSUB_DT_2BP_CODE_STRING = 0x10; + private final static int DVBSUB_DT_4BP_CODE_STRING = 0x11; + private final static int DVBSUB_DT_8BP_CODE_STRING = 0x12; + private final static int DVBSUB_DT_24_TABLE_DATA = 0x20; + private final static int DVBSUB_DT_28_TABLE_DATA = 0x21; + private final static int DVBSUB_DT_48_TABLE_DATA = 0x22; + private final static int DVBSUB_DT_END_LINE = 0xf0; + + /* Clut mapping tables */ + /* According to EN 300-743, 10.4 10.5 10.6 */ + private byte[] defaultMap24 = {(byte) 0x00, (byte) 0x07, (byte) 0x08, (byte) 0x0f }; + private byte[] defaultMap28 = {(byte) 0x00, (byte) 0x77, (byte) 0x88, (byte) 0xff }; + private byte[] defaultMap48 = {(byte) 0x00, (byte) 0x11, (byte) 0x22, (byte) 0x33, + (byte) 0x44, (byte) 0x55, (byte) 0x66, (byte) 0x77, + (byte) 0x88, (byte) 0x99, (byte) 0xaa, (byte) 0xbb, + (byte) 0xcc, (byte) 0xdd, (byte) 0xee, (byte) 0xff}; + + private ClutDefinition defaultClut = new ClutDefinition(); + + /* FLAGS */ + private final static int DISPLAY_WINDOW_FLAG = 0x01; + + private final static int REGION_FILL_FLAG = 0x01; + + private final static int OBJECT_NON_MODIFYING_COLOUR_FLAG = 0x01; + + /* Constants */ + private static final int UNDEF_PAGE = -1; + private final int NUM_BITMAPS = 4; + + /* instance variables */ + private Paint defaultPaintObject = new Paint(); + private Paint fillRegionPaintObject = new Paint(); + private Paint debugRegionPaintObject = new Paint(); + private Paint debugObjectPaintObject = new Paint(); + private Canvas[] canvasObjects = new Canvas[NUM_BITMAPS]; + private Bitmap[] bitmaps = new Bitmap[NUM_BITMAPS]; + private Bitmap lastValidBitmap; + private int currentBitmap = 0; + private static ParsableBitArray tsStream; + private SubtitleService subtitleService; + + private class SubtitleService { + int subtitlePage; + int ancillaryPage; + boolean newSubtitle = false; + + // subtitle page + DisplayDefinition displayDefinition; + PageComposition pageComposition; + SparseArray regions = new SparseArray(); + SparseArray cluts = new SparseArray(); + SparseArray objects = new SparseArray(); + + // ancillary page + SparseArray ancillaryCluts = new SparseArray(); + SparseArray ancillaryObjects = new SparseArray(); + } + + /* The displays dimensions [7.2.1] */ + private class DisplayDefinition { + int pageId; + int versionNumber; + + int displayWidth = 719; + int displayHeight = 575; + + int flags; + int displayWindowHorizontalPositionMinimum = 0; + int displayWindowHorizontalPositionMaximum = 719; + int displayWindowVerticalPositionMinimum = 0; + int displayWindowVerticalPositionMaximum = 575; + + void updateBitmapResolution() { + for (int i = 0; i < NUM_BITMAPS; i++) { + bitmaps[i] = Bitmap.createBitmap(this.displayWidth + 1, this.displayHeight + 1, + Bitmap.Config.ARGB_8888); + canvasObjects[i] = new Canvas(bitmaps[i]); + } + } + } + + /* The page final static ints the list of regions [7.2.2] */ + private class PageComposition { + int pageId; + int pageTimeOut; /* in seconds */ + int pageVersionNumber; + int pageState; + SparseArray pageRegions = new SparseArray(); + } + + private class PageRegion { + int regionId; + int regionHorizontalAddress; + int regionVerticalAddress; + } + + /* The Region is an area on the image [7.2.3] + * with a list of the object definitions associated and a CLUT */ + private class RegionComposition { + int pageId; + int regionId; + int regionVersionNumber; + int flags; + int regionWidth; + int regionHeight; + int regionLevelOfCompatibility; + int regionDepth; + int clutId; + int region8bitPixelCode; + int region4bitPixelCode; + int region2bitPixelCode; + SparseArray regionObjects = new SparseArray<>(); + } + + /* The entry in the palette CLUT */ + private class ClutEntry { + int clutEntryId; + byte flags; + byte Y; + byte Cr; + byte Cb; + byte T; + + byte A; + byte R; + byte G; + byte B; + int ARGB; + + void clutYCbCrT (int Y, int Cb, int Cr, int T) { + + this.Y = (byte) Y; + this.Cb = (byte) Cb; + this.Cr = (byte) Cr; + this.T = (byte) T; + + int R = (int) (Y + 1.40200 * (Cr - 128)); + int G = (int) (Y - 0.34414 * (Cb - 128) - 0.71414 * (Cr - 128)); + int B = (int) (Y + 1.77200 * (Cb - 128)); + + if (R > 255) this.R = (byte) 255; + else if (R < 0) this.R = 0; + else this.R = (byte) R; + + if (G > 255) this.G = (byte) 255; + else if (G < 0) this.G = 0; + else this.G = (byte) G; + + if (B > 255) this.B = (byte) 255; + else if (B < 0) this.B = 0; + else this.B = (byte) B; + + this.A = (byte) (0xFF - (this.T & 0xFF)); + this.ARGB = + ((this.A & 0xFF) << 24) | + ((this.R & 0xFF) << 16) | + ((this.G & 0xFF) << 8) | + (this.B & 0xFF); + + } + + void clutRGBA (int R, int G, int B, int A) { + + this.A = (byte) A; + this.R = (byte) R; + this.G = (byte) G; + this.B = (byte) B; + + this.ARGB = + ((A & 0xFF) << 24) | + ((R & 0xFF) << 16) | + ((G & 0xFF) << 8) | + (B & 0xFF); + + int y = (int) ( 0.299000 * R + 0.587000 * G + 0.114000 * B); + int Cb = 128 + (int) (-0.168736 * R + -0.331264 * G + 0.500000 * B); + int Cr = 128 + (int) ( 0.500000 * R + -0.418688 * G + -0.081312 * B); + + if (y > 255) this.Y = (byte) 255; + else if (y < 0) this.Y = 0; + else this.Y = (byte) y; + + if (Cb > 255) this.Cb = (byte) 255; + else if (Cb < 0) this.Cb = 0; + else this.Cb = (byte) Cb; + + if (Cr > 255) this.Cr = (byte) 255; + else if (Cr < 0) this.Cr = 0; + else this.Cr = (byte) Cr; + + this.T = (byte) (0xFF - (this.A & 0xFF)); + } + } + + /* Colours to be applied in a CLUT family [7.2.4] */ + private class ClutDefinition { + int pageId; + int clutId; + int clutVersionNumber; + ClutEntry[] clutEntries2bit; + ClutEntry[] clutEntries4bit; + ClutEntry[] clutEntries8bit; + + ClutEntry[] generateDefault2bitClut() { + ClutEntry[] entries = new ClutEntry[4]; + + entries[0] = new ClutEntry(); + entries[0].clutRGBA(0x00, 0x00, 0x00, 0x00); + entries[1] = new ClutEntry(); + entries[1].clutRGBA(0xFF, 0xFF, 0xFF, 0xFF); + entries[2] = new ClutEntry(); + entries[2].clutRGBA(0x00, 0x00, 0x00, 0xFF); + entries[3] = new ClutEntry(); + entries[3].clutRGBA(0x7F, 0x7F, 0x7F, 0xFF); + + return entries; + } + + ClutEntry[] generateDefault4bitClut() { + ClutEntry[] entries = new ClutEntry[16]; + + entries[0] = new ClutEntry(); + entries[0].clutRGBA(0x00, 0x00, 0x00, 0x00); + + int i = 15; + while (i > 0) { + entries[i] = new ClutEntry(); + if (i < 8) { + entries[i].clutRGBA( + ((i & 0x01) != 0 ? 0xFF : 0x00), + ((i & 0x02) != 0 ? 0xFF : 0x00), + ((i & 0x04) != 0 ? 0xFF : 0x00), + 0xFF); + } else { + entries[i].clutRGBA( + ((i & 0x01) != 0 ? 0x7F : 0x00), + ((i & 0x02) != 0 ? 0x7F : 0x00), + ((i & 0x04) != 0 ? 0x7F : 0x00), + 0xFF); + } + + i--; + } + + return entries; + } + + ClutEntry[] generateDefault8bitClut() { + ClutEntry[] entries = new ClutEntry[256]; + + entries[0] = new ClutEntry(); + entries[0].clutRGBA(0x00, 0x00, 0x00, 0x00); + + int i = 255; + while (i > 0) { + entries[i] = new ClutEntry(); + if (i < 8) { + entries[i].clutRGBA( + ((i & 0x01) != 0 ? 0xFF : 0x00), + ((i & 0x02) != 0 ? 0xFF : 0x00), + ((i & 0x04) != 0 ? 0xFF : 0x00), + 0x3F); + } else { + switch (i & 0x88) { + case 0x00: + entries[i].clutRGBA( + (((i & 0x01) != 0 ? 0x55 : 0x00) + ((i & 0x10) != 0 ? 0xAA : 0x00)), + (((i & 0x02) != 0 ? 0x55 : 0x00) + ((i & 0x20) != 0 ? 0xAA : 0x00)), + (((i & 0x04) != 0 ? 0x55 : 0x00) + ((i & 0x40) != 0 ? 0xAA : 0x00)), + 0xFF); + break; + case 0x08: + entries[i].clutRGBA( + (((i & 0x01) != 0 ? 0x55 : 0x00) + ((i & 0x10) != 0 ? 0xAA : 0x00)), + (((i & 0x02) != 0 ? 0x55 : 0x00) + ((i & 0x20) != 0 ? 0xAA : 0x00)), + (((i & 0x04) != 0 ? 0x55 : 0x00) + ((i & 0x40) != 0 ? 0xAA : 0x00)), + 0x7F); + break; + case 0x80: + entries[i].clutRGBA( + (127 + ((i & 0x01) != 0 ? 0x2B : 0x00) + ((i & 0x10) != 0 ? 0x55 : 0x00)), + (127 + ((i & 0x02) != 0 ? 0x2B : 0x00) + ((i & 0x20) != 0 ? 0x55 : 0x00)), + (127 + ((i & 0x04) != 0 ? 0x2B : 0x00) + ((i & 0x40) != 0 ? 0x55 : 0x00)), + 0xFF); + break; + case 0x88: + entries[i].clutRGBA( + (((i & 0x01) != 0 ? 0x2B : 0x00) + ((i & 0x10) != 0 ? 0x55 : 0x00)), + (((i & 0x02) != 0 ? 0x2B : 0x00) + ((i & 0x20) != 0 ? 0x55 : 0x00)), + (((i & 0x04) != 0 ? 0x2B : 0x00) + ((i & 0x40) != 0 ? 0x55 : 0x00)), + 0xFF); + break; + } + + } + + i--; + } + + return entries; + } + + ClutDefinition () { + clutEntries2bit = generateDefault2bitClut(); + clutEntries4bit = generateDefault4bitClut(); + clutEntries8bit = generateDefault8bitClut(); + } + + } + + /* The object data segment contains the data of an object [7.2.5] + */ + private class ObjectData { + int pageId; + int objectId; + int objectVersionNumber; + int objectCodingMethod; + byte flags; + int topFieldDataLength; + byte[] topFieldData; + int bottomFieldDataLength; + byte[] bottomFieldData; + int numberOfCodes; + } + + private class RegionObject { + int objectId; + int objectType; + int objectProvider; + int objectHorizontalPosition; + int objectVerticalPosition; + int foregroundPixelCode; + int backgroundPixelCode; + } + + DvbSubtitlesParser() { + this(1); + } + + DvbSubtitlesParser(int subtitlePge) { + this(subtitlePge, UNDEF_PAGE); + } + + DvbSubtitlesParser(int subtitlePage, int ancillaryPage) { + this(subtitlePage, ancillaryPage, 0); + } + + DvbSubtitlesParser(int subtitlePage, int ancillaryPage, @Flags int flags) { + this.subtitleService = new SubtitleService(); + this.flags = flags; + + this.defaultPaintObject.setStyle(Paint.Style.FILL_AND_STROKE); + this.defaultPaintObject.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC)); + this.defaultPaintObject.setPathEffect(null); + + this.fillRegionPaintObject.setStyle(Paint.Style.FILL); + this.fillRegionPaintObject.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC)); + this.fillRegionPaintObject.setPathEffect(null); + + this.debugRegionPaintObject.setColor(0xff00ff00); + this.debugRegionPaintObject.setStyle(Paint.Style.STROKE); + this.debugRegionPaintObject.setPathEffect(new DashPathEffect(new float[] {2,2}, 0)); + + this.debugObjectPaintObject.setColor(0xffff0000); + this.debugObjectPaintObject.setStyle(Paint.Style.STROKE); + this.debugObjectPaintObject.setPathEffect(new DashPathEffect(new float[]{10, 20}, 0)); + + this.subtitleService.subtitlePage = subtitlePage; + this.subtitleService.ancillaryPage = ancillaryPage; + + this.subtitleService.displayDefinition = new DisplayDefinition(); + this.subtitleService.displayDefinition.updateBitmapResolution(); + + this.currentBitmap = 0; + this.lastValidBitmap = bitmaps[currentBitmap]; + } + + private void parseSubtitlingSegment() { + + /* Parse subtitling segment. ETSI EN 300 743 7.2 + + SYNTAX SIZE SEMANTICS + --------------------------------------- ---- ------------------------------------------ + Subtitling_segment() { + sync_byte 8 An 8-bit field that shall be coded with the value '0000 1111' + segment_type 8 Indicates the type of data contained in the segment data field + page_id 16 Identifies the subtitle service of the data contained in this subtitling_segment + segment_length 16 Number of bytes contained in the segment_data_field + segment_data_field() This is the payload of the segment + + */ + + int pageId, segmentId, segmentLength; + segmentId = tsStream.readBits(8); + switch (segmentId) { + case DVBSUB_ST_DISPLAY_DEFINITION: + if (BuildConfig.DEBUG) Log.d(TAG, " Parse Display Definition segment."); + DisplayDefinition tempDisplay = parseDisplayDefinitionSegment(); + if (tempDisplay != null && tempDisplay.pageId == subtitleService.subtitlePage) { + if (tempDisplay.displayWidth != subtitleService.displayDefinition.displayWidth || + tempDisplay.displayHeight != subtitleService.displayDefinition.displayHeight || + tempDisplay.displayWindowHorizontalPositionMaximum != subtitleService.displayDefinition.displayWindowHorizontalPositionMaximum || + tempDisplay.displayWindowHorizontalPositionMinimum != subtitleService.displayDefinition.displayWindowHorizontalPositionMinimum || + tempDisplay.displayWindowVerticalPositionMaximum != subtitleService.displayDefinition.displayWindowVerticalPositionMaximum || + tempDisplay.displayWindowVerticalPositionMinimum != subtitleService.displayDefinition.displayWindowVerticalPositionMinimum || + tempDisplay.flags != subtitleService.displayDefinition.flags) { + subtitleService.displayDefinition = tempDisplay; + subtitleService.displayDefinition.updateBitmapResolution(); + lastValidBitmap = bitmaps[currentBitmap]; + } else { + subtitleService.displayDefinition.versionNumber = tempDisplay.versionNumber; + } + + if (BuildConfig.DEBUG) Log.d(TAG + "/DDS", " [versionNumber] = " + tempDisplay.versionNumber + + " [width/height] = " + (tempDisplay.displayWidth + 1) + "/" + (tempDisplay.displayHeight + 1) + + " Window[minX/minY/maxX/maxY] = " + tempDisplay.displayWindowHorizontalPositionMinimum + + "/" + tempDisplay.displayWindowVerticalPositionMinimum + + "/" + tempDisplay.displayWindowHorizontalPositionMaximum + + "/" + tempDisplay.displayWindowVerticalPositionMaximum + ); + } + break; + case DVBSUB_ST_PAGE_COMPOSITION: + if (BuildConfig.DEBUG) Log.d(TAG, " Parse Page Composition segment."); + PageComposition tempPage = parsePageCompositionSegment(); + if (tempPage != null && tempPage.pageId == subtitleService.subtitlePage) { + if (tempPage.pageState == DVBSUB_PCS_STATE_NORMAL && subtitleService.pageComposition == null) + break; + subtitleService.pageComposition = tempPage; + } + break; + case DVBSUB_ST_REGION_COMPOSITION: + if (BuildConfig.DEBUG) Log.d(TAG, " Parse Region Composition segment."); + RegionComposition tempRegionComposition = parseRegionCompositionSegment(); + if (tempRegionComposition != null && tempRegionComposition.pageId == subtitleService.subtitlePage) { + subtitleService.regions.put(tempRegionComposition.regionId, tempRegionComposition); + } + break; + case DVBSUB_ST_CLUT_DEFINITION: + if (BuildConfig.DEBUG) Log.d(TAG, " Parse Clut Definition segment."); + ClutDefinition tempClutDefinition = parseClutDefinitionSegment(); + if (tempClutDefinition != null ) { + if (tempClutDefinition.pageId == subtitleService.subtitlePage) { + subtitleService.cluts.put(tempClutDefinition.clutId, tempClutDefinition); + } else if (tempClutDefinition.pageId == subtitleService.ancillaryPage) { + subtitleService.ancillaryCluts.put(tempClutDefinition.clutId, tempClutDefinition); + } + } + break; + case DVBSUB_ST_OBJECT_DATA: + if (BuildConfig.DEBUG) Log.d(TAG, " Parse Object Data segment."); + ObjectData tempObjectData = parseObjectDataSegment(); + if (tempObjectData != null) { + if (tempObjectData.pageId == subtitleService.subtitlePage) { + subtitleService.objects.put(tempObjectData.objectId, tempObjectData); + } else if (tempObjectData.pageId == subtitleService.ancillaryPage) { + subtitleService.ancillaryObjects.put(tempObjectData.objectId, tempObjectData); + } + } + break; + case DVBSUB_ST_ENDOFDISPLAY: + pageId = tsStream.readBits(16); + segmentLength = tsStream.readBits(16); + if (BuildConfig.DEBUG) Log.d(TAG, "pageId " + pageId + "end of display size = " + segmentLength); + tsStream.skipBits(segmentLength * 8); + break; + case DVBSUB_ST_STUFFING: + pageId = tsStream.readBits(16); + segmentLength = tsStream.readBits(16); + if (BuildConfig.DEBUG) Log.d(TAG, "pageId " + pageId + "stuffing size = " + segmentLength); + tsStream.skipBits(segmentLength * 8); + break; + default: + break; + } + } + + private DisplayDefinition parseDisplayDefinitionSegment() { + + /* Parse display definition segment. ETSI EN 300 743 7.2.1 + + SYNTAX SIZE SEMANTICS + --------------------------------------- ---- ------------------------------------------ + display_definition_segment(){ + sync_byte 8 An 8-bit field that shall be coded with the value '0000 1111' + segment_type 8 Indicates the type of data contained in the segment data field + page_id 16 Identifies the subtitle service of the data contained in this subtitling_segment + segment_length 16 Number of bytes contained in the segment_data_field + dds_version_number 4 Incremented when any of the contents of this segment change + display_window_flag 1 if "1" display the subtitle in the defined window + reserved 3 + display_width 16 Specifies the maximum horizontal width of the display in pixels minus 1 + display_height 16 Specifies the maximum vertical height of the display in lines minus 1 + if (display_window_flag == 1) { With origin in the top-left of the screen: + display_window_horizontal_position_minimum + 16 Specifies the left-hand most pixel of this DVB subtitle display set + display_window_horizontal_position_maximum + 16 Specifies the right-hand most pixel of this DVB subtitle display set + display_window_vertical_position_minimum + 16 Specifies the upper most line of this DVB subtitle display set + display_window_vertical_position_maximum + 16 Specifies the bottom line of this DVB subtitle display set + } + } + */ + + DisplayDefinition display = new DisplayDefinition(); + + display.pageId = tsStream.readBits(16); + tsStream.skipBits(16); + display.versionNumber = tsStream.readBits(4); + if (tsStream.readBits(1) == 1) { + display.flags |= DISPLAY_WINDOW_FLAG; + } + tsStream.skipBits(3); + display.displayWidth = tsStream.readBits(16); + display.displayHeight = tsStream.readBits(16); + if ((display.flags & DISPLAY_WINDOW_FLAG) != 0) { + display.displayWindowHorizontalPositionMinimum = tsStream.readBits(16); + display.displayWindowHorizontalPositionMaximum = tsStream.readBits(16); + display.displayWindowVerticalPositionMinimum = tsStream.readBits(16); + display.displayWindowVerticalPositionMaximum = tsStream.readBits(16); + } else { + display.displayWindowHorizontalPositionMinimum = 0; + display.displayWindowHorizontalPositionMaximum = display.displayWidth; + display.displayWindowVerticalPositionMinimum = 0; + display.displayWindowVerticalPositionMaximum = display.displayHeight; + } + + return display; + } + + private PageComposition parsePageCompositionSegment() { + + /* Parse page composition segment. ETSI EN 300 743 7.2.2 + + SYNTAX SIZE SEMANTICS + --------------------------------------- ---- ------------------------------------------ + page_composition_segment() { + sync_byte 8 + segment_type 8 + page_id 16 + segment_length 16 + page_time_out 8 The period after the page instace should be erased + page_version_number 4 Incremented when any of the contents of this segment change + page_state 2 The status of the subtitling page instance + reserved 2 + while (processed_length < segment_length) { Page region list + region_id 8 Uniquely identifies a region within a page + reserved 8 + region_horizontal_address 16 Horizontal address of the top left pixel of this region + region_vertical_address 16 Vertical address of the top line of this region + } + } + */ + + PageComposition page = new PageComposition(); + + page.pageId = tsStream.readBits(16); + int remainingSegmentLength = tsStream.readBits(16); + page.pageTimeOut = tsStream.readBits(8); + page.pageVersionNumber = tsStream.readBits(4); + page.pageState = tsStream.readBits(2); + tsStream.skipBits(2); + + if (page.pageState == DVBSUB_PCS_STATE_NORMAL && + subtitleService.pageComposition != null && + subtitleService.pageComposition.pageId == page.pageId && + (subtitleService.pageComposition.pageVersionNumber + 1) % 16 == page.pageVersionNumber) { + //page.pageRegions = subtitleService.pageComposition.pageRegions; + + if (BuildConfig.DEBUG) { + Log.d(TAG, " Updated Page Composition. pageId: " + page.pageId + + " version: " + page.pageVersionNumber + + " timeout: " + page.pageTimeOut + ); + } + + } else if (subtitleService.subtitlePage == page.pageId) { + if (BuildConfig.DEBUG) { + if (page.pageState == DVBSUB_PCS_STATE_NORMAL) { + Log.d(TAG, " FAILED Page Composition update. pageId: " + page.pageId + + " Version(Old/New): " + (subtitleService.pageComposition != null ? subtitleService.pageComposition.pageVersionNumber : "NaN") + "/" + page.pageVersionNumber); + } + } + + subtitleService.newSubtitle = false; + subtitleService.pageComposition = null; + subtitleService.regions = new SparseArray<>(); + subtitleService.cluts = new SparseArray<>(); + subtitleService.objects = new SparseArray<>(); + + if (BuildConfig.DEBUG) { + if (page.pageState != DVBSUB_PCS_STATE_NORMAL) { + Log.d(TAG, " New Page Composition. pageId: " + page.pageId + + " version: " + page.pageVersionNumber + + " timeout: " + page.pageTimeOut + ); + } + } + } + + remainingSegmentLength -= 2; + while (remainingSegmentLength > 0) { + PageRegion region = new PageRegion(); + + region.regionId = tsStream.readBits(8); + tsStream.skipBits(8); + region.regionHorizontalAddress = tsStream.readBits(16); + region.regionVerticalAddress = tsStream.readBits(16); + + if (BuildConfig.DEBUG) { + Log.d(TAG, " " + + (page.pageRegions.get(region.regionId) == null ? "New" : "Upd.") + + " Page Region. regionId: " + region.regionId + + " (x/y): (" + region.regionHorizontalAddress + "/" + region.regionVerticalAddress + ")"); + } + + page.pageRegions.put(region.regionId, region); + + remainingSegmentLength -= 6; + } + + return page; + } + + private RegionComposition parseRegionCompositionSegment() { + + /* Parse region composition segment. ETSI EN 300 743 7.2.3 + + SYNTAX SIZE SEMANTICS + --------------------------------------- ---- ------------------------------------------ + region_composition_segment() { + sync_byte 8 + segment_type 8 + page_id 16 + segment_length 16 + region_id 8 Uniquely identifies the region + region_version_number 4 Indicates the version of this region + region_fill_flag 1 If set the region is to be filled region_n-bit_pixel_code clut index + reserved 3 + region_width 16 Specifies the horizontal length of this region + region_height 16 Specifies the vertical length of the region + region_level_of_compatibility 3 Code that indicates the minimum bithdepth of CLUT + region_depth 3 Identifies the intended pixel depth for this region + reserved 2 + CLUT_id 8 Identifies the family of CLUTs that applies to this region + region_8-bit_pixel_code 8 Specifies the entry of the applied 8-bit CLUT as background colour + region_4-bit_pixel-code 4 Specifies the entry of the applied 4-bit CLUT as background colour + region_2-bit_pixel-code 2 Specifies the entry of the applied 2-bit CLUT as background colour + reserved 2 + while (processed_length < segment_length) { list of region objects + object_id 16 Identifies an object that is shown in the region + object_type 2 Identifies the type of object + object_provider_flag 2 How this object is provided + object_horizontal_position 12 Specifies the horizontal position of the top left pixel of this object + reserved 4 + object_vertical_position 12 Specifies the vertical position of the top left pixel of this object + if (object_type ==0x01 or object_type == 0x02){ UNSUPPORTED + foreground_pixel_code 8 + background_pixel_code 8 + } + } + } + */ + + RegionComposition region = new RegionComposition(); + + region.pageId = tsStream.readBits(16); + int remainingSegmentLength = tsStream.readBits(16); + region.regionId = tsStream.readBits(8); + region.regionVersionNumber = tsStream.readBits(4); + if (tsStream.readBits(1) == 1) { + region.flags |= REGION_FILL_FLAG; + } + tsStream.skipBits(3); + region.regionWidth = tsStream.readBits(16); + region.regionHeight = tsStream.readBits(16); + region.regionLevelOfCompatibility = tsStream.readBits(3); + region.regionDepth = tsStream.readBits(3); + tsStream.skipBits(2); + region.clutId = tsStream.readBits(8); + tsStream.skipBits(16); + + if (BuildConfig.DEBUG) { + Log.d(TAG, " New Region Composition. regionId: " + region.regionId + + " (w/h): (" + region.regionWidth + "/" + region.regionHeight + ")"); + } + + int arrayIndex = 0; // index by an incremental counter to allow repeating objects in one region + + if (subtitleService.pageComposition != null && subtitleService.pageComposition.pageId == region.pageId && + subtitleService.pageComposition.pageState == DVBSUB_PCS_STATE_NORMAL) { + RegionComposition tempRegion = subtitleService.regions.get(region.regionId); + if (tempRegion != null) { + region.regionObjects = tempRegion.regionObjects; + arrayIndex = region.regionObjects.size(); + } + } + + remainingSegmentLength -= 10; + RegionObject object; + while (remainingSegmentLength > 0) { + object = new RegionObject(); + + object.objectId = tsStream.readBits(16); + object.objectType = tsStream.readBits(2); + object.objectProvider = tsStream.readBits(2); + object.objectHorizontalPosition = tsStream.readBits(12); + tsStream.skipBits(4); + object.objectVerticalPosition = tsStream.readBits(12); + remainingSegmentLength -= 6; + + if (object.objectType == 0x01 || object.objectType == 0x02) { // Only seems to affect to char subtitles + object.foregroundPixelCode = tsStream.readBits(8); + object.backgroundPixelCode = tsStream.readBits(8); + remainingSegmentLength -= 2; + } + + if (BuildConfig.DEBUG) { + Log.d(TAG, " New Region Object[" + arrayIndex + "]." + + " objectId: " + object.objectId + + " (x/y): (" + object.objectHorizontalPosition + "/" + object.objectVerticalPosition + ")"); + } + + region.regionObjects.put(arrayIndex++, object); + } + + + return region; + } + + private ClutDefinition parseClutDefinitionSegment() { + + /* Parse CLUT definition segment. ETSI EN 300 743 7.2.4 + + SYNTAX SIZE SEMANTICS + --------------------------------------- ---- ------------------------------------------ + CLUT_definition_segment() { + sync_byte 8 + segment_type 8 + page_id 16 + segment_length 16 + CLUT-id 8 Uniquely identifies within a page the CLUT family + CLUT_version_number 4 Indicates the version of this segment data + reserved 4 + while (processed_length < segment_length) { Clut entries list + CLUT_entry_id 8 Specifies the entry number of the CLUT + 2-bit/entry_CLUT_flag 1 Indicates this entry is to be loaded into the 2-bit/entry CLUT + 4-bit/entry_CLUT_flag 1 Indicates this entry is to be loaded into the 4-bit/entry CLUT + 8-bit/entry_CLUT_flag 1 Indicates this entry is to be loaded into the 8-bit/entry CLUT + reserved 4 + full_range_flag 1 Indicates that the Y_value, Cr_value, Cb_value and T_value + fields have the full 8-bit resolution + if full_range_flag =='1' { + Y-value 8 The Y value for this CLUT entry. + Cr-value 8 The Cr value for this CLUT entry. + Cb-value 8 The Cb value for this CLUT entry. + T-value 8 The Transparency value for this CLUT entry. 0 = no transparency + } else { + Y-value 6 The Y value for this CLUT entry. + Cr-value 4 The Cr value for this CLUT entry. + Cb-value 4 The Cb value for this CLUT entry. + T-value 2 The Transparency value for this CLUT entry. 0 = no transparency + } + } + } + */ + + ClutDefinition clut = new ClutDefinition(); + clut.pageId = tsStream.readBits(16); + int remainingSegmentLength = tsStream.readBits(16); + clut.clutId = tsStream.readBits(8); + clut.clutVersionNumber = tsStream.readBits(4); + tsStream.skipBits(4); + + remainingSegmentLength -= 2; + ClutEntry entry; + int Y, Cb, Cr, T; + int entryId, entryFlags; + while (remainingSegmentLength > 0) { + entryId = tsStream.readBits(8); + entryFlags = tsStream.readBits(8); + + if ((entryFlags & 0x80) != 0) { + entry = clut.clutEntries2bit[entryId]; + } else if ((entryFlags & 0x40) != 0) { + entry = clut.clutEntries4bit[entryId]; + } else { + entry = clut.clutEntries8bit[entryId]; + } + + entry.flags = (byte) (entryFlags & 0xE1); + if ((entry.flags & 0x01) != 0) { + Y = tsStream.readBits(8); + Cr = tsStream.readBits(8); + Cb = tsStream.readBits(8); + T = tsStream.readBits(8); + remainingSegmentLength -= 6; + } else { + Y = tsStream.readBits(6) << 2; + Cr = tsStream.readBits(4) << 4; + Cb = tsStream.readBits(4) << 4; + T = tsStream.readBits(2) << 6; + remainingSegmentLength -= 4; + } + + if (Y == 0x00) { + Cr = 0x00; + Cb = 0x00; + T = 0xFF; + } + + entry.clutYCbCrT(Y, Cb, Cr, T); + } + return clut; + } + + private ObjectData parseObjectDataSegment() { + + /* Parse object data segment. ETSI EN 300 743 7.2.5 + + SYNTAX SIZE SEMANTICS + --------------------------------------- ---- ------------------------------------------ + object_data_segment() { + sync_byte 8 + segment_type 8 + page_id 16 + segment_length 16 + object_id 16 Uniquely identifies within the page the object + object_version_number 4 Indicates the version of this segment data + object_coding_method 2 Specifies the method used to code the object + non_modifying_colour_flag 1 Indicates that the CLUT entry value '1' is a non modifying colour + reserved 1 + if (object_coding_method == '00'){ + top_field_data_block_length 16 Specifies the number of bytes contained in his pixel-data_sub-blocks + bottom_field_data_block_length 16 Specifies the number of bytes contained in his pixel-data_sub-blocks + while(processed_length 0) { + switch (data.readBits(8)) { + case DVBSUB_DT_2BP_CODE_STRING: + if (regionDepth == DVBSUB_RCS_BITDEPTH_8) { + clutMapTable = clutMapTable28 == null ? defaultMap28 : clutMapTable28; + } else if (regionDepth == DVBSUB_RCS_BITDEPTH_4) { + clutMapTable = clutMapTable24 == null ? defaultMap24 : clutMapTable24; + } else { + clutMapTable = null; + } + column += dvbSub2BitPixelCodeString(data, lineHeight, clutEntries, clutMapTable, + column, line, (object.flags & OBJECT_NON_MODIFYING_COLOUR_FLAG) == 0); + if ((i = data.getPosition() % 8) != 0) { + data.skipBits(7 - i + 1); + } + break; + case DVBSUB_DT_4BP_CODE_STRING: + if (regionDepth == DVBSUB_RCS_BITDEPTH_8) + clutMapTable = clutMapTable48 == null ? defaultMap48 : clutMapTable48; + else + clutMapTable = null; + column += dvbSub4BitPixelCodeString(data, lineHeight, clutEntries, clutMapTable, + column, line, (object.flags & OBJECT_NON_MODIFYING_COLOUR_FLAG) == 0); + if ((i = data.getPosition() % 8) != 0) { + data.skipBits(7 - i + 1); + } + break; + case DVBSUB_DT_8BP_CODE_STRING: + column += dvbSub8BitPixelCodeString(data, lineHeight, clutEntries, null, + column, line, (object.flags & OBJECT_NON_MODIFYING_COLOUR_FLAG) == 0); + break; + case DVBSUB_DT_24_TABLE_DATA: + clutMapTable24 = new byte[4]; + for (i = 0; i < 4; i++) { + clutMapTable24[i] = (byte) data.readBits(4); + } + break; + case DVBSUB_DT_28_TABLE_DATA: + clutMapTable28 = new byte[4]; + for (i = 0; i < 4; i++) { + clutMapTable28[i] = (byte) data.readBits(8); + } + break; + case DVBSUB_DT_48_TABLE_DATA: + clutMapTable48 = new byte[16]; + for (i = 0; i < 4; i++) { + clutMapTable48[i] = (byte) data.readBits(8); + } + break; + case DVBSUB_DT_END_LINE: + column = horizontalAddress; + line += 2; + break; + default: + break; + } + } + field += lineHeight; + } + + return null; + } + + private int dvbSub2BitPixelCodeString(ParsableBitArray data, int lineHeigth, + ClutEntry[] clutEntries, byte[] clutMapTable, + int column, int line, boolean paint) { + + /* Parse 2-bit/pixel code string. ETSI EN 300 743 7.2.5.2 + + SYNTAX SIZE + --------------------------------------- ---- + 2-bit/pixel_code_string() { + if (nextbits() != '00') { + 2-bit_pixel-code 2 + } else { + 2-bit_zero 2 + switch_1 1 bslbf + if (switch_1 == '1') { + run_length_3-10 3 + 2-bit_pixel-code 2 + } else { + switch_2 1 + if (switch_2 == '0') { + switch_3 2 + if (switch_3 == '10') { + run_length_12-27 4 + 2-bit_pixel-code 2 + } + if (switch_3 == '11') { + run_length_29-284 8 + 2-bit_pixel-code 2 + } + } + } + } + } + */ + + int savedColumn = column, peek, runLength, clutIdx = 0x00, colour; + boolean endOfPixelCodeString = false; + + while (!endOfPixelCodeString) { + runLength = 0; + peek = data.readBits(2); + if (peek != 0x00) { + runLength = 1; + clutIdx = peek; + } else { + peek = data.readBits(1); + if (peek == 0x01) { + runLength = 3 + data.readBits(3); + clutIdx = data.readBits(2); + } else { + peek = data.readBits(1); + if (peek == 0x00) { + peek = data.readBits(2); + switch (peek) { + case 0x00: + endOfPixelCodeString = true; + break; + case 0x01: + runLength = 2; + clutIdx = 0x00; + break; + case 0x02: + runLength = 12 + data.readBits(4); + clutIdx = data.readBits(2); + break; + case 0x03: + runLength = 29 + data.readBits(8); + clutIdx = data.readBits(2); + break; + } + } + } + } + + if (runLength != 0 && paint) { + colour = clutMapTable != null ? clutEntries[clutMapTable[clutIdx]].ARGB + : clutEntries[clutIdx].ARGB; + defaultPaintObject.setColor(colour); + canvasObjects[currentBitmap].drawRect( + column, line, column + runLength, line + lineHeigth, defaultPaintObject); + } + + column += runLength; + } + + return column - savedColumn; + } + + private int dvbSub4BitPixelCodeString(ParsableBitArray data, int lineHeigth, + ClutEntry[] clutEntries, byte[] clutMapTable, + int column, int line, boolean paint) { + + /* Parse 4-bit/pixel code string. ETSI EN 300 743 7.2.5.2 + + SYNTAX SIZE + --------------------------------------- ---- + 4-bit/pixel_code_string() { + if (nextbits() != '0000') { + 4-bit_pixel-code 4 + } else { + 4-bit_zero 4 + switch_1 1 + if (switch_1 == '0') { + if (nextbits() != '000') + run_length_3-9 3 + else + end_of_string_signal 3 + } else { + switch_2 1 + if (switch_2 == '0') { + run_length_4-7 2 + 4-bit_pixel-code 4 + } else { + switch_3 2 + if (switch_3 == '10') { + run_length_9-24 4 + 4-bit_pixel-code 4 + } + if (switch_3 == '11') { + run_length_25-280 8 + 4-bit_pixel-code 4 + } + } + } + } + } + */ + + int savedColumn = column, peek, runLength, clutIdx = 0x00, colour; + boolean endOfPixelCodeString = false; + + while (!endOfPixelCodeString) { + runLength = 0; + peek = data.readBits(4); + if (peek != 0x00) { + runLength = 1; + clutIdx = peek; + } else { + peek = data.readBits(1); + if (peek == 0x00) { + peek = data.readBits(3); + if (peek != 0x00) { + runLength = 2 + peek; + clutIdx = 0x00; + } else { + endOfPixelCodeString = true; + } + } else { + peek = data.readBits(1); + if (peek == 0x00) { + runLength = 4 + data.readBits(2); + clutIdx = data.readBits(4); + } else { + peek = data.readBits(2); + switch (peek) { + case 0x00: + runLength = 1; + clutIdx = 0x00; + break; + case 0x01: + runLength = 2; + clutIdx = 0x00; + break; + case 0x02: + runLength = 9 + data.readBits(4); + clutIdx = data.readBits(4); + break; + case 0x03: + runLength = 25 + data.readBits(8); + clutIdx = data.readBits(4); + break; + } + } + } + } + + if (runLength != 0 && paint) { + colour = clutMapTable != null ? clutEntries[clutMapTable[clutIdx]].ARGB + : clutEntries[clutIdx].ARGB; + defaultPaintObject.setColor(colour); + canvasObjects[currentBitmap].drawRect( + column, line, column + runLength, line + lineHeigth, defaultPaintObject); + } + + column += runLength; + } + + return column - savedColumn; + } + + private int dvbSub8BitPixelCodeString(ParsableBitArray data, int lineHeigth, + ClutEntry[] clutEntries, byte[] clutMapTable, + int column, int line, boolean paint) { + + /* Parse 8-bit/pixel code string. ETSI EN 300 743 7.2.5.2 + + SYNTAX SIZE + --------------------------------------- ---- + + 8-bit/pixel_code_string() { + if (nextbits() != '0000 0000') { + 8-bit_pixel-code 8 + } else { + 8-bit_zero 8 + switch_1 1 + if switch_1 == '0' { + if nextbits() != '000 0000' + run_length_1-127 7 + else + end_of_string_signal 7 + } else { + run_length_3-127 7 + 8-bit_pixel-code 8 + } + } + } + */ + + int savedColumn = column, peek, runLength, clutIdx = 0x00, colour; + boolean endOfPixelCodeString = false; + + while (!endOfPixelCodeString) { + runLength = 0; + peek = data.readBits(8); + if (peek != 0x00) { + runLength = 1; + clutIdx = peek; + } else { + peek = data.readBits(1); + if (peek == 0x00) { + peek = data.readBits(7); + if (peek != 0x00) { + runLength = peek; + clutIdx = 0x00; + } else { + endOfPixelCodeString = true; + } + } else { + runLength = data.readBits(7); + clutIdx = data.readBits(8); + } + } + + if (runLength != 0 && paint) { + colour = clutMapTable != null ? clutEntries[clutMapTable[clutIdx]].ARGB + : clutEntries[clutIdx].ARGB; + defaultPaintObject.setColor(colour); + canvasObjects[currentBitmap].drawRect( + column, line, column + runLength, line + lineHeigth, defaultPaintObject); + } + + column += runLength; + } + + return column - savedColumn; + } + + public Bitmap dvbSubsDecode(byte[] input, int inputSize) { + + /* process PES PACKET. ETSI EN 300 743 7.1 + + SYNTAX SIZE SEMANTICS + --------------------------------------- ---- ------------------------------------------ + PES_data_field() { + data_identifier 8 For DVB subtitle streams it shall be 0x20 + subtitle_stream_id 8 For DVB subtitling stream it shall be 0x00 + while nextbits() == '0000 1111' { + Subtitling_segment() + } + end_of_PES_data_field_marker 8 An 8-bit field with fixed contents '1111 1111' + + */ + + if (input != null) { + tsStream = new ParsableBitArray(input, inputSize); + } else { + return null; + } + if (!isSet(FLAG_PES_STRIPPED_DVBSUB)) { + if (tsStream.readBits(8) != 0x20) { // data_identifier + return null; + } + if (tsStream.readBits(8) != 0x00) { // subtitle_stream_id + return null; + } + } + + if (BuildConfig.DEBUG) Log.d(TAG,"New PES subtitle packet."); + + int sync = tsStream.readBits(8); + // test for segment Sync Byte and account for possible additional wordalign byte in Object data segment + while (sync == 0x0f || (sync == 0x00 && (sync = tsStream.readBits(8)) == 0x0f)) { + parseSubtitlingSegment(); + if (isSet(FLAG_PES_STRIPPED_DVBSUB) && tsStream.bitsLeft() == 0) { + break; + } + sync = tsStream.readBits(8); + + } + + if (sync == 0xff || (isSet(FLAG_PES_STRIPPED_DVBSUB) && tsStream.bitsLeft() == 0)) { // end_of_PES_data_field_marker + // paint the current Subtitle definition + if (subtitleService.pageComposition != null) { + + if (BuildConfig.DEBUG) { + Log.d(TAG, "Rendering subtitle. w: " + subtitleService.displayDefinition.displayWidth + + " h: " + subtitleService.displayDefinition.displayHeight); + + if ((subtitleService.displayDefinition.flags & DISPLAY_WINDOW_FLAG) != 0) { + Log.d(TAG, " Window dimensions (x/y/w/h): (" + + subtitleService.displayDefinition.displayWindowHorizontalPositionMinimum + "/" + + subtitleService.displayDefinition.displayWindowVerticalPositionMinimum + "/" + + (subtitleService.displayDefinition.displayWindowHorizontalPositionMaximum - + subtitleService.displayDefinition.displayWindowHorizontalPositionMinimum) + "/" + + (subtitleService.displayDefinition.displayWindowVerticalPositionMaximum - + subtitleService.displayDefinition.displayWindowVerticalPositionMinimum) + ")" ); + } + } + + int a,b; + PageRegion pageRegion; + RegionComposition regionComposition; + int baseHorizontalAddress, baseVerticalAddress; + ObjectData object; + ClutDefinition clut; + int regionKey; + // process page regions + for (a = 0; a < subtitleService.pageComposition.pageRegions.size(); a++) { + regionKey = subtitleService.pageComposition.pageRegions.keyAt(a); + pageRegion = subtitleService.pageComposition.pageRegions.get(regionKey); + regionComposition = subtitleService.regions.get(regionKey); + + baseHorizontalAddress = pageRegion.regionHorizontalAddress; + baseVerticalAddress = pageRegion.regionVerticalAddress; + + if ((subtitleService.displayDefinition.flags & DISPLAY_WINDOW_FLAG) != 0) { + baseHorizontalAddress += + subtitleService.displayDefinition.displayWindowHorizontalPositionMinimum; + baseVerticalAddress += + subtitleService.displayDefinition.displayWindowVerticalPositionMinimum; + } + + // clip object drawing to the current region and display definition window + canvasObjects[currentBitmap].clipRect( + baseHorizontalAddress, baseVerticalAddress, + Math.min(baseHorizontalAddress + regionComposition.regionWidth, + subtitleService.displayDefinition.displayWindowHorizontalPositionMaximum), + Math.min(baseVerticalAddress + regionComposition.regionHeight, + subtitleService.displayDefinition.displayWindowVerticalPositionMaximum), + Region.Op.REPLACE); + + if ((clut = subtitleService.cluts.get(regionComposition.clutId)) == null) { + if ((clut = subtitleService.ancillaryCluts.get(regionComposition.clutId)) == null) { + clut = defaultClut; + } + } + + // fill the region if needed + if ((regionComposition.flags & REGION_FILL_FLAG )!= 0) { + int colour; + if (regionComposition.regionDepth == DVBSUB_RCS_BITDEPTH_8) { + colour = clut.clutEntries8bit[regionComposition.region8bitPixelCode].ARGB; + } else if (regionComposition.regionDepth == DVBSUB_RCS_BITDEPTH_4) { + colour = clut.clutEntries4bit[regionComposition.region4bitPixelCode].ARGB; + } else { + colour = clut.clutEntries2bit[regionComposition.region2bitPixelCode].ARGB; + } + + fillRegionPaintObject.setColor(colour); + canvasObjects[currentBitmap].drawRect( + baseHorizontalAddress, baseVerticalAddress, + baseHorizontalAddress + regionComposition.regionWidth , + baseVerticalAddress + regionComposition.regionHeight, + fillRegionPaintObject); + + } + + if (BuildConfig.DEBUG) { + Log.d(TAG, " Region: " + regionKey + " (x/y/w/h): (" + + baseHorizontalAddress + "/" + baseVerticalAddress + "/" + + (baseHorizontalAddress + regionComposition.regionWidth - 1) + "/" + + (baseVerticalAddress + regionComposition.regionHeight - 1) + ")" + ); + + canvasObjects[currentBitmap].drawRect( + baseHorizontalAddress, baseVerticalAddress, + baseHorizontalAddress + regionComposition.regionWidth - 1, + baseVerticalAddress + regionComposition.regionHeight - 1, + debugRegionPaintObject); + } + + RegionObject regionObject; + int objectKey; + // process regions compositions + for ( b = 0; b < regionComposition.regionObjects.size(); b++) { + objectKey = regionComposition.regionObjects.keyAt(b); + regionObject = regionComposition.regionObjects.get(objectKey); + + if (BuildConfig.DEBUG) { + Log.d(TAG, " Object[" + objectKey + "]. objectId: " + regionObject.objectId + " (x/y): (" + + (baseHorizontalAddress + regionObject.objectHorizontalPosition) + "/" + + (baseVerticalAddress + regionObject.objectVerticalPosition) + ")" + ); + + canvasObjects[currentBitmap].drawRect( + baseHorizontalAddress + regionObject.objectHorizontalPosition, + baseVerticalAddress + regionObject.objectVerticalPosition, + baseHorizontalAddress + regionObject.objectHorizontalPosition + regionComposition.regionWidth - 1, + baseVerticalAddress + regionObject.objectVerticalPosition + regionComposition.regionHeight - 1, + debugObjectPaintObject); + } + + if ((object = subtitleService.objects.get(regionObject.objectId)) == null) { + if ((object = subtitleService.ancillaryObjects.get(regionObject.objectId)) == null) { + continue; + } + } + + parsePixelDataSubBlocks(object, clut, regionComposition.regionDepth, + baseHorizontalAddress + regionObject.objectHorizontalPosition, + baseVerticalAddress + regionObject.objectVerticalPosition); + + } + } + + lastValidBitmap = bitmaps[currentBitmap]; + currentBitmap = (currentBitmap + 1) % NUM_BITMAPS; + canvasObjects[currentBitmap].clipRect(0,0, + subtitleService.displayDefinition.displayWidth, + subtitleService.displayDefinition.displayHeight, + Region.Op.REPLACE); + canvasObjects[currentBitmap].drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR); + + return lastValidBitmap; + } + } else { + Log.d(TAG,"Unexpected..."); + } + return null; + } + + private boolean isSet(@Flags int flag) { + return (flags & flag) != 0; + } + +} \ No newline at end of file diff --git a/library/core/src/main/java/com/google/android/exoplayer2/util/MimeTypes.java b/library/core/src/main/java/com/google/android/exoplayer2/util/MimeTypes.java index 69d4229186..ea669e6f2a 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/util/MimeTypes.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/util/MimeTypes.java @@ -81,6 +81,7 @@ public final class MimeTypes { public static final String APPLICATION_SCTE35 = BASE_TYPE_APPLICATION + "/x-scte35"; public static final String APPLICATION_CAMERA_MOTION = BASE_TYPE_APPLICATION + "/x-camera-motion"; public static final String APPLICATION_EMSG = BASE_TYPE_APPLICATION + "/x-emsg"; + public static final String APPLICATION_DVBSUBS = BASE_TYPE_APPLICATION + "/dvbsubs"; private MimeTypes() {} @@ -222,7 +223,7 @@ public final class MimeTypes { || APPLICATION_SUBRIP.equals(mimeType) || APPLICATION_TTML.equals(mimeType) || APPLICATION_TX3G.equals(mimeType) || APPLICATION_MP4VTT.equals(mimeType) || APPLICATION_RAWCC.equals(mimeType) || APPLICATION_VOBSUB.equals(mimeType) - || APPLICATION_PGS.equals(mimeType)) { + || APPLICATION_PGS.equals(mimeType) || APPLICATION_DVBSUBS.equals(mimeType)) { return C.TRACK_TYPE_TEXT; } else if (APPLICATION_ID3.equals(mimeType) || APPLICATION_EMSG.equals(mimeType) diff --git a/library/ui/src/main/java/com/google/android/exoplayer2/ui/SubtitlePainter.java b/library/ui/src/main/java/com/google/android/exoplayer2/ui/SubtitlePainter.java index d4f09b1721..4bf7ce0b74 100644 --- a/library/ui/src/main/java/com/google/android/exoplayer2/ui/SubtitlePainter.java +++ b/library/ui/src/main/java/com/google/android/exoplayer2/ui/SubtitlePainter.java @@ -77,6 +77,7 @@ import com.google.android.exoplayer2.util.Util; @Cue.AnchorType private int cuePositionAnchor; private float cueSize; + private float cueSar; private boolean applyEmbeddedStyles; private int foregroundColor; private int backgroundColor; @@ -173,6 +174,7 @@ import com.google.android.exoplayer2.util.Util; && this.cuePosition == cue.position && Util.areEqual(this.cuePositionAnchor, cue.positionAnchor) && this.cueSize == cue.size + && this.cueSar == cue.sar && this.applyEmbeddedStyles == applyEmbeddedStyles && this.foregroundColor == style.foregroundColor && this.backgroundColor == style.backgroundColor @@ -200,6 +202,7 @@ import com.google.android.exoplayer2.util.Util; this.cuePosition = cue.position; this.cuePositionAnchor = cue.positionAnchor; this.cueSize = cue.size; + this.cueSar = cue.sar; this.applyEmbeddedStyles = applyEmbeddedStyles; this.foregroundColor = style.foregroundColor; this.backgroundColor = style.backgroundColor; @@ -312,7 +315,7 @@ import com.google.android.exoplayer2.util.Util; float anchorX = parentLeft + (parentWidth * cuePosition); float anchorY = parentTop + (parentHeight * cueLine); int width = Math.round(parentWidth * cueSize); - int height = Math.round(width * ((float) cueBitmap.getHeight() / cueBitmap.getWidth())); + int height = Math.round(width * ((float) cueBitmap.getHeight() / cueBitmap.getWidth()) / (((float) parentWidth / parentHeight) / cueSar)); int x = Math.round(cueLineAnchor == Cue.ANCHOR_TYPE_END ? (anchorX - width) : cueLineAnchor == Cue.ANCHOR_TYPE_MIDDLE ? (anchorX - (width / 2)) : anchorX); int y = Math.round(cuePositionAnchor == Cue.ANCHOR_TYPE_END ? (anchorY - height) From 937675d1dbeb8fcaf31f2e7c67fe576115c204cc Mon Sep 17 00:00:00 2001 From: Sergio Moreno Mozota Date: Thu, 23 Mar 2017 08:54:07 +0100 Subject: [PATCH 023/119] Add DVB sustitles support to the TsExtractor --- .../ts/DefaultTsPayloadReaderFactory.java | 2 + .../extractor/ts/DvbSubtitlesReader.java | 89 +++++++++++++++++++ .../exoplayer2/extractor/ts/TsExtractor.java | 6 ++ 3 files changed, 97 insertions(+) create mode 100644 library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/DvbSubtitlesReader.java diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/DefaultTsPayloadReaderFactory.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/DefaultTsPayloadReaderFactory.java index e8b664d5ab..d2b0acca85 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/DefaultTsPayloadReaderFactory.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/DefaultTsPayloadReaderFactory.java @@ -109,6 +109,8 @@ public final class DefaultTsPayloadReaderFactory implements TsPayloadReader.Fact ? null : new SectionReader(new SpliceInfoSectionReader()); case TsExtractor.TS_STREAM_TYPE_ID3: return new PesReader(new Id3Reader()); + case TsExtractor.TS_STREAM_TYPE_DVBSUBS: + return new PesReader(new DvbSubtitlesReader(esInfo)); default: return null; } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/DvbSubtitlesReader.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/DvbSubtitlesReader.java new file mode 100644 index 0000000000..0e5931e9fe --- /dev/null +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/DvbSubtitlesReader.java @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.exoplayer2.extractor.ts; + + +import com.google.android.exoplayer2.C; +import com.google.android.exoplayer2.Format; +import com.google.android.exoplayer2.extractor.ExtractorOutput; +import com.google.android.exoplayer2.extractor.TrackOutput; +import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.TrackIdGenerator; +import com.google.android.exoplayer2.util.MimeTypes; +import com.google.android.exoplayer2.util.ParsableByteArray; + +import java.util.ArrayList; +import java.util.List; + + +public class DvbSubtitlesReader implements ElementaryStreamReader { + + private static final String TAG= "DVBSubsReader"; + private final String language; + private List initializationData = new ArrayList<>(); + + private long sampleTimeUs; + private int totalBytesWritten; + private boolean writingSample; + + private TrackOutput output; + + public DvbSubtitlesReader(TsPayloadReader.EsInfo esInfo) { + // we only support one subtitle service per PID + this.language = esInfo.language; + this.initializationData.add(new byte[] {esInfo.descriptorBytes[5]}); // subtitle subtype + this.initializationData.add(new byte[] {esInfo.descriptorBytes[6], esInfo.descriptorBytes[7]}); // subtitle compose page + this.initializationData.add(new byte[] {esInfo.descriptorBytes[8], esInfo.descriptorBytes[9]}); // subtitle ancillary page + this.initializationData.add("mp2t".getBytes()); + } + + + @Override + public void seek() { + writingSample = false; + } + + @Override + public void createTracks(ExtractorOutput extractorOutput, TrackIdGenerator idGenerator) { + idGenerator.generateNewId(); + this.output = extractorOutput.track(idGenerator.getTrackId(), C.TRACK_TYPE_TEXT); + output.format(Format.createImageSampleFormat(idGenerator.getFormatId(), MimeTypes.APPLICATION_DVBSUBS, null, Format.NO_VALUE, initializationData, language, null)); + } + + + @Override + public void packetStarted(long pesTimeUs, boolean dataAlignmentIndicator) { + if (!dataAlignmentIndicator) { + return; + } + writingSample = true; + sampleTimeUs = pesTimeUs; + totalBytesWritten = 0; + } + + @Override + public void packetFinished() { + output.sampleMetadata(sampleTimeUs, C.BUFFER_FLAG_KEY_FRAME, totalBytesWritten, 0, null); + writingSample = false; + } + + @Override + public void consume(ParsableByteArray data) { + if (writingSample) { + totalBytesWritten += data.bytesLeft(); + output.sampleData(data, data.bytesLeft()); + } + } +} \ No newline at end of file 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 65b97c8a73..3c163541ac 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 @@ -92,6 +92,7 @@ public final class TsExtractor implements Extractor { public static final int TS_STREAM_TYPE_H265 = 0x24; public static final int TS_STREAM_TYPE_ID3 = 0x15; public static final int TS_STREAM_TYPE_SPLICE_INFO = 0x86; + public static final int TS_STREAM_TYPE_DVBSUBS = 0x59; private static final int TS_PACKET_SIZE = 188; private static final int TS_SYNC_BYTE = 0x47; // First byte of each TS packet. @@ -356,6 +357,7 @@ public final class TsExtractor implements Extractor { private static final int TS_PMT_DESC_AC3 = 0x6A; private static final int TS_PMT_DESC_EAC3 = 0x7A; private static final int TS_PMT_DESC_DTS = 0x7B; + private static final int TS_PMT_DESC_DVBSUBS = 0x59; private final ParsableBitArray pmtScratch; private final int pid; @@ -498,6 +500,10 @@ public final class TsExtractor implements Extractor { } else if (descriptorTag == TS_PMT_DESC_ISO639_LANG) { language = new String(data.data, data.getPosition(), 3).trim(); // Audio type is ignored. + } else if (descriptorTag == TS_PMT_DESC_DVBSUBS) { + streamType = TS_STREAM_TYPE_DVBSUBS; + // we only support one subtitle service per PID + language = new String(data.data, data.getPosition(), 3).trim(); } // Skip unused bytes of current descriptor. data.skipBytes(positionOfNextDescriptor - data.getPosition()); From 0c49b81b48c83c906d009c1165fbe6b25e66f693 Mon Sep 17 00:00:00 2001 From: Sergio Moreno Mozota Date: Thu, 23 Mar 2017 08:55:20 +0100 Subject: [PATCH 024/119] add DVB subtitle support, based in the TVHeadend spec, to the MatroskaExtractor --- .../extractor/mkv/MatroskaExtractor.java | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mkv/MatroskaExtractor.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mkv/MatroskaExtractor.java index 51ce819282..1f3bb0192d 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mkv/MatroskaExtractor.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mkv/MatroskaExtractor.java @@ -99,6 +99,7 @@ public final class MatroskaExtractor implements Extractor { private static final String CODEC_ID_SUBRIP = "S_TEXT/UTF8"; private static final String CODEC_ID_VOBSUB = "S_VOBSUB"; private static final String CODEC_ID_PGS = "S_HDMV/PGS"; + private static final String CODEC_ID_DVBSUB = "S_DVBSUB"; private static final int VORBIS_MAX_INPUT_SIZE = 8192; private static final int OPUS_MAX_INPUT_SIZE = 5760; @@ -1233,8 +1234,8 @@ public final class MatroskaExtractor implements Extractor { || CODEC_ID_PCM_INT_LIT.equals(codecId) || CODEC_ID_SUBRIP.equals(codecId) || CODEC_ID_VOBSUB.equals(codecId) - || CODEC_ID_PGS.equals(codecId); - } + || CODEC_ID_PGS.equals(codecId) + || CODEC_ID_DVBSUB.equals(codecId); } /** * Returns an array that can store (at least) {@code length} elements, which will be either a new @@ -1461,6 +1462,14 @@ public final class MatroskaExtractor implements Extractor { case CODEC_ID_PGS: mimeType = MimeTypes.APPLICATION_PGS; break; + case CODEC_ID_DVBSUB: + mimeType = MimeTypes.APPLICATION_DVBSUBS; + initializationData = new ArrayList<>(4); + initializationData.add(null); + initializationData.add(new byte[] {codecPrivate[0], codecPrivate[1]}); + initializationData.add(new byte[] {codecPrivate[2], codecPrivate[3]}); + initializationData.add("mkv".getBytes()); + break; default: throw new ParserException("Unrecognized codec identifier."); } @@ -1495,7 +1504,8 @@ public final class MatroskaExtractor implements Extractor { format = Format.createTextSampleFormat(Integer.toString(trackId), mimeType, null, Format.NO_VALUE, selectionFlags, language, drmInitData); } else if (MimeTypes.APPLICATION_VOBSUB.equals(mimeType) - || MimeTypes.APPLICATION_PGS.equals(mimeType)) { + || MimeTypes.APPLICATION_PGS.equals(mimeType) + || MimeTypes.APPLICATION_DVBSUBS.equals(mimeType)) { type = C.TRACK_TYPE_TEXT; format = Format.createImageSampleFormat(Integer.toString(trackId), mimeType, null, Format.NO_VALUE, initializationData, language, drmInitData); From 255523b4323b94b2e9fa0873f80b2f5873192cc5 Mon Sep 17 00:00:00 2001 From: olly Date: Thu, 23 Mar 2017 08:01:41 -0700 Subject: [PATCH 025/119] Call DrmSession methods on the session not the manager The way it was before worked, but it's not really documented that DrmSession methods implemented by DefaultDrmSessionManager can be called after the session has been released, and it feels wrong to do this. At some point we should probably consider moving the DrmSession part of DefaultDrmSessionManager into an inner class, to force usage in the "normal" way. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=151003855 --- .../exoplayer2/drm/OfflineLicenseHelper.java | 33 +++++++++++-------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/drm/OfflineLicenseHelper.java b/library/core/src/main/java/com/google/android/exoplayer2/drm/OfflineLicenseHelper.java index 93f3b396c8..040ca50c76 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/drm/OfflineLicenseHelper.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/drm/OfflineLicenseHelper.java @@ -133,8 +133,7 @@ public final class OfflineLicenseHelper { public synchronized byte[] downloadLicense(DrmInitData drmInitData) throws IOException, InterruptedException, DrmSessionException { Assertions.checkArgument(drmInitData != null); - blockingKeyRequest(DefaultDrmSessionManager.MODE_DOWNLOAD, null, drmInitData); - return drmSessionManager.getOfflineLicenseKeySetId(); + return blockingKeyRequest(DefaultDrmSessionManager.MODE_DOWNLOAD, null, drmInitData); } /** @@ -147,8 +146,7 @@ public final class OfflineLicenseHelper { public synchronized byte[] renewLicense(byte[] offlineLicenseKeySetId) throws DrmSessionException { Assertions.checkNotNull(offlineLicenseKeySetId); - blockingKeyRequest(DefaultDrmSessionManager.MODE_DOWNLOAD, offlineLicenseKeySetId, null); - return drmSessionManager.getOfflineLicenseKeySetId(); + return blockingKeyRequest(DefaultDrmSessionManager.MODE_DOWNLOAD, offlineLicenseKeySetId, null); } /** @@ -173,34 +171,43 @@ public final class OfflineLicenseHelper { public synchronized Pair getLicenseDurationRemainingSec(byte[] offlineLicenseKeySetId) throws DrmSessionException { Assertions.checkNotNull(offlineLicenseKeySetId); - DrmSession session = openBlockingKeyRequest(DefaultDrmSessionManager.MODE_QUERY, + DrmSession drmSession = openBlockingKeyRequest(DefaultDrmSessionManager.MODE_QUERY, offlineLicenseKeySetId, null); + DrmSessionException error = drmSession.getError(); Pair licenseDurationRemainingSec = - WidevineUtil.getLicenseDurationRemainingSec(drmSessionManager); - drmSessionManager.releaseSession(session); + WidevineUtil.getLicenseDurationRemainingSec(drmSession); + drmSessionManager.releaseSession(drmSession); + if (error != null) { + if (error.getCause() instanceof KeysExpiredException) { + return Pair.create(0L, 0L); + } + throw error; + } return licenseDurationRemainingSec; } - private void blockingKeyRequest(@Mode int licenseMode, byte[] offlineLicenseKeySetId, + private byte[] blockingKeyRequest(@Mode int licenseMode, byte[] offlineLicenseKeySetId, DrmInitData drmInitData) throws DrmSessionException { - DrmSession session = openBlockingKeyRequest(licenseMode, offlineLicenseKeySetId, + DrmSession drmSession = openBlockingKeyRequest(licenseMode, offlineLicenseKeySetId, drmInitData); - DrmSessionException error = session.getError(); + DrmSessionException error = drmSession.getError(); + byte[] keySetId = drmSession.getOfflineLicenseKeySetId(); + drmSessionManager.releaseSession(drmSession); if (error != null) { throw error; } - drmSessionManager.releaseSession(session); + return keySetId; } private DrmSession openBlockingKeyRequest(@Mode int licenseMode, byte[] offlineLicenseKeySetId, DrmInitData drmInitData) { drmSessionManager.setMode(licenseMode, offlineLicenseKeySetId); conditionVariable.close(); - DrmSession session = drmSessionManager.acquireSession(handlerThread.getLooper(), + DrmSession drmSession = drmSessionManager.acquireSession(handlerThread.getLooper(), drmInitData); // Block current thread until key loading is finished conditionVariable.block(); - return session; + return drmSession; } } From 7aff624477851485cddb9bb95fbb877355dc6ac4 Mon Sep 17 00:00:00 2001 From: olly Date: Thu, 23 Mar 2017 08:03:58 -0700 Subject: [PATCH 026/119] Fix typo on CEA-708 decoder Issue: #2595 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=151004198 --- .../com/google/android/exoplayer2/text/cea/Cea708Decoder.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/text/cea/Cea708Decoder.java b/library/core/src/main/java/com/google/android/exoplayer2/text/cea/Cea708Decoder.java index 740fd17013..8fd70f7a67 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/text/cea/Cea708Decoder.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/text/cea/Cea708Decoder.java @@ -483,7 +483,7 @@ public final class Cea708Decoder extends CeaDecoder { private void handleC2Command(int command) { // C2 Table doesn't contain any commands in CEA-708-B, but we do need to skip bytes - if (command <= 0x0F) { + if (command <= 0x07) { // Do nothing. } else if (command <= 0x0F) { serviceBlockPacket.skipBits(8); From 14f1b33fcf0f40df4614571591fb9ea19b5754c9 Mon Sep 17 00:00:00 2001 From: eguven Date: Tue, 7 Feb 2017 08:32:45 +0000 Subject: [PATCH 027/119] Make CacheDataSource.open() throw exception if resolved range doesn't cover position ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=151006826 --- .../android/exoplayer2/upstream/cache/CacheDataSource.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheDataSource.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheDataSource.java index a2e4382e0c..a11f1956ea 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheDataSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheDataSource.java @@ -181,6 +181,9 @@ public final class CacheDataSource implements DataSource { bytesRemaining = cache.getContentLength(key); if (bytesRemaining != C.LENGTH_UNSET) { bytesRemaining -= dataSpec.position; + if (bytesRemaining <= 0) { + throw new DataSourceException(DataSourceException.POSITION_OUT_OF_RANGE); + } } } openNextSource(true); From 6faf566344622a437dc24118b8682547e66f0f41 Mon Sep 17 00:00:00 2001 From: olly Date: Thu, 23 Mar 2017 09:38:01 -0700 Subject: [PATCH 028/119] Fix NullPointerException enabling WebVtt subtitles in DASH Issue: #2596 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=151013802 --- .../source/dash/DefaultDashChunkSource.java | 33 ++++++++++--------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DefaultDashChunkSource.java b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DefaultDashChunkSource.java index 3c928f3ccf..834f8c147e 100644 --- a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DefaultDashChunkSource.java +++ b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DefaultDashChunkSource.java @@ -179,23 +179,24 @@ public class DefaultDashChunkSource implements DashChunkSource { RepresentationHolder representationHolder = representationHolders[trackSelection.getSelectedIndex()]; - Representation selectedRepresentation = representationHolder.representation; - DashSegmentIndex segmentIndex = representationHolder.segmentIndex; - RangedUri pendingInitializationUri = null; - RangedUri pendingIndexUri = null; - if (representationHolder.extractorWrapper.getSampleFormats() == null) { - pendingInitializationUri = selectedRepresentation.getInitializationUri(); - } - if (segmentIndex == null) { - pendingIndexUri = selectedRepresentation.getIndexUri(); - } - if (pendingInitializationUri != null || pendingIndexUri != null) { - // We have initialization and/or index requests to make. - out.chunk = newInitializationChunk(representationHolder, dataSource, - trackSelection.getSelectedFormat(), trackSelection.getSelectionReason(), - trackSelection.getSelectionData(), pendingInitializationUri, pendingIndexUri); - return; + if (representationHolder.extractorWrapper != null) { + Representation selectedRepresentation = representationHolder.representation; + RangedUri pendingInitializationUri = null; + RangedUri pendingIndexUri = null; + if (representationHolder.extractorWrapper.getSampleFormats() == null) { + pendingInitializationUri = selectedRepresentation.getInitializationUri(); + } + if (representationHolder.segmentIndex == null) { + pendingIndexUri = selectedRepresentation.getIndexUri(); + } + if (pendingInitializationUri != null || pendingIndexUri != null) { + // We have initialization and/or index requests to make. + out.chunk = newInitializationChunk(representationHolder, dataSource, + trackSelection.getSelectedFormat(), trackSelection.getSelectionReason(), + trackSelection.getSelectionData(), pendingInitializationUri, pendingIndexUri); + return; + } } long nowUs = getNowUnixTimeUs(); From 0e6ef0edf69862e6c2ad4d4824b65bd331aa6145 Mon Sep 17 00:00:00 2001 From: andrewlewis Date: Thu, 23 Mar 2017 11:08:11 -0700 Subject: [PATCH 029/119] Add Sonic library for audio speed adjustment. Add methods to ExoPlayer for setting/getting the playback speed, using SonicAudioProcessor. Remove PlaybackParams support, as the AudioTrack timestamp does not work reliably on Marshmallow. The platform also uses Sonic and performance should be comparable between the Java and native versions on recent Android runtimes. In a later change, SonicAudioProcessor will be made public so it can be used in conjunction with other processors. Issue: #26 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=151027121 --- .../android/exoplayer2/demo/EventLogger.java | 7 + .../exoplayer2/demo/PlayerActivity.java | 6 + .../exoplayer2/ext/flac/FlacPlaybackTest.java | 6 + .../exoplayer2/ext/opus/OpusPlaybackTest.java | 6 + .../exoplayer2/ext/vp9/VpxPlaybackTest.java | 6 + .../android/exoplayer2/ExoPlayerTest.java | 15 + .../java/com/google/android/exoplayer2/C.java | 13 +- .../google/android/exoplayer2/ExoPlayer.java | 33 + .../android/exoplayer2/ExoPlayerImpl.java | 28 + .../exoplayer2/ExoPlayerImplInternal.java | 54 +- .../exoplayer2/PlaybackParameters.java | 83 ++ .../android/exoplayer2/SimpleExoPlayer.java | 54 +- .../android/exoplayer2/audio/AudioTrack.java | 205 ++--- .../audio/MediaCodecAudioRenderer.java | 15 +- .../audio/SimpleDecoderAudioRenderer.java | 15 +- .../android/exoplayer2/audio/Sonic.java | 817 ++++++++++++++++++ .../exoplayer2/audio/SonicAudioProcessor.java | 205 +++++ .../android/exoplayer2/util/MediaClock.java | 16 + .../exoplayer2/util/StandaloneMediaClock.java | 73 +- .../google/android/exoplayer2/util/Util.java | 12 + .../exoplayer2/ui/DebugTextViewHelper.java | 6 + .../exoplayer2/ui/PlaybackControlView.java | 6 + .../exoplayer2/ui/SimpleExoPlayerView.java | 6 + .../playbacktests/util/ExoHostedTest.java | 6 + 24 files changed, 1492 insertions(+), 201 deletions(-) create mode 100644 library/core/src/main/java/com/google/android/exoplayer2/PlaybackParameters.java create mode 100644 library/core/src/main/java/com/google/android/exoplayer2/audio/Sonic.java create mode 100644 library/core/src/main/java/com/google/android/exoplayer2/audio/SonicAudioProcessor.java diff --git a/demo/src/main/java/com/google/android/exoplayer2/demo/EventLogger.java b/demo/src/main/java/com/google/android/exoplayer2/demo/EventLogger.java index e39cd16743..79e0bcd693 100644 --- a/demo/src/main/java/com/google/android/exoplayer2/demo/EventLogger.java +++ b/demo/src/main/java/com/google/android/exoplayer2/demo/EventLogger.java @@ -22,6 +22,7 @@ import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.ExoPlaybackException; import com.google.android.exoplayer2.ExoPlayer; import com.google.android.exoplayer2.Format; +import com.google.android.exoplayer2.PlaybackParameters; import com.google.android.exoplayer2.RendererCapabilities; import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.audio.AudioRendererEventListener; @@ -99,6 +100,12 @@ import java.util.Locale; Log.d(TAG, "positionDiscontinuity"); } + @Override + public void onPlaybackParametersChanged(PlaybackParameters playbackParameters) { + Log.d(TAG, "playbackParameters " + String.format( + "[speed=%.2f, pitch=%.2f]", playbackParameters.speed, playbackParameters.pitch)); + } + @Override public void onTimelineChanged(Timeline timeline, Object manifest) { int periodCount = timeline.getPeriodCount(); diff --git a/demo/src/main/java/com/google/android/exoplayer2/demo/PlayerActivity.java b/demo/src/main/java/com/google/android/exoplayer2/demo/PlayerActivity.java index adb04eaa24..15433af92d 100644 --- a/demo/src/main/java/com/google/android/exoplayer2/demo/PlayerActivity.java +++ b/demo/src/main/java/com/google/android/exoplayer2/demo/PlayerActivity.java @@ -34,6 +34,7 @@ import com.google.android.exoplayer2.DefaultLoadControl; import com.google.android.exoplayer2.ExoPlaybackException; import com.google.android.exoplayer2.ExoPlayer; import com.google.android.exoplayer2.ExoPlayerFactory; +import com.google.android.exoplayer2.PlaybackParameters; import com.google.android.exoplayer2.SimpleExoPlayer; import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.drm.DefaultDrmSessionManager; @@ -427,6 +428,11 @@ public class PlayerActivity extends Activity implements OnClickListener, ExoPlay } } + @Override + public void onPlaybackParametersChanged(PlaybackParameters playbackParameters) { + // Do nothing. + } + @Override public void onTimelineChanged(Timeline timeline, Object manifest) { // Do nothing. diff --git a/extensions/flac/src/androidTest/java/com/google/android/exoplayer2/ext/flac/FlacPlaybackTest.java b/extensions/flac/src/androidTest/java/com/google/android/exoplayer2/ext/flac/FlacPlaybackTest.java index 990c470a93..21f01f0cca 100644 --- a/extensions/flac/src/androidTest/java/com/google/android/exoplayer2/ext/flac/FlacPlaybackTest.java +++ b/extensions/flac/src/androidTest/java/com/google/android/exoplayer2/ext/flac/FlacPlaybackTest.java @@ -22,6 +22,7 @@ import android.test.InstrumentationTestCase; import com.google.android.exoplayer2.ExoPlaybackException; import com.google.android.exoplayer2.ExoPlayer; import com.google.android.exoplayer2.ExoPlayerFactory; +import com.google.android.exoplayer2.PlaybackParameters; import com.google.android.exoplayer2.Renderer; import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.extractor.mkv.MatroskaExtractor; @@ -102,6 +103,11 @@ public class FlacPlaybackTest extends InstrumentationTestCase { // Do nothing. } + @Override + public void onPlaybackParametersChanged(PlaybackParameters playbackParameters) { + // Do nothing. + } + @Override public void onTimelineChanged(Timeline timeline, Object manifest) { // Do nothing. diff --git a/extensions/opus/src/androidTest/java/com/google/android/exoplayer2/ext/opus/OpusPlaybackTest.java b/extensions/opus/src/androidTest/java/com/google/android/exoplayer2/ext/opus/OpusPlaybackTest.java index 3e07186995..263934d982 100644 --- a/extensions/opus/src/androidTest/java/com/google/android/exoplayer2/ext/opus/OpusPlaybackTest.java +++ b/extensions/opus/src/androidTest/java/com/google/android/exoplayer2/ext/opus/OpusPlaybackTest.java @@ -22,6 +22,7 @@ import android.test.InstrumentationTestCase; import com.google.android.exoplayer2.ExoPlaybackException; import com.google.android.exoplayer2.ExoPlayer; import com.google.android.exoplayer2.ExoPlayerFactory; +import com.google.android.exoplayer2.PlaybackParameters; import com.google.android.exoplayer2.Renderer; import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.extractor.mkv.MatroskaExtractor; @@ -102,6 +103,11 @@ public class OpusPlaybackTest extends InstrumentationTestCase { // Do nothing. } + @Override + public void onPlaybackParametersChanged(PlaybackParameters playbackParameters) { + // Do nothing. + } + @Override public void onTimelineChanged(Timeline timeline, Object manifest) { // Do nothing. diff --git a/extensions/vp9/src/androidTest/java/com/google/android/exoplayer2/ext/vp9/VpxPlaybackTest.java b/extensions/vp9/src/androidTest/java/com/google/android/exoplayer2/ext/vp9/VpxPlaybackTest.java index f888554e22..2647776b74 100644 --- a/extensions/vp9/src/androidTest/java/com/google/android/exoplayer2/ext/vp9/VpxPlaybackTest.java +++ b/extensions/vp9/src/androidTest/java/com/google/android/exoplayer2/ext/vp9/VpxPlaybackTest.java @@ -23,6 +23,7 @@ import android.util.Log; import com.google.android.exoplayer2.ExoPlaybackException; import com.google.android.exoplayer2.ExoPlayer; import com.google.android.exoplayer2.ExoPlayerFactory; +import com.google.android.exoplayer2.PlaybackParameters; import com.google.android.exoplayer2.Renderer; import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.extractor.mkv.MatroskaExtractor; @@ -134,6 +135,11 @@ public class VpxPlaybackTest extends InstrumentationTestCase { // Do nothing. } + @Override + public void onPlaybackParametersChanged(PlaybackParameters playbackParameters) { + // Do nothing. + } + @Override public void onTimelineChanged(Timeline timeline, Object manifest) { // Do nothing. diff --git a/library/core/src/androidTest/java/com/google/android/exoplayer2/ExoPlayerTest.java b/library/core/src/androidTest/java/com/google/android/exoplayer2/ExoPlayerTest.java index 93c0a7dc11..daa845298b 100644 --- a/library/core/src/androidTest/java/com/google/android/exoplayer2/ExoPlayerTest.java +++ b/library/core/src/androidTest/java/com/google/android/exoplayer2/ExoPlayerTest.java @@ -140,6 +140,16 @@ public final class ExoPlayerTest extends TestCase { return isCurrentStreamFinal() ? 60000030 : 60000000; } + @Override + public PlaybackParameters setPlaybackParameters(PlaybackParameters playbackParameters) { + return PlaybackParameters.DEFAULT; + } + + @Override + public PlaybackParameters getPlaybackParameters() { + return PlaybackParameters.DEFAULT; + } + @Override public boolean isEnded() { // Allow playback to end once the final period is playing. @@ -272,6 +282,11 @@ public final class ExoPlayerTest extends TestCase { positionDiscontinuityCount++; } + @Override + public void onPlaybackParametersChanged(PlaybackParameters playbackParameters) { + // Do nothing. + } + } private static final class TimelineWindowDefinition { diff --git a/library/core/src/main/java/com/google/android/exoplayer2/C.java b/library/core/src/main/java/com/google/android/exoplayer2/C.java index 6a1db191a0..29f8220037 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/C.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/C.java @@ -479,15 +479,6 @@ public final class C { */ public static final int MSG_SET_VOLUME = 2; - /** - * A type of a message that can be passed to an audio {@link Renderer} via - * {@link ExoPlayer#sendMessages} or {@link ExoPlayer#blockingSendMessages}. The message object - * should be a {@link android.media.PlaybackParams}, or null, which will be used to configure the - * underlying {@link android.media.AudioTrack}. The message object should not be modified by the - * caller after it has been passed. - */ - public static final int MSG_SET_PLAYBACK_PARAMS = 3; - /** * A type of a message that can be passed to an audio {@link Renderer} via * {@link ExoPlayer#sendMessages} or {@link ExoPlayer#blockingSendMessages}. The message object @@ -500,7 +491,7 @@ public final class C { * introduce a brief gap in audio output. Note also that tracks in the same audio session must * share the same routing, so a new audio session id will be generated. */ - public static final int MSG_SET_STREAM_TYPE = 4; + public static final int MSG_SET_STREAM_TYPE = 3; /** * The type of a message that can be passed to a {@link MediaCodec}-based video {@link Renderer} @@ -510,7 +501,7 @@ public final class C { * Note that the scaling mode only applies if the {@link Surface} targeted by the renderer is * owned by a {@link android.view.SurfaceView}. */ - public static final int MSG_SET_SCALING_MODE = 5; + public static final int MSG_SET_SCALING_MODE = 4; /** * Applications or extensions may define custom {@code MSG_*} constants greater than or equal to diff --git a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayer.java b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayer.java index f7dbea851a..ab521e3733 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayer.java @@ -15,6 +15,7 @@ */ package com.google.android.exoplayer2; +import android.support.annotation.Nullable; import com.google.android.exoplayer2.audio.MediaCodecAudioRenderer; import com.google.android.exoplayer2.metadata.MetadataRenderer; import com.google.android.exoplayer2.source.ConcatenatingMediaSource; @@ -168,6 +169,16 @@ public interface ExoPlayer { */ void onPositionDiscontinuity(); + /** + * Called when the current playback parameters change. The playback parameters may change due to + * a call to {@link ExoPlayer#setPlaybackParameters(PlaybackParameters)}, or the player itself + * may change them (for example, if audio playback switches to passthrough mode, where speed + * adjustment is no longer possible). + * + * @param playbackParameters The playback parameters. + */ + void onPlaybackParametersChanged(PlaybackParameters playbackParameters); + } /** @@ -340,6 +351,28 @@ public interface ExoPlayer { */ void seekTo(int windowIndex, long positionMs); + /** + * Attempts to set the playback parameters. Passing {@code null} sets the parameters to the + * default, {@link PlaybackParameters#DEFAULT}, which means there is no speed or pitch adjustment. + *

    + * Playback parameters changes may cause the player to buffer. + * {@link EventListener#onPlaybackParametersChanged(PlaybackParameters)} will be called whenever + * the currently active playback parameters change. When that listener is called, the parameters + * passed to it may not match {@code playbackParameters}. For example, the chosen speed or pitch + * may be out of range, in which case they are constrained to a set of permitted values. If it is + * not possible to change the playback parameters, the listener will not be invoked. + * + * @param playbackParameters The playback parameters, or {@code null} to use the defaults. + */ + void setPlaybackParameters(@Nullable PlaybackParameters playbackParameters); + + /** + * Returns the currently active playback parameters. + * + * @see EventListener#onPlaybackParametersChanged(PlaybackParameters) + */ + PlaybackParameters getPlaybackParameters(); + /** * Stops playback. Use {@code setPlayWhenReady(false)} rather than this method if the intention * is to pause playback. 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 d44d138091..7497ed0da9 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 @@ -19,6 +19,7 @@ import android.annotation.SuppressLint; import android.os.Handler; import android.os.Looper; import android.os.Message; +import android.support.annotation.Nullable; import android.util.Log; import com.google.android.exoplayer2.ExoPlayerImplInternal.PlaybackInfo; import com.google.android.exoplayer2.ExoPlayerImplInternal.SourceInfo; @@ -57,6 +58,7 @@ import java.util.concurrent.CopyOnWriteArraySet; private Object manifest; private TrackGroupArray trackGroups; private TrackSelectionArray trackSelections; + private PlaybackParameters playbackParameters; // Playback information when there is no pending seek/set source operation. private PlaybackInfo playbackInfo; @@ -87,6 +89,7 @@ import java.util.concurrent.CopyOnWriteArraySet; period = new Timeline.Period(); trackGroups = TrackGroupArray.EMPTY; trackSelections = emptyTrackSelections; + playbackParameters = PlaybackParameters.DEFAULT; eventHandler = new Handler() { @Override public void handleMessage(Message msg) { @@ -196,6 +199,19 @@ import java.util.concurrent.CopyOnWriteArraySet; } } + @Override + public void setPlaybackParameters(@Nullable PlaybackParameters playbackParameters) { + if (playbackParameters == null) { + playbackParameters = PlaybackParameters.DEFAULT; + } + internalPlayer.setPlaybackParameters(playbackParameters); + } + + @Override + public PlaybackParameters getPlaybackParameters() { + return playbackParameters; + } + @Override public void stop() { internalPlayer.stop(); @@ -376,6 +392,16 @@ import java.util.concurrent.CopyOnWriteArraySet; } break; } + case ExoPlayerImplInternal.MSG_PLAYBACK_PARAMETERS_CHANGED: { + PlaybackParameters playbackParameters = (PlaybackParameters) msg.obj; + if (!this.playbackParameters.equals(playbackParameters)) { + this.playbackParameters = playbackParameters; + for (EventListener listener : listeners) { + listener.onPlaybackParametersChanged(playbackParameters); + } + } + break; + } case ExoPlayerImplInternal.MSG_ERROR: { ExoPlaybackException exception = (ExoPlaybackException) msg.obj; for (EventListener listener : listeners) { @@ -383,6 +409,8 @@ import java.util.concurrent.CopyOnWriteArraySet; } break; } + default: + throw new IllegalStateException(); } } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java index e4c109e85b..f6fa0d39ac 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java @@ -96,20 +96,22 @@ import java.io.IOException; public static final int MSG_SEEK_ACK = 4; public static final int MSG_POSITION_DISCONTINUITY = 5; public static final int MSG_SOURCE_INFO_REFRESHED = 6; - public static final int MSG_ERROR = 7; + public static final int MSG_PLAYBACK_PARAMETERS_CHANGED = 7; + public static final int MSG_ERROR = 8; // Internal messages private static final int MSG_PREPARE = 0; private static final int MSG_SET_PLAY_WHEN_READY = 1; private static final int MSG_DO_SOME_WORK = 2; private static final int MSG_SEEK_TO = 3; - private static final int MSG_STOP = 4; - private static final int MSG_RELEASE = 5; - private static final int MSG_REFRESH_SOURCE_INFO = 6; - private static final int MSG_PERIOD_PREPARED = 7; - private static final int MSG_SOURCE_CONTINUE_LOADING_REQUESTED = 8; - private static final int MSG_TRACK_SELECTION_INVALIDATED = 9; - private static final int MSG_CUSTOM = 10; + private static final int MSG_SET_PLAYBACK_PARAMETERS = 4; + private static final int MSG_STOP = 5; + private static final int MSG_RELEASE = 6; + private static final int MSG_REFRESH_SOURCE_INFO = 7; + private static final int MSG_PERIOD_PREPARED = 8; + private static final int MSG_SOURCE_CONTINUE_LOADING_REQUESTED = 9; + private static final int MSG_TRACK_SELECTION_INVALIDATED = 10; + private static final int MSG_CUSTOM = 11; private static final int PREPARING_SOURCE_INTERVAL_MS = 10; private static final int RENDERING_INTERVAL_MS = 10; @@ -143,6 +145,7 @@ import java.io.IOException; private final Timeline.Period period; private PlaybackInfo playbackInfo; + private PlaybackParameters playbackParameters; private Renderer rendererMediaClockSource; private MediaClock rendererMediaClock; private MediaSource mediaSource; @@ -188,6 +191,7 @@ import java.io.IOException; window = new Timeline.Window(); period = new Timeline.Period(); trackSelector.init(this); + playbackParameters = PlaybackParameters.DEFAULT; // Note: The documentation for Process.THREAD_PRIORITY_AUDIO that states "Applications can // not normally change to this priority" is incorrect. @@ -211,6 +215,10 @@ import java.io.IOException; .sendToTarget(); } + public void setPlaybackParameters(PlaybackParameters playbackParameters) { + handler.obtainMessage(MSG_SET_PLAYBACK_PARAMETERS, playbackParameters).sendToTarget(); + } + public void stop() { handler.sendEmptyMessage(MSG_STOP); } @@ -304,6 +312,10 @@ import java.io.IOException; seekToInternal((SeekPosition) msg.obj); return true; } + case MSG_SET_PLAYBACK_PARAMETERS: { + setPlaybackParametersInternal((PlaybackParameters) msg.obj); + return true; + } case MSG_STOP: { stopInternal(); return true; @@ -478,6 +490,19 @@ import java.io.IOException; maybeThrowPeriodPrepareError(); } + // The standalone media clock never changes playback parameters, so just check the renderer. + if (rendererMediaClock != null) { + PlaybackParameters playbackParameters = rendererMediaClock.getPlaybackParameters(); + if (!playbackParameters.equals(this.playbackParameters)) { + // TODO: Make LoadControl, period transition position projection, adaptive track selection + // and potentially any time-related code in renderers take into account the playback speed. + this.playbackParameters = playbackParameters; + standaloneMediaClock.synchronize(rendererMediaClock); + eventHandler.obtainMessage(MSG_PLAYBACK_PARAMETERS_CHANGED, playbackParameters) + .sendToTarget(); + } + } + long playingPeriodDurationUs = timeline.getPeriod(playingPeriodHolder.index, period) .getDurationUs(); if (allRenderersEnded @@ -646,6 +671,14 @@ import java.io.IOException; } } + private void setPlaybackParametersInternal(PlaybackParameters playbackParameters) { + playbackParameters = rendererMediaClock != null + ? rendererMediaClock.setPlaybackParameters(playbackParameters) + : standaloneMediaClock.setPlaybackParameters(playbackParameters); + this.playbackParameters = playbackParameters; + eventHandler.obtainMessage(MSG_PLAYBACK_PARAMETERS_CHANGED, playbackParameters).sendToTarget(); + } + private void stopInternal() { resetInternal(true); loadControl.onStopped(); @@ -774,7 +807,7 @@ import java.io.IOException; if (sampleStream == null) { // The renderer won't be re-enabled. Sync standaloneMediaClock so that it can take // over timing responsibilities. - standaloneMediaClock.setPositionUs(rendererMediaClock.getPositionUs()); + standaloneMediaClock.synchronize(rendererMediaClock); } rendererMediaClock = null; rendererMediaClockSource = null; @@ -1334,7 +1367,7 @@ import java.io.IOException; // is final and it's not reading ahead. if (renderer == rendererMediaClockSource) { // Sync standaloneMediaClock so that it can take over timing responsibilities. - standaloneMediaClock.setPositionUs(rendererMediaClock.getPositionUs()); + standaloneMediaClock.synchronize(rendererMediaClock); rendererMediaClock = null; rendererMediaClockSource = null; } @@ -1380,6 +1413,7 @@ import java.io.IOException; } rendererMediaClock = mediaClock; rendererMediaClockSource = renderer; + rendererMediaClock.setPlaybackParameters(playbackParameters); } // Start the renderer if playing. if (playing) { diff --git a/library/core/src/main/java/com/google/android/exoplayer2/PlaybackParameters.java b/library/core/src/main/java/com/google/android/exoplayer2/PlaybackParameters.java new file mode 100644 index 0000000000..90aded7660 --- /dev/null +++ b/library/core/src/main/java/com/google/android/exoplayer2/PlaybackParameters.java @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.exoplayer2; + +/** + * The parameters that apply to playback. + */ +public final class PlaybackParameters { + + /** + * The default playback parameters: real-time playback with no pitch modification. + */ + public static final PlaybackParameters DEFAULT = new PlaybackParameters(1f, 1f); + + /** + * The factor by which playback will be sped up. + */ + public final float speed; + + /** + * The factor by which the audio pitch will be scaled. + */ + public final float pitch; + + private final int scaledUsPerMs; + + /** + * Creates new playback parameters. + * + * @param speed The factor by which playback will be sped up. + * @param pitch The factor by which the audio pitch will be scaled. + */ + public PlaybackParameters(float speed, float pitch) { + this.speed = speed; + this.pitch = pitch; + scaledUsPerMs = Math.round(speed * 1000f); + } + + /** + * Scales the millisecond duration {@code timeMs} by the playback speed, returning the result in + * microseconds. + * + * @param timeMs The time to scale, in milliseconds. + * @return The scaled time, in microseconds. + */ + public long getSpeedAdjustedDurationUs(long timeMs) { + return timeMs * scaledUsPerMs; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + PlaybackParameters other = (PlaybackParameters) obj; + return this.speed == other.speed && this.pitch == other.pitch; + } + + @Override + public int hashCode() { + int result = 17; + result = 31 * result + Float.floatToRawIntBits(speed); + result = 31 * result + Float.floatToRawIntBits(pitch); + return result; + } + +} 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 3ce4937911..6ce6191905 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 @@ -22,6 +22,7 @@ import android.media.MediaCodec; import android.media.PlaybackParams; import android.os.Handler; import android.support.annotation.IntDef; +import android.support.annotation.Nullable; import android.util.Log; import android.view.Surface; import android.view.SurfaceHolder; @@ -145,7 +146,6 @@ public class SimpleExoPlayer implements ExoPlayer { @C.StreamType private int audioStreamType; private float audioVolume; - private PlaybackParamsHolder playbackParamsHolder; protected SimpleExoPlayer(Context context, TrackSelector trackSelector, LoadControl loadControl, DrmSessionManager drmSessionManager, @@ -344,37 +344,20 @@ public class SimpleExoPlayer implements ExoPlayer { /** * Sets the {@link PlaybackParams} governing audio playback. * + * @deprecated Use {@link #setPlaybackParameters(PlaybackParameters)}. * @param params The {@link PlaybackParams}, or null to clear any previously set parameters. */ + @Deprecated @TargetApi(23) - public void setPlaybackParams(PlaybackParams params) { + public void setPlaybackParams(@Nullable PlaybackParams params) { + PlaybackParameters playbackParameters; if (params != null) { - // The audio renderers will call this on the playback thread to ensure they can query - // parameters without failure. We do the same up front, which is redundant except that it - // ensures an immediate call to getPlaybackParams will retrieve the instance with defaults - // allowed, rather than this change becoming visible sometime later once the audio renderers - // receive the parameters. params.allowDefaults(); - playbackParamsHolder = new PlaybackParamsHolder(params); + playbackParameters = new PlaybackParameters(params.getSpeed(), params.getPitch()); } else { - playbackParamsHolder = null; + playbackParameters = null; } - ExoPlayerMessage[] messages = new ExoPlayerMessage[audioRendererCount]; - int count = 0; - for (Renderer renderer : renderers) { - if (renderer.getTrackType() == C.TRACK_TYPE_AUDIO) { - messages[count++] = new ExoPlayerMessage(renderer, C.MSG_SET_PLAYBACK_PARAMS, params); - } - } - player.sendMessages(messages); - } - - /** - * Returns the {@link PlaybackParams} governing audio playback, or null if not set. - */ - @TargetApi(23) - public PlaybackParams getPlaybackParams() { - return playbackParamsHolder == null ? null : playbackParamsHolder.params; + setPlaybackParameters(playbackParameters); } /** @@ -519,6 +502,16 @@ public class SimpleExoPlayer implements ExoPlayer { player.seekTo(windowIndex, positionMs); } + @Override + public void setPlaybackParameters(PlaybackParameters playbackParameters) { + player.setPlaybackParameters(playbackParameters); + } + + @Override + public PlaybackParameters getPlaybackParameters() { + return player.getPlaybackParameters(); + } + @Override public void stop() { player.stop(); @@ -1024,15 +1017,4 @@ public class SimpleExoPlayer implements ExoPlayer { } - @TargetApi(23) - private static final class PlaybackParamsHolder { - - public final PlaybackParams params; - - public PlaybackParamsHolder(PlaybackParams params) { - this.params = params; - } - - } - } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioTrack.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioTrack.java index 3b8a1b8f39..d56f6a0d89 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioTrack.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioTrack.java @@ -20,11 +20,11 @@ import android.annotation.TargetApi; import android.media.AudioAttributes; import android.media.AudioFormat; import android.media.AudioTimestamp; -import android.media.PlaybackParams; import android.os.ConditionVariable; import android.os.SystemClock; import android.util.Log; import com.google.android.exoplayer2.C; +import com.google.android.exoplayer2.PlaybackParameters; import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.Util; @@ -271,6 +271,7 @@ public final class AudioTrack { private final AudioCapabilities audioCapabilities; private final ChannelMappingAudioProcessor channelMappingAudioProcessor; + private final SonicAudioProcessor sonicAudioProcessor; private final AudioProcessor[] availableAudioProcessors; private final Listener listener; private final ConditionVariable releasingConditionVariable; @@ -294,6 +295,7 @@ public final class AudioTrack { private boolean passthrough; private int bufferSize; private long bufferSizeUs; + private PlaybackParameters playbackParameters; private ByteBuffer avSyncHeader; private int bytesUntilNextAvSync; @@ -344,11 +346,6 @@ public final class AudioTrack { public AudioTrack(AudioCapabilities audioCapabilities, AudioProcessor[] audioProcessors, Listener listener) { this.audioCapabilities = audioCapabilities; - channelMappingAudioProcessor = new ChannelMappingAudioProcessor(); - availableAudioProcessors = new AudioProcessor[audioProcessors.length + 2]; - availableAudioProcessors[0] = new ResamplingAudioProcessor(); - availableAudioProcessors[1] = channelMappingAudioProcessor; - System.arraycopy(audioProcessors, 0, availableAudioProcessors, 2, audioProcessors.length); this.listener = listener; releasingConditionVariable = new ConditionVariable(true); if (Util.SDK_INT >= 18) { @@ -359,18 +356,24 @@ public final class AudioTrack { // There's no guarantee this method exists. Do nothing. } } - if (Util.SDK_INT >= 23) { - audioTrackUtil = new AudioTrackUtilV23(); - } else if (Util.SDK_INT >= 19) { + if (Util.SDK_INT >= 19) { audioTrackUtil = new AudioTrackUtilV19(); } else { audioTrackUtil = new AudioTrackUtil(); } + channelMappingAudioProcessor = new ChannelMappingAudioProcessor(); + sonicAudioProcessor = new SonicAudioProcessor(); + availableAudioProcessors = new AudioProcessor[3 + audioProcessors.length]; + availableAudioProcessors[0] = new ResamplingAudioProcessor(); + availableAudioProcessors[1] = channelMappingAudioProcessor; + System.arraycopy(audioProcessors, 0, availableAudioProcessors, 2, audioProcessors.length); + availableAudioProcessors[2 + audioProcessors.length] = sonicAudioProcessor; playheadOffsets = new long[MAX_PLAYHEAD_OFFSET_COUNT]; volume = 1.0f; startMediaTimeState = START_NOT_SET; streamType = C.STREAM_TYPE_DEFAULT; audioSessionId = C.AUDIO_SESSION_ID_UNSET; + playbackParameters = PlaybackParameters.DEFAULT; drainingAudioProcessorIndex = C.INDEX_UNSET; this.audioProcessors = new AudioProcessor[0]; outputBuffers = new ByteBuffer[0]; @@ -408,33 +411,28 @@ public final class AudioTrack { } long systemClockUs = System.nanoTime() / 1000; - long currentPositionUs; + long positionUs; if (audioTimestampSet) { - // How long ago in the past the audio timestamp is (negative if it's in the future). - long presentationDiff = systemClockUs - (audioTrackUtil.getTimestampNanoTime() / 1000); - // Fixes such difference if the playback speed is not real time speed. - long actualSpeedPresentationDiff = (long) (presentationDiff - * audioTrackUtil.getPlaybackSpeed()); - long framesDiff = durationUsToFrames(actualSpeedPresentationDiff); - // The position of the frame that's currently being presented. - long currentFramePosition = audioTrackUtil.getTimestampFramePosition() + framesDiff; - currentPositionUs = framesToDurationUs(currentFramePosition) + startMediaTimeUs; + // Calculate the speed-adjusted position using the timestamp (which may be in the future). + long elapsedSinceTimestampUs = systemClockUs - (audioTrackUtil.getTimestampNanoTime() / 1000); + long elapsedSinceTimestampFrames = durationUsToFrames(elapsedSinceTimestampUs); + long elapsedFrames = audioTrackUtil.getTimestampFramePosition() + elapsedSinceTimestampFrames; + positionUs = framesToDurationUs(elapsedFrames); } else { if (playheadOffsetCount == 0) { // The AudioTrack has started, but we don't have any samples to compute a smoothed position. - currentPositionUs = audioTrackUtil.getPlaybackHeadPositionUs() + startMediaTimeUs; + positionUs = audioTrackUtil.getPositionUs(); } else { // getPlayheadPositionUs() only has a granularity of ~20 ms, so we base the position off the // system clock (and a smoothed offset between it and the playhead position) so as to // prevent jitter in the reported positions. - currentPositionUs = systemClockUs + smoothedPlayheadOffsetUs + startMediaTimeUs; + positionUs = systemClockUs + smoothedPlayheadOffsetUs; } if (!sourceEnded) { - currentPositionUs -= latencyUs; + positionUs -= latencyUs; } } - - return currentPositionUs; + return startMediaTimeUs + scaleFrames(positionUs); } /** @@ -481,10 +479,7 @@ public final class AudioTrack { boolean flush = false; if (!passthrough) { pcmFrameSize = Util.getPcmFrameSize(pcmEncoding, channelCount); - - // Reconfigure the audio processors. channelMappingAudioProcessor.setChannelMap(outputChannels); - ArrayList newAudioProcessors = new ArrayList<>(); for (AudioProcessor audioProcessor : availableAudioProcessors) { try { flush |= audioProcessor.configure(sampleRate, channelCount, encoding); @@ -492,23 +487,12 @@ public final class AudioTrack { throw new ConfigurationException(e); } if (audioProcessor.isActive()) { - newAudioProcessors.add(audioProcessor); channelCount = audioProcessor.getOutputChannelCount(); encoding = audioProcessor.getOutputEncoding(); - } else { - audioProcessor.flush(); } } - if (flush) { - int count = newAudioProcessors.size(); - audioProcessors = newAudioProcessors.toArray(new AudioProcessor[count]); - outputBuffers = new ByteBuffer[count]; - for (int i = 0; i < count; i++) { - AudioProcessor audioProcessor = audioProcessors[i]; - audioProcessor.flush(); - outputBuffers[i] = audioProcessor.getOutput(); - } + resetAudioProcessors(); } } @@ -603,6 +587,28 @@ public final class AudioTrack { : multipliedBufferSize; } bufferSizeUs = passthrough ? C.TIME_UNSET : framesToDurationUs(bufferSize / outputPcmFrameSize); + + // The old playback parameters may no longer be applicable so try to reset them now. + setPlaybackParameters(playbackParameters); + } + + private void resetAudioProcessors() { + ArrayList newAudioProcessors = new ArrayList<>(); + for (AudioProcessor audioProcessor : availableAudioProcessors) { + if (audioProcessor.isActive()) { + newAudioProcessors.add(audioProcessor); + } else { + audioProcessor.flush(); + } + } + int count = newAudioProcessors.size(); + audioProcessors = newAudioProcessors.toArray(new AudioProcessor[count]); + outputBuffers = new ByteBuffer[count]; + for (int i = 0; i < count; i++) { + AudioProcessor audioProcessor = audioProcessors[i]; + audioProcessor.flush(); + outputBuffers[i] = audioProcessor.getOutput(); + } } private void initialize() throws InitializationException { @@ -940,15 +946,42 @@ public final class AudioTrack { } /** - * Sets the playback parameters. Only available for {@link Util#SDK_INT} >= 23 + * Attempts to set the playback parameters and returns the active playback parameters, which may + * differ from those passed in. * - * @param playbackParams The playback parameters to be used by the - * {@link android.media.AudioTrack}. - * @throws UnsupportedOperationException if the Playback Parameters are not supported. That is, - * {@link Util#SDK_INT} < 23. + * @return The active playback parameters. */ - public void setPlaybackParams(PlaybackParams playbackParams) { - audioTrackUtil.setPlaybackParams(playbackParams); + public PlaybackParameters setPlaybackParameters(PlaybackParameters playbackParameters) { + if (passthrough) { + this.playbackParameters = PlaybackParameters.DEFAULT; + } else { + this.playbackParameters = new PlaybackParameters( + sonicAudioProcessor.setSpeed(playbackParameters.speed), + sonicAudioProcessor.setPitch(playbackParameters.pitch)); + // TODO: Avoid resetting the track, so that speed/pitch changes are seamless. + // See [Internal: b/36542189]. + reset(); + // Setting the playback parameters never changes the output format, so it is not necessary to + // reconfigure the processors, though they may have become active/inactive. + resetAudioProcessors(); + } + return this.playbackParameters; + } + + /** + * Gets the {@link PlaybackParameters}. + */ + public PlaybackParameters getPlaybackParameters() { + return playbackParameters; + } + + /** + * Returns the number of input frames corresponding to the specified number of output frames, + * taking into account any internal playback speed adjustment. + */ + private long scaleFrames(long outputFrameCount) { + return sonicAudioProcessor.isActive() ? sonicAudioProcessor.getInputFrames(outputFrameCount) + : outputFrameCount; } /** @@ -1145,7 +1178,7 @@ public final class AudioTrack { * Updates the audio track latency and playback position parameters. */ private void maybeSampleSyncParams() { - long playbackPositionUs = audioTrackUtil.getPlaybackHeadPositionUs(); + long playbackPositionUs = audioTrackUtil.getPositionUs(); if (playbackPositionUs == 0) { // The AudioTrack hasn't output anything yet. return; @@ -1441,15 +1474,15 @@ public final class AudioTrack { /** * Stops the audio track in a way that ensures media written to it is played out in full, and - * that {@link #getPlaybackHeadPosition()} and {@link #getPlaybackHeadPositionUs()} continue to - * increment as the remaining media is played out. + * that {@link #getPlaybackHeadPosition()} and {@link #getPositionUs()} continue to increment as + * the remaining media is played out. * - * @param submittedFrames The total number of frames that have been submitted. + * @param writtenFrames The total number of frames that have been written. */ - public void handleEndOfStream(long submittedFrames) { + public void handleEndOfStream(long writtenFrames) { stopPlaybackHeadPosition = getPlaybackHeadPosition(); stopTimestampUs = SystemClock.elapsedRealtime() * 1000; - endPlaybackHeadPosition = submittedFrames; + endPlaybackHeadPosition = writtenFrames; audioTrack.stop(); } @@ -1471,8 +1504,7 @@ public final class AudioTrack { * returns the playback head position as a long that will only wrap around if the value exceeds * {@link Long#MAX_VALUE} (which in practice will never happen). * - * @return {@link android.media.AudioTrack#getPlaybackHeadPosition()} of {@link #audioTrack} - * expressed as a long. + * @return The playback head position, in frames. */ public long getPlaybackHeadPosition() { if (stopTimestampUs != C.TIME_UNSET) { @@ -1507,9 +1539,9 @@ public final class AudioTrack { } /** - * Returns {@link #getPlaybackHeadPosition()} expressed as microseconds. + * Returns the duration of played media since reconfiguration, in microseconds. */ - public long getPlaybackHeadPositionUs() { + public long getPositionUs() { return (getPlaybackHeadPosition() * C.MICROS_PER_SECOND) / sampleRate; } @@ -1553,28 +1585,6 @@ public final class AudioTrack { throw new UnsupportedOperationException(); } - /** - * Sets the Playback Parameters to be used by the underlying {@link android.media.AudioTrack}. - * - * @param playbackParams The playback parameters to be used by the - * {@link android.media.AudioTrack}. - * @throws UnsupportedOperationException If Playback Parameters are not supported - * (i.e. {@link Util#SDK_INT} < 23). - */ - public void setPlaybackParams(PlaybackParams playbackParams) { - throw new UnsupportedOperationException(); - } - - /** - * Returns the configured playback speed according to the used Playback Parameters. If these are - * not supported, 1.0f(normal speed) is returned. - * - * @return The speed factor used by the underlying {@link android.media.AudioTrack}. - */ - public float getPlaybackSpeed() { - return 1.0f; - } - } @TargetApi(19) @@ -1626,43 +1636,4 @@ public final class AudioTrack { } - @TargetApi(23) - private static class AudioTrackUtilV23 extends AudioTrackUtilV19 { - - private PlaybackParams playbackParams; - private float playbackSpeed; - - public AudioTrackUtilV23() { - playbackSpeed = 1.0f; - } - - @Override - public void reconfigure(android.media.AudioTrack audioTrack, - boolean needsPassthroughWorkaround) { - super.reconfigure(audioTrack, needsPassthroughWorkaround); - maybeApplyPlaybackParams(); - } - - @Override - public void setPlaybackParams(PlaybackParams playbackParams) { - playbackParams = (playbackParams != null ? playbackParams : new PlaybackParams()) - .allowDefaults(); - this.playbackParams = playbackParams; - playbackSpeed = playbackParams.getSpeed(); - maybeApplyPlaybackParams(); - } - - @Override - public float getPlaybackSpeed() { - return playbackSpeed; - } - - private void maybeApplyPlaybackParams() { - if (audioTrack != null && playbackParams != null) { - audioTrack.setPlaybackParams(playbackParams); - } - } - - } - } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/audio/MediaCodecAudioRenderer.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/MediaCodecAudioRenderer.java index e34068861d..48c7462b03 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/audio/MediaCodecAudioRenderer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/audio/MediaCodecAudioRenderer.java @@ -19,12 +19,12 @@ import android.annotation.TargetApi; import android.media.MediaCodec; import android.media.MediaCrypto; import android.media.MediaFormat; -import android.media.PlaybackParams; import android.media.audiofx.Virtualizer; import android.os.Handler; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.ExoPlaybackException; import com.google.android.exoplayer2.Format; +import com.google.android.exoplayer2.PlaybackParameters; import com.google.android.exoplayer2.audio.AudioRendererEventListener.EventDispatcher; import com.google.android.exoplayer2.drm.DrmSessionManager; import com.google.android.exoplayer2.drm.FrameworkMediaCrypto; @@ -345,6 +345,16 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media return currentPositionUs; } + @Override + public PlaybackParameters setPlaybackParameters(PlaybackParameters playbackParameters) { + return audioTrack.setPlaybackParameters(playbackParameters); + } + + @Override + public PlaybackParameters getPlaybackParameters() { + return audioTrack.getPlaybackParameters(); + } + @Override protected boolean processOutputBuffer(long positionUs, long elapsedRealtimeUs, MediaCodec codec, ByteBuffer buffer, int bufferIndex, int bufferFlags, long bufferPresentationTimeUs, @@ -389,9 +399,6 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media case C.MSG_SET_VOLUME: audioTrack.setVolume((Float) message); break; - case C.MSG_SET_PLAYBACK_PARAMS: - audioTrack.setPlaybackParams((PlaybackParams) message); - break; case C.MSG_SET_STREAM_TYPE: @C.StreamType int streamType = (Integer) message; audioTrack.setStreamType(streamType); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/audio/SimpleDecoderAudioRenderer.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/SimpleDecoderAudioRenderer.java index 5594d9a90e..3a50a8244a 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/audio/SimpleDecoderAudioRenderer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/audio/SimpleDecoderAudioRenderer.java @@ -15,7 +15,6 @@ */ package com.google.android.exoplayer2.audio; -import android.media.PlaybackParams; import android.media.audiofx.Virtualizer; import android.os.Handler; import android.os.Looper; @@ -26,6 +25,7 @@ import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.ExoPlaybackException; import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.FormatHolder; +import com.google.android.exoplayer2.PlaybackParameters; import com.google.android.exoplayer2.audio.AudioRendererEventListener.EventDispatcher; import com.google.android.exoplayer2.decoder.DecoderCounters; import com.google.android.exoplayer2.decoder.DecoderInputBuffer; @@ -434,6 +434,16 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements return currentPositionUs; } + @Override + public PlaybackParameters setPlaybackParameters(PlaybackParameters playbackParameters) { + return audioTrack.setPlaybackParameters(playbackParameters); + } + + @Override + public PlaybackParameters getPlaybackParameters() { + return audioTrack.getPlaybackParameters(); + } + @Override protected void onEnabled(boolean joining) throws ExoPlaybackException { decoderCounters = new DecoderCounters(); @@ -585,9 +595,6 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements case C.MSG_SET_VOLUME: audioTrack.setVolume((Float) message); break; - case C.MSG_SET_PLAYBACK_PARAMS: - audioTrack.setPlaybackParams((PlaybackParams) message); - break; case C.MSG_SET_STREAM_TYPE: @C.StreamType int streamType = (Integer) message; audioTrack.setStreamType(streamType); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/audio/Sonic.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/Sonic.java new file mode 100644 index 0000000000..40c52f13c2 --- /dev/null +++ b/library/core/src/main/java/com/google/android/exoplayer2/audio/Sonic.java @@ -0,0 +1,817 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * Copyright (C) 2010 Bill Cox, Sonic Library + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.exoplayer2.audio; + +/** + * Sonic audio time/pitch stretching library. Based on https://github.com/waywardgeek/sonic. + */ +/* package */ final class Sonic { + + private static final int SONIC_MIN_PITCH = 65; + private static final int SONIC_MAX_PITCH = 400; + /* This is used to down-sample some inputs to improve speed */ + private static final int SONIC_AMDF_FREQ = 4000; + + private short[] inputBuffer; + private short[] outputBuffer; + private short[] pitchBuffer; + private short[] downSampleBuffer; + private float speed; + private float volume; + private float pitch; + private float rate; + private int oldRatePosition; + private int newRatePosition; + private boolean useChordPitch; + private int quality; + private int numChannels; + private int inputBufferSize; + private int pitchBufferSize; + private int outputBufferSize; + private int numInputSamples; + private int numOutputSamples; + private int numPitchSamples; + private int minPeriod; + private int maxPeriod; + private int maxRequired; + private int remainingInputToCopy; + private int sampleRate; + private int prevPeriod; + private int prevMinDiff; + private int minDiff; + private int maxDiff; + + // Resize the array. + private short[] resize(short[] oldArray, int newLength) { + newLength *= numChannels; + short[] newArray = new short[newLength]; + int length = Math.min(oldArray.length, newLength); + + System.arraycopy(oldArray, 0, newArray, 0, length); + return newArray; + } + + // Move samples from one array to another. May move samples down within an array, but not up. + private void move(short[] dest, int destPos, short[] source, int sourcePos, int numSamples) { + System.arraycopy( + source, sourcePos * numChannels, dest, destPos * numChannels, numSamples * numChannels); + } + + // Scale the samples by the factor. + private void scaleSamples(short[] samples, int position, int numSamples, float volume) { + int fixedPointVolume = (int) (volume * 4096.0f); + int start = position * numChannels; + int stop = start + numSamples * numChannels; + + for (int xSample = start; xSample < stop; xSample++) { + int value = (samples[xSample] * fixedPointVolume) >> 12; + if (value > 32767) { + value = 32767; + } else if (value < -32767) { + value = -32767; + } + samples[xSample] = (short) value; + } + } + + // Get the speed of the stream. + public float getSpeed() { + return speed; + } + + // Set the speed of the stream. + public void setSpeed(float speed) { + this.speed = speed; + } + + // Get the pitch of the stream. + public float getPitch() { + return pitch; + } + + // Set the pitch of the stream. + public void setPitch(float pitch) { + this.pitch = pitch; + } + + // Get the rate of the stream. + public float getRate() { + return rate; + } + + // Set the playback rate of the stream. This scales pitch and speed at the same time. + public void setRate(float rate) { + this.rate = rate; + this.oldRatePosition = 0; + this.newRatePosition = 0; + } + + // Get the vocal chord pitch setting. + public boolean getChordPitch() { + return useChordPitch; + } + + // Set the vocal chord mode for pitch computation. Default is off. + public void setChordPitch(boolean useChordPitch) { + this.useChordPitch = useChordPitch; + } + + // Get the quality setting. + public int getQuality() { + return quality; + } + + // Set the "quality". Default 0 is virtually as good as 1, but very much faster. + public void setQuality(int quality) { + this.quality = quality; + } + + // Get the scaling factor of the stream. + public float getVolume() { + return volume; + } + + // Set the scaling factor of the stream. + public void setVolume(float volume) { + this.volume = volume; + } + + // Allocate stream buffers. + private void allocateStreamBuffers(int sampleRate, int numChannels) { + minPeriod = sampleRate / SONIC_MAX_PITCH; + maxPeriod = sampleRate / SONIC_MIN_PITCH; + maxRequired = 2 * maxPeriod; + inputBufferSize = maxRequired; + inputBuffer = new short[maxRequired * numChannels]; + outputBufferSize = maxRequired; + outputBuffer = new short[maxRequired * numChannels]; + pitchBufferSize = maxRequired; + pitchBuffer = new short[maxRequired * numChannels]; + downSampleBuffer = new short[maxRequired]; + this.sampleRate = sampleRate; + this.numChannels = numChannels; + oldRatePosition = 0; + newRatePosition = 0; + prevPeriod = 0; + } + + // Create a sonic stream. + public Sonic(int sampleRate, int numChannels) { + allocateStreamBuffers(sampleRate, numChannels); + speed = 1.0f; + pitch = 1.0f; + volume = 1.0f; + rate = 1.0f; + oldRatePosition = 0; + newRatePosition = 0; + useChordPitch = false; + quality = 0; + } + + // Get the sample rate of the stream. + public int getSampleRate() { + return sampleRate; + } + + // Set the sample rate of the stream. This will cause samples buffered in the stream to be lost. + public void setSampleRate(int sampleRate) { + allocateStreamBuffers(sampleRate, numChannels); + } + + // Get the number of channels. + public int getNumChannels() { + return numChannels; + } + + // Set the num channels of the stream. This will cause samples buffered in the stream to be lost. + public void setNumChannels(int numChannels) { + allocateStreamBuffers(sampleRate, numChannels); + } + + // Enlarge the output buffer if needed. + private void enlargeOutputBufferIfNeeded(int numSamples) { + if (numOutputSamples + numSamples > outputBufferSize) { + outputBufferSize += (outputBufferSize >> 1) + numSamples; + outputBuffer = resize(outputBuffer, outputBufferSize); + } + } + + // Enlarge the input buffer if needed. + private void enlargeInputBufferIfNeeded(int numSamples) { + if (numInputSamples + numSamples > inputBufferSize) { + inputBufferSize += (inputBufferSize >> 1) + numSamples; + inputBuffer = resize(inputBuffer, inputBufferSize); + } + } + + // Add the input samples to the input buffer. + private void addFloatSamplesToInputBuffer(float[] samples, int numSamples) { + if (numSamples == 0) { + return; + } + enlargeInputBufferIfNeeded(numSamples); + int xBuffer = numInputSamples * numChannels; + for (int xSample = 0; xSample < numSamples * numChannels; xSample++) { + inputBuffer[xBuffer++] = (short) (samples[xSample] * 32767.0f); + } + numInputSamples += numSamples; + } + + // Add the input samples to the input buffer. + private void addShortSamplesToInputBuffer(short[] samples, int numSamples) { + if (numSamples == 0) { + return; + } + enlargeInputBufferIfNeeded(numSamples); + move(inputBuffer, numInputSamples, samples, 0, numSamples); + numInputSamples += numSamples; + } + + // Add the input samples to the input buffer. + private void addUnsignedByteSamplesToInputBuffer(byte[] samples, int numSamples) { + short sample; + + enlargeInputBufferIfNeeded(numSamples); + int xBuffer = numInputSamples * numChannels; + for (int xSample = 0; xSample < numSamples * numChannels; xSample++) { + sample = (short) ((samples[xSample] & 0xff) - 128); // Convert from unsigned to signed + inputBuffer[xBuffer++] = (short) (sample << 8); + } + numInputSamples += numSamples; + } + + // Add the input samples to the input buffer. They must be 16-bit little-endian encoded in a byte + // array. + private void addBytesToInputBuffer(byte[] inBuffer, int numBytes) { + int numSamples = numBytes / (2 * numChannels); + short sample; + + enlargeInputBufferIfNeeded(numSamples); + int xBuffer = numInputSamples * numChannels; + for (int xByte = 0; xByte + 1 < numBytes; xByte += 2) { + sample = (short) ((inBuffer[xByte] & 0xff) | (inBuffer[xByte + 1] << 8)); + inputBuffer[xBuffer++] = sample; + } + numInputSamples += numSamples; + } + + // Remove input samples that we have already processed. + private void removeInputSamples(int position) { + int remainingSamples = numInputSamples - position; + + move(inputBuffer, 0, inputBuffer, position, remainingSamples); + numInputSamples = remainingSamples; + } + + // Just copy from the array to the output buffer + private void copyToOutput(short[] samples, int position, int numSamples) { + enlargeOutputBufferIfNeeded(numSamples); + move(outputBuffer, numOutputSamples, samples, position, numSamples); + numOutputSamples += numSamples; + } + + // Just copy from the input buffer to the output buffer. Return num samples copied. + private int copyInputToOutput(int position) { + int numSamples = remainingInputToCopy; + + if (numSamples > maxRequired) { + numSamples = maxRequired; + } + copyToOutput(inputBuffer, position, numSamples); + remainingInputToCopy -= numSamples; + return numSamples; + } + + // Read data out of the stream. Sometimes no data will be available, and zero + // is returned, which is not an error condition. + public int readFloatFromStream(float[] samples, int maxSamples) { + int numSamples = numOutputSamples; + int remainingSamples = 0; + + if (numSamples == 0) { + return 0; + } + if (numSamples > maxSamples) { + remainingSamples = numSamples - maxSamples; + numSamples = maxSamples; + } + for (int xSample = 0; xSample < numSamples * numChannels; xSample++) { + samples[xSample++] = (outputBuffer[xSample]) / 32767.0f; + } + move(outputBuffer, 0, outputBuffer, numSamples, remainingSamples); + numOutputSamples = remainingSamples; + return numSamples; + } + + // Read short data out of the stream. Sometimes no data will be available, and zero + // is returned, which is not an error condition. + public int readShortFromStream(short[] samples, int maxSamples) { + int numSamples = numOutputSamples; + int remainingSamples = 0; + + if (numSamples == 0) { + return 0; + } + if (numSamples > maxSamples) { + remainingSamples = numSamples - maxSamples; + numSamples = maxSamples; + } + move(samples, 0, outputBuffer, 0, numSamples); + move(outputBuffer, 0, outputBuffer, numSamples, remainingSamples); + numOutputSamples = remainingSamples; + return numSamples; + } + + // Read unsigned byte data out of the stream. Sometimes no data will be available, and zero + // is returned, which is not an error condition. + public int readBytesFromStream(byte[] outBuffer, int maxBytes) { + int maxSamples = maxBytes / (2 * numChannels); + int numSamples = numOutputSamples; + int remainingSamples = 0; + + if (numSamples == 0 || maxSamples == 0) { + return 0; + } + if (numSamples > maxSamples) { + remainingSamples = numSamples - maxSamples; + numSamples = maxSamples; + } + for (int xSample = 0; xSample < numSamples * numChannels; xSample++) { + short sample = outputBuffer[xSample]; + outBuffer[xSample << 1] = (byte) (sample & 0xff); + outBuffer[(xSample << 1) + 1] = (byte) (sample >> 8); + } + move(outputBuffer, 0, outputBuffer, numSamples, remainingSamples); + numOutputSamples = remainingSamples; + return 2 * numSamples * numChannels; + } + + // Force the sonic stream to generate output using whatever data it currently + // has. No extra delay will be added to the output, but flushing in the middle of + // words could introduce distortion. + public void flushStream() { + int remainingSamples = numInputSamples; + float s = speed / pitch; + float r = rate * pitch; + int expectedOutputSamples = + numOutputSamples + (int) ((remainingSamples / s + numPitchSamples) / r + 0.5f); + + // Add enough silence to flush both input and pitch buffers. + enlargeInputBufferIfNeeded(remainingSamples + 2 * maxRequired); + for (int xSample = 0; xSample < 2 * maxRequired * numChannels; xSample++) { + inputBuffer[remainingSamples * numChannels + xSample] = 0; + } + numInputSamples += 2 * maxRequired; + writeShortToStream(null, 0); + // Throw away any extra samples we generated due to the silence we added. + if (numOutputSamples > expectedOutputSamples) { + numOutputSamples = expectedOutputSamples; + } + // Empty input and pitch buffers. + numInputSamples = 0; + remainingInputToCopy = 0; + numPitchSamples = 0; + } + + // Return the number of samples in the output buffer + public int samplesAvailable() { + return numOutputSamples; + } + + // If skip is greater than one, average skip samples together and write them to + // the down-sample buffer. If numChannels is greater than one, mix the channels + // together as we down sample. + private void downSampleInput(short[] samples, int position, int skip) { + int numSamples = maxRequired / skip; + int samplesPerValue = numChannels * skip; + int value; + + position *= numChannels; + for (int i = 0; i < numSamples; i++) { + value = 0; + for (int j = 0; j < samplesPerValue; j++) { + value += samples[position + i * samplesPerValue + j]; + } + value /= samplesPerValue; + downSampleBuffer[i] = (short) value; + } + } + + // Find the best frequency match in the range, and given a sample skip multiple. + // For now, just find the pitch of the first channel. + private int findPitchPeriodInRange(short[] samples, int position, int minPeriod, int maxPeriod) { + int bestPeriod = 0; + int worstPeriod = 255; + int minDiff = 1; + int maxDiff = 0; + + position *= numChannels; + for (int period = minPeriod; period <= maxPeriod; period++) { + int diff = 0; + for (int i = 0; i < period; i++) { + short sVal = samples[position + i]; + short pVal = samples[position + period + i]; + diff += sVal >= pVal ? sVal - pVal : pVal - sVal; + } + // Note that the highest number of samples we add into diff will be less than 256, since we + // skip samples. Thus, diff is a 24 bit number, and we can safely multiply by numSamples + // without overflow. + if (diff * bestPeriod < minDiff * period) { + minDiff = diff; + bestPeriod = period; + } + if (diff * worstPeriod > maxDiff * period) { + maxDiff = diff; + worstPeriod = period; + } + } + this.minDiff = minDiff / bestPeriod; + this.maxDiff = maxDiff / worstPeriod; + + return bestPeriod; + } + + // At abrupt ends of voiced words, we can have pitch periods that are better + // approximated by the previous pitch period estimate. Try to detect this case. + private boolean prevPeriodBetter(int minDiff, int maxDiff, boolean preferNewPeriod) { + if (minDiff == 0 || prevPeriod == 0) { + return false; + } + if (preferNewPeriod) { + if (maxDiff > minDiff * 3) { + // Got a reasonable match this period + return false; + } + if (minDiff * 2 <= prevMinDiff * 3) { + // Mismatch is not that much greater this period + return false; + } + } else { + if (minDiff <= prevMinDiff) { + return false; + } + } + return true; + } + + // Find the pitch period. This is a critical step, and we may have to try + // multiple ways to get a good answer. This version uses AMDF. To improve + // speed, we down sample by an integer factor get in the 11KHz range, and then + // do it again with a narrower frequency range without down sampling + private int findPitchPeriod(short[] samples, int position, boolean preferNewPeriod) { + int period; + int retPeriod; + int skip = 1; + + if (sampleRate > SONIC_AMDF_FREQ && quality == 0) { + skip = sampleRate / SONIC_AMDF_FREQ; + } + if (numChannels == 1 && skip == 1) { + period = findPitchPeriodInRange(samples, position, minPeriod, maxPeriod); + } else { + downSampleInput(samples, position, skip); + period = findPitchPeriodInRange(downSampleBuffer, 0, minPeriod / skip, maxPeriod / skip); + if (skip != 1) { + period *= skip; + int minP = period - (skip << 2); + int maxP = period + (skip << 2); + if (minP < minPeriod) { + minP = minPeriod; + } + if (maxP > maxPeriod) { + maxP = maxPeriod; + } + if (numChannels == 1) { + period = findPitchPeriodInRange(samples, position, minP, maxP); + } else { + downSampleInput(samples, position, 1); + period = findPitchPeriodInRange(downSampleBuffer, 0, minP, maxP); + } + } + } + if (prevPeriodBetter(minDiff, maxDiff, preferNewPeriod)) { + retPeriod = prevPeriod; + } else { + retPeriod = period; + } + prevMinDiff = minDiff; + prevPeriod = period; + return retPeriod; + } + + // Overlap two sound segments, ramp the volume of one down, while ramping the + // other one from zero up, and add them, storing the result at the output. + private static void overlapAdd(int numSamples, int numChannels, short[] out, int outPos, + short[] rampDown, int rampDownPos, short[] rampUp, int rampUpPos) { + for (int i = 0; i < numChannels; i++) { + int o = outPos * numChannels + i; + int u = rampUpPos * numChannels + i; + int d = rampDownPos * numChannels + i; + for (int t = 0; t < numSamples; t++) { + out[o] = (short) ((rampDown[d] * (numSamples - t) + rampUp[u] * t) / numSamples); + o += numChannels; + d += numChannels; + u += numChannels; + } + } + } + + // Overlap two sound segments, ramp the volume of one down, while ramping the + // other one from zero up, and add them, storing the result at the output. + private static void overlapAddWithSeparation(int numSamples, int numChannels, int separation, + short[] out, int outPos, short[] rampDown, int rampDownPos, short[] rampUp, int rampUpPos) { + for (int i = 0; i < numChannels; i++) { + int o = outPos * numChannels + i; + int u = rampUpPos * numChannels + i; + int d = rampDownPos * numChannels + i; + for (int t = 0; t < numSamples + separation; t++) { + if (t < separation) { + out[o] = (short) (rampDown[d] * (numSamples - t) / numSamples); + d += numChannels; + } else if (t < numSamples) { + out[o] = + (short) ((rampDown[d] * (numSamples - t) + rampUp[u] * (t - separation)) + / numSamples); + d += numChannels; + u += numChannels; + } else { + out[o] = (short) (rampUp[u] * (t - separation) / numSamples); + u += numChannels; + } + o += numChannels; + } + } + } + + // Just move the new samples in the output buffer to the pitch buffer + private void moveNewSamplesToPitchBuffer(int originalNumOutputSamples) { + int numSamples = numOutputSamples - originalNumOutputSamples; + + if (numPitchSamples + numSamples > pitchBufferSize) { + pitchBufferSize += (pitchBufferSize >> 1) + numSamples; + pitchBuffer = resize(pitchBuffer, pitchBufferSize); + } + move(pitchBuffer, numPitchSamples, outputBuffer, originalNumOutputSamples, numSamples); + numOutputSamples = originalNumOutputSamples; + numPitchSamples += numSamples; + } + + // Remove processed samples from the pitch buffer. + private void removePitchSamples(int numSamples) { + if (numSamples == 0) { + return; + } + move(pitchBuffer, 0, pitchBuffer, numSamples, numPitchSamples - numSamples); + numPitchSamples -= numSamples; + } + + // Change the pitch. The latency this introduces could be reduced by looking at + // past samples to determine pitch, rather than future. + private void adjustPitch(int originalNumOutputSamples) { + int period; + int newPeriod; + int separation; + int position = 0; + + if (numOutputSamples == originalNumOutputSamples) { + return; + } + moveNewSamplesToPitchBuffer(originalNumOutputSamples); + while (numPitchSamples - position >= maxRequired) { + period = findPitchPeriod(pitchBuffer, position, false); + newPeriod = (int) (period / pitch); + enlargeOutputBufferIfNeeded(newPeriod); + if (pitch >= 1.0f) { + overlapAdd(newPeriod, numChannels, outputBuffer, numOutputSamples, pitchBuffer, position, + pitchBuffer, position + period - newPeriod); + } else { + separation = newPeriod - period; + overlapAddWithSeparation(period, numChannels, separation, outputBuffer, numOutputSamples, + pitchBuffer, position, pitchBuffer, position); + } + numOutputSamples += newPeriod; + position += period; + } + removePitchSamples(position); + } + + // Interpolate the new output sample. + private short interpolate(short[] in, int inPos, int oldSampleRate, int newSampleRate) { + short left = in[inPos * numChannels]; + short right = in[inPos * numChannels + numChannels]; + int position = newRatePosition * oldSampleRate; + int leftPosition = oldRatePosition * newSampleRate; + int rightPosition = (oldRatePosition + 1) * newSampleRate; + int ratio = rightPosition - position; + int width = rightPosition - leftPosition; + + return (short) ((ratio * left + (width - ratio) * right) / width); + } + + // Change the rate. + private void adjustRate(float rate, int originalNumOutputSamples) { + int newSampleRate = (int) (sampleRate / rate); + int oldSampleRate = sampleRate; + int position; + + // Set these values to help with the integer math + while (newSampleRate > (1 << 14) || oldSampleRate > (1 << 14)) { + newSampleRate >>= 1; + oldSampleRate >>= 1; + } + if (numOutputSamples == originalNumOutputSamples) { + return; + } + moveNewSamplesToPitchBuffer(originalNumOutputSamples); + // Leave at least one pitch sample in the buffer + for (position = 0; position < numPitchSamples - 1; position++) { + while ((oldRatePosition + 1) * newSampleRate > newRatePosition * oldSampleRate) { + enlargeOutputBufferIfNeeded(1); + for (int i = 0; i < numChannels; i++) { + outputBuffer[numOutputSamples * numChannels + i] = + interpolate(pitchBuffer, position + i, oldSampleRate, newSampleRate); + } + newRatePosition++; + numOutputSamples++; + } + oldRatePosition++; + if (oldRatePosition == oldSampleRate) { + oldRatePosition = 0; + if (newRatePosition != newSampleRate) { + System.out.printf("Assertion failed: newRatePosition != newSampleRate\n"); + assert false; + } + newRatePosition = 0; + } + } + removePitchSamples(position); + } + + // Skip over a pitch period, and copy period/speed samples to the output + private int skipPitchPeriod(short[] samples, int position, float speed, int period) { + int newSamples; + + if (speed >= 2.0f) { + newSamples = (int) (period / (speed - 1.0f)); + } else { + newSamples = period; + remainingInputToCopy = (int) (period * (2.0f - speed) / (speed - 1.0f)); + } + enlargeOutputBufferIfNeeded(newSamples); + overlapAdd(newSamples, numChannels, outputBuffer, numOutputSamples, samples, position, samples, + position + period); + numOutputSamples += newSamples; + return newSamples; + } + + // Insert a pitch period, and determine how much input to copy directly. + private int insertPitchPeriod(short[] samples, int position, float speed, int period) { + int newSamples; + + if (speed < 0.5f) { + newSamples = (int) (period * speed / (1.0f - speed)); + } else { + newSamples = period; + remainingInputToCopy = (int) (period * (2.0f * speed - 1.0f) / (1.0f - speed)); + } + enlargeOutputBufferIfNeeded(period + newSamples); + move(outputBuffer, numOutputSamples, samples, position, period); + overlapAdd(newSamples, numChannels, outputBuffer, numOutputSamples + period, samples, + position + period, samples, position); + numOutputSamples += period + newSamples; + return newSamples; + } + + // Resample as many pitch periods as we have buffered on the input. Return 0 if + // we fail to resize an input or output buffer. Also scale the output by the volume. + private void changeSpeed(float speed) { + int numSamples = numInputSamples; + int position = 0; + int period; + int newSamples; + + if (numInputSamples < maxRequired) { + return; + } + do { + if (remainingInputToCopy > 0) { + newSamples = copyInputToOutput(position); + position += newSamples; + } else { + period = findPitchPeriod(inputBuffer, position, true); + if (speed > 1.0) { + newSamples = skipPitchPeriod(inputBuffer, position, speed, period); + position += period + newSamples; + } else { + newSamples = insertPitchPeriod(inputBuffer, position, speed, period); + position += newSamples; + } + } + } while (position + maxRequired <= numSamples); + removeInputSamples(position); + } + + // Resample as many pitch periods as we have buffered on the input. Scale the output by the + // volume. + private void processStreamInput() { + int originalNumOutputSamples = numOutputSamples; + float s = speed / pitch; + float r = rate; + + if (!useChordPitch) { + r *= pitch; + } + if (s > 1.00001 || s < 0.99999) { + changeSpeed(s); + } else { + copyToOutput(inputBuffer, 0, numInputSamples); + numInputSamples = 0; + } + if (useChordPitch) { + if (pitch != 1.0f) { + adjustPitch(originalNumOutputSamples); + } + } else if (r != 1.0f) { + adjustRate(r, originalNumOutputSamples); + } + if (volume != 1.0f) { + // Adjust output volume. + scaleSamples(outputBuffer, originalNumOutputSamples, + numOutputSamples - originalNumOutputSamples, volume); + } + } + + // Write floating point data to the input buffer and process it. + public void writeFloatToStream(float[] samples, int numSamples) { + addFloatSamplesToInputBuffer(samples, numSamples); + processStreamInput(); + } + + // Write the data to the input stream, and process it. + public void writeShortToStream(short[] samples, int numSamples) { + addShortSamplesToInputBuffer(samples, numSamples); + processStreamInput(); + } + + // Simple wrapper around sonicWriteFloatToStream that does the unsigned byte to short + // conversion for you. + public void writeUnsignedByteToStream(byte[] samples, int numSamples) { + addUnsignedByteSamplesToInputBuffer(samples, numSamples); + processStreamInput(); + } + + // Simple wrapper around sonicWriteBytesToStream that does the byte to 16-bit LE conversion. + public void writeBytesToStream(byte[] inBuffer, int numBytes) { + addBytesToInputBuffer(inBuffer, numBytes); + processStreamInput(); + } + + // This is a non-stream oriented interface to just change the speed of a sound sample + public static int changeFloatSpeed(float[] samples, int numSamples, float speed, float pitch, + float rate, float volume, boolean useChordPitch, int sampleRate, int numChannels) { + Sonic stream = new Sonic(sampleRate, numChannels); + + stream.setSpeed(speed); + stream.setPitch(pitch); + stream.setRate(rate); + stream.setVolume(volume); + stream.setChordPitch(useChordPitch); + stream.writeFloatToStream(samples, numSamples); + stream.flushStream(); + numSamples = stream.samplesAvailable(); + stream.readFloatFromStream(samples, numSamples); + return numSamples; + } + + /* This is a non-stream oriented interface to just change the speed of a sound sample */ + public int sonicChangeShortSpeed(short[] samples, int numSamples, float speed, float pitch, + float rate, float volume, boolean useChordPitch, int sampleRate, int numChannels) { + Sonic stream = new Sonic(sampleRate, numChannels); + + stream.setSpeed(speed); + stream.setPitch(pitch); + stream.setRate(rate); + stream.setVolume(volume); + stream.setChordPitch(useChordPitch); + stream.writeShortToStream(samples, numSamples); + stream.flushStream(); + numSamples = stream.samplesAvailable(); + stream.readShortFromStream(samples, numSamples); + return numSamples; + } + +} diff --git a/library/core/src/main/java/com/google/android/exoplayer2/audio/SonicAudioProcessor.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/SonicAudioProcessor.java new file mode 100644 index 0000000000..ae1fae40c2 --- /dev/null +++ b/library/core/src/main/java/com/google/android/exoplayer2/audio/SonicAudioProcessor.java @@ -0,0 +1,205 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.exoplayer2.audio; + +import com.google.android.exoplayer2.C; +import com.google.android.exoplayer2.C.Encoding; +import com.google.android.exoplayer2.Format; +import com.google.android.exoplayer2.util.Util; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +/** + * An {@link AudioProcessor} that uses the Sonic library to modify the speed/pitch of audio. + */ +// TODO: Make public once it is possible to override AudioTrack's position calculations. +/* package */ final class SonicAudioProcessor implements AudioProcessor { + + /** + * The maximum allowed playback speed in {@link #setSpeed(float)}. + */ + public static final float MAXIMUM_SPEED = 8.0f; + /** + * The minimum allowed playback speed in {@link #setSpeed(float)}. + */ + public static final float MINIMUM_SPEED = 0.1f; + /** + * The maximum allowed pitch in {@link #setPitch(float)}. + */ + public static final float MAXIMUM_PITCH = 8.0f; + /** + * The minimum allowed pitch in {@link #setPitch(float)}. + */ + public static final float MINIMUM_PITCH = 0.1f; + + /** + * The threshold below which the difference between two pitch/speed factors is negligible. + */ + private static final float CLOSE_THRESHOLD = 0.01f; + + private static final byte[] EMPTY_ARRAY = new byte[0]; + + private int channelCount; + private int sampleRateHz; + + private Sonic sonic; + private float speed; + private float pitch; + + private byte[] inputArray; + private ByteBuffer buffer; + private byte[] bufferArray; + private ByteBuffer outputBuffer; + private long inputBytes; + private long outputBytes; + private boolean inputEnded; + + /** + * Creates a new Sonic audio processor. + */ + public SonicAudioProcessor() { + speed = 1f; + pitch = 1f; + channelCount = Format.NO_VALUE; + sampleRateHz = Format.NO_VALUE; + buffer = EMPTY_BUFFER; + outputBuffer = EMPTY_BUFFER; + inputArray = EMPTY_ARRAY; + bufferArray = EMPTY_ARRAY; + } + + /** + * Sets the playback speed. The new speed will take effect after a call to {@link #flush()}. + * + * @param speed The requested new playback speed. + * @return The actual new playback speed. + */ + public float setSpeed(float speed) { + this.speed = Util.constrainValue(speed, MINIMUM_SPEED, MAXIMUM_SPEED); + return this.speed; + } + + /** + * Sets the playback pitch. The new pitch will take effect after a call to {@link #flush()}. + * + * @param pitch The requested new pitch. + * @return The actual new pitch. + */ + public float setPitch(float pitch) { + this.pitch = Util.constrainValue(pitch, MINIMUM_PITCH, MAXIMUM_PITCH); + return pitch; + } + + /** + * Returns the number of input frames corresponding to the specified number of output frames. + */ + public long getInputFrames(long outputFrames) { + // Sonic produces output data as soon as input is queued. + return outputBytes == 0 ? 0 : Util.scaleLargeTimestamp(outputFrames, inputBytes, outputBytes); + } + + @Override + public boolean configure(int sampleRateHz, int channelCount, @Encoding int encoding) + throws UnhandledFormatException { + if (encoding != C.ENCODING_PCM_16BIT) { + throw new UnhandledFormatException(sampleRateHz, channelCount, encoding); + } + if (this.sampleRateHz == sampleRateHz && this.channelCount == channelCount) { + return false; + } + this.sampleRateHz = sampleRateHz; + this.channelCount = channelCount; + return true; + } + + @Override + public boolean isActive() { + return Math.abs(speed - 1f) >= CLOSE_THRESHOLD || Math.abs(pitch - 1f) >= CLOSE_THRESHOLD; + } + + @Override + public int getOutputChannelCount() { + return channelCount; + } + + @Override + public int getOutputEncoding() { + return C.ENCODING_PCM_16BIT; + } + + @Override + public void queueInput(ByteBuffer inputBuffer) { + // TODO: Remove this extra copy. + int inputBytesToRead = inputBuffer.remaining(); + if (inputArray == null || inputArray.length < inputBytesToRead) { + inputArray = new byte[inputBytesToRead]; + } + inputBuffer.get(inputArray, 0, inputBytesToRead); + sonic.writeBytesToStream(inputArray, inputBytesToRead); + int outputSize = sonic.samplesAvailable() * channelCount * 2; + if (buffer.capacity() < outputSize) { + buffer = ByteBuffer.allocateDirect(outputSize).order(ByteOrder.nativeOrder()); + bufferArray = new byte[outputSize]; + } else { + buffer.clear(); + } + inputBytes += inputBytesToRead; + int outputBytesRead = sonic.readBytesFromStream(bufferArray, outputSize); + buffer.put(bufferArray, 0, outputBytesRead); + buffer.flip(); + outputBytes += outputSize; + outputBuffer = buffer; + } + + @Override + public void queueEndOfStream() { + sonic.flushStream(); + inputEnded = true; + } + + @Override + public ByteBuffer getOutput() { + ByteBuffer outputBuffer = this.outputBuffer; + this.outputBuffer = EMPTY_BUFFER; + return outputBuffer; + } + + @Override + public boolean isEnded() { + return inputEnded && (sonic == null || sonic.samplesAvailable() == 0); + } + + @Override + public void flush() { + sonic = new Sonic(sampleRateHz, channelCount); + sonic.setSpeed(speed); + sonic.setPitch(pitch); + outputBuffer = EMPTY_BUFFER; + inputBytes = 0; + outputBytes = 0; + inputEnded = false; + } + + @Override + public void release() { + sonic = null; + buffer = EMPTY_BUFFER; + outputBuffer = EMPTY_BUFFER; + inputArray = EMPTY_ARRAY; + bufferArray = EMPTY_ARRAY; + } + +} diff --git a/library/core/src/main/java/com/google/android/exoplayer2/util/MediaClock.java b/library/core/src/main/java/com/google/android/exoplayer2/util/MediaClock.java index 5f42a40e04..a10298e456 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/util/MediaClock.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/util/MediaClock.java @@ -15,6 +15,8 @@ */ package com.google.android.exoplayer2.util; +import com.google.android.exoplayer2.PlaybackParameters; + /** * Tracks the progression of media time. */ @@ -25,4 +27,18 @@ public interface MediaClock { */ long getPositionUs(); + /** + * Attempts to set the playback parameters and returns the active playback parameters, which may + * differ from those passed in. + * + * @param playbackParameters The playback parameters. + * @return The active playback parameters. + */ + PlaybackParameters setPlaybackParameters(PlaybackParameters playbackParameters); + + /** + * Returns the active playback parameters. + */ + PlaybackParameters getPlaybackParameters(); + } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/util/StandaloneMediaClock.java b/library/core/src/main/java/com/google/android/exoplayer2/util/StandaloneMediaClock.java index 0ae9b40869..5b8d117dd0 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/util/StandaloneMediaClock.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/util/StandaloneMediaClock.java @@ -16,33 +16,34 @@ package com.google.android.exoplayer2.util; import android.os.SystemClock; +import com.google.android.exoplayer2.C; +import com.google.android.exoplayer2.PlaybackParameters; /** - * A standalone {@link MediaClock}. The clock can be started, stopped and its time can be set and - * retrieved. When started, this clock is based on {@link SystemClock#elapsedRealtime()}. + * A {@link MediaClock} whose position advances with real time based on the playback parameters when + * started. */ public final class StandaloneMediaClock implements MediaClock { private boolean started; + private long baseUs; + private long baseElapsedMs; + private PlaybackParameters playbackParameters; /** - * The media time when the clock was last set or stopped. + * Creates a new standalone media clock. */ - private long positionUs; - - /** - * The difference between {@link SystemClock#elapsedRealtime()} and {@link #positionUs} - * when the clock was last set or started. - */ - private long deltaUs; + public StandaloneMediaClock() { + playbackParameters = PlaybackParameters.DEFAULT; + } /** * Starts the clock. Does nothing if the clock is already started. */ public void start() { if (!started) { + baseElapsedMs = SystemClock.elapsedRealtime(); started = true; - deltaUs = elapsedRealtimeMinus(positionUs); } } @@ -51,26 +52,60 @@ public final class StandaloneMediaClock implements MediaClock { */ public void stop() { if (started) { - positionUs = elapsedRealtimeMinus(deltaUs); + setPositionUs(getPositionUs()); started = false; } } /** - * @param timeUs The position to set in microseconds. + * Sets the clock's position. + * + * @param positionUs The position to set in microseconds. */ - public void setPositionUs(long timeUs) { - this.positionUs = timeUs; - deltaUs = elapsedRealtimeMinus(timeUs); + public void setPositionUs(long positionUs) { + baseUs = positionUs; + if (started) { + baseElapsedMs = SystemClock.elapsedRealtime(); + } + } + + /** + * Synchronizes this clock with the current state of {@code clock}. + * + * @param clock The clock with which to synchronize. + */ + public void synchronize(MediaClock clock) { + setPositionUs(clock.getPositionUs()); + playbackParameters = clock.getPlaybackParameters(); } @Override public long getPositionUs() { - return started ? elapsedRealtimeMinus(deltaUs) : positionUs; + long positionUs = baseUs; + if (started) { + long elapsedSinceBaseMs = SystemClock.elapsedRealtime() - baseElapsedMs; + if (playbackParameters.speed == 1f) { + positionUs += C.msToUs(elapsedSinceBaseMs); + } else { + positionUs += playbackParameters.getSpeedAdjustedDurationUs(elapsedSinceBaseMs); + } + } + return positionUs; } - private long elapsedRealtimeMinus(long toSubtractUs) { - return SystemClock.elapsedRealtime() * 1000 - toSubtractUs; + @Override + public PlaybackParameters setPlaybackParameters(PlaybackParameters playbackParameters) { + // Store the current position as the new base, in case the playback speed has changed. + if (started) { + setPositionUs(getPositionUs()); + } + this.playbackParameters = playbackParameters; + return playbackParameters; + } + + @Override + public PlaybackParameters getPlaybackParameters() { + return playbackParameters; } } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/util/Util.java b/library/core/src/main/java/com/google/android/exoplayer2/util/Util.java index d9282700d7..e481066720 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/util/Util.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/util/Util.java @@ -309,6 +309,18 @@ public final class Util { return Math.max(min, Math.min(value, max)); } + /** + * Constrains a value to the specified bounds. + * + * @param value The value to constrain. + * @param min The lower bound. + * @param max The upper bound. + * @return The constrained value {@code Math.max(min, Math.min(value, max))}. + */ + public static float constrainValue(float value, float min, float max) { + return Math.max(min, Math.min(value, max)); + } + /** * Returns the index of the largest element in {@code array} that is less than (or optionally * equal to) a specified {@code value}. diff --git a/library/ui/src/main/java/com/google/android/exoplayer2/ui/DebugTextViewHelper.java b/library/ui/src/main/java/com/google/android/exoplayer2/ui/DebugTextViewHelper.java index 1bf5b59a4a..38c7a5be9c 100644 --- a/library/ui/src/main/java/com/google/android/exoplayer2/ui/DebugTextViewHelper.java +++ b/library/ui/src/main/java/com/google/android/exoplayer2/ui/DebugTextViewHelper.java @@ -19,6 +19,7 @@ import android.widget.TextView; import com.google.android.exoplayer2.ExoPlaybackException; import com.google.android.exoplayer2.ExoPlayer; import com.google.android.exoplayer2.Format; +import com.google.android.exoplayer2.PlaybackParameters; import com.google.android.exoplayer2.SimpleExoPlayer; import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.decoder.DecoderCounters; @@ -90,6 +91,11 @@ public final class DebugTextViewHelper implements Runnable, ExoPlayer.EventListe updateAndPost(); } + @Override + public void onPlaybackParametersChanged(PlaybackParameters playbackParameters) { + // Do nothing. + } + @Override public void onTimelineChanged(Timeline timeline, Object manifest) { // Do nothing. diff --git a/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlaybackControlView.java b/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlaybackControlView.java index 88a65589fc..757252297d 100644 --- a/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlaybackControlView.java +++ b/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlaybackControlView.java @@ -29,6 +29,7 @@ import android.widget.TextView; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.ExoPlaybackException; import com.google.android.exoplayer2.ExoPlayer; +import com.google.android.exoplayer2.PlaybackParameters; import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.source.TrackGroupArray; import com.google.android.exoplayer2.trackselection.TrackSelectionArray; @@ -766,6 +767,11 @@ public class PlaybackControlView extends FrameLayout { updateProgress(); } + @Override + public void onPlaybackParametersChanged(PlaybackParameters playbackParameters) { + // Do nothing. + } + @Override public void onTimelineChanged(Timeline timeline, Object manifest) { updateNavigation(); diff --git a/library/ui/src/main/java/com/google/android/exoplayer2/ui/SimpleExoPlayerView.java b/library/ui/src/main/java/com/google/android/exoplayer2/ui/SimpleExoPlayerView.java index 2fa9c29fce..9d221be60a 100644 --- a/library/ui/src/main/java/com/google/android/exoplayer2/ui/SimpleExoPlayerView.java +++ b/library/ui/src/main/java/com/google/android/exoplayer2/ui/SimpleExoPlayerView.java @@ -34,6 +34,7 @@ import android.widget.ImageView; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.ExoPlaybackException; import com.google.android.exoplayer2.ExoPlayer; +import com.google.android.exoplayer2.PlaybackParameters; import com.google.android.exoplayer2.SimpleExoPlayer; import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.metadata.Metadata; @@ -741,6 +742,11 @@ public final class SimpleExoPlayerView extends FrameLayout { // Do nothing. } + @Override + public void onPlaybackParametersChanged(PlaybackParameters playbackParameters) { + // Do nothing. + } + @Override public void onTimelineChanged(Timeline timeline, Object manifest) { // Do nothing. diff --git a/playbacktests/src/main/java/com/google/android/exoplayer2/playbacktests/util/ExoHostedTest.java b/playbacktests/src/main/java/com/google/android/exoplayer2/playbacktests/util/ExoHostedTest.java index 87c55e9248..b13cfbdd15 100644 --- a/playbacktests/src/main/java/com/google/android/exoplayer2/playbacktests/util/ExoHostedTest.java +++ b/playbacktests/src/main/java/com/google/android/exoplayer2/playbacktests/util/ExoHostedTest.java @@ -24,6 +24,7 @@ import com.google.android.exoplayer2.ExoPlaybackException; import com.google.android.exoplayer2.ExoPlayer; import com.google.android.exoplayer2.ExoPlayerFactory; import com.google.android.exoplayer2.Format; +import com.google.android.exoplayer2.PlaybackParameters; import com.google.android.exoplayer2.SimpleExoPlayer; import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.audio.AudioRendererEventListener; @@ -223,6 +224,11 @@ public abstract class ExoHostedTest implements HostedTest, ExoPlayer.EventListen // Do nothing. } + @Override + public final void onPlaybackParametersChanged(PlaybackParameters playbackParameters) { + // Do nothing. + } + @Override public final void onTimelineChanged(Timeline timeline, Object manifest) { // Do nothing. From f4c33daf77fadd207a8e195242619fbaa06ac1c5 Mon Sep 17 00:00:00 2001 From: olly Date: Thu, 23 Mar 2017 11:17:09 -0700 Subject: [PATCH 030/119] Bump version for bugfix release ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=151028565 --- RELEASENOTES.md | 9 +++++++++ build.gradle | 2 +- demo/src/main/AndroidManifest.xml | 4 ++-- .../google/android/exoplayer2/ExoPlayerLibraryInfo.java | 4 ++-- 4 files changed, 14 insertions(+), 5 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index b85c65e769..48bf6dd073 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -1,5 +1,14 @@ # Release notes # +### r2.3.1 ### + +* Fix NPE enabling WebVTT subtitles in DASH streams + ([#2596](https://github.com/google/ExoPlayer/issues/2596)). +* Fix skipping to keyframes when MediaCodecVideoRenderer is enabled but without + a Surface ([#2575](https://github.com/google/ExoPlayer/issues/2575)). +* Minor fix for CEA-708 decoder + ([#2595](https://github.com/google/ExoPlayer/issues/2595)). + ### r2.3.0 ### * GVR extension: Wraps the Google VR Audio SDK to provide spatial audio diff --git a/build.gradle b/build.gradle index 49c7d5c22a..6d5a069d3e 100644 --- a/build.gradle +++ b/build.gradle @@ -37,7 +37,7 @@ allprojects { releaseRepoName = getBintrayRepo() releaseUserOrg = 'google' releaseGroupId = 'com.google.android.exoplayer' - releaseVersion = 'r2.3.0' + releaseVersion = 'r2.3.1' releaseWebsite = 'https://github.com/google/ExoPlayer' } } diff --git a/demo/src/main/AndroidManifest.xml b/demo/src/main/AndroidManifest.xml index a834c5df19..9a6e1a4d3a 100644 --- a/demo/src/main/AndroidManifest.xml +++ b/demo/src/main/AndroidManifest.xml @@ -16,8 +16,8 @@ + android:versionCode="2301" + android:versionName="2.3.1"> 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 5ec7fac5dd..bee9904590 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 @@ -23,7 +23,7 @@ public interface ExoPlayerLibraryInfo { /** * The version of the library, expressed as a string. */ - String VERSION = "2.3.0"; + String VERSION = "2.3.1"; /** * The version of the library, expressed as an integer. @@ -32,7 +32,7 @@ public interface ExoPlayerLibraryInfo { * corresponding integer version 1002003 (001-002-003), and "123.45.6" has the corresponding * integer version 123045006 (123-045-006). */ - int VERSION_INT = 2003000; + int VERSION_INT = 2003001; /** * Whether the library was compiled with {@link com.google.android.exoplayer2.util.Assertions} From 7e0ea5a65b818a2985c05f1fd9c86acfe22565c4 Mon Sep 17 00:00:00 2001 From: Sergio Moreno Mozota Date: Thu, 23 Mar 2017 17:44:33 +0100 Subject: [PATCH 031/119] Optimization of memory and draw operations, and proper treatment of filled regions (as far as we can infer from the specs) --- .../text/dvbsubs/DvbSubsSubtitle.java | 10 +- .../text/dvbsubs/DvbSubtitlesParser.java | 171 +++++++++--------- 2 files changed, 86 insertions(+), 95 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/text/dvbsubs/DvbSubsSubtitle.java b/library/core/src/main/java/com/google/android/exoplayer2/text/dvbsubs/DvbSubsSubtitle.java index 7d845a24dc..ae4444b7a7 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/text/dvbsubs/DvbSubsSubtitle.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/text/dvbsubs/DvbSubsSubtitle.java @@ -15,9 +15,6 @@ */ package com.google.android.exoplayer2.text.dvbsubs; - -import android.graphics.Bitmap; - import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.text.Cue; import com.google.android.exoplayer2.text.Subtitle; @@ -28,12 +25,11 @@ import java.util.List; final class DvbSubsSubtitle implements Subtitle { private final List cues; - public DvbSubsSubtitle(Bitmap data) { - if (data == null) { + public DvbSubsSubtitle(List cues) { + if (cues == null) { this.cues = Collections.emptyList(); } else { - Cue cue = new Cue(data, 0, Cue.ANCHOR_TYPE_START, 0, Cue.ANCHOR_TYPE_START, 1, (float) data.getWidth()/data.getHeight()); - this.cues = Collections.singletonList(cue); + this.cues = cues; } } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/text/dvbsubs/DvbSubtitlesParser.java b/library/core/src/main/java/com/google/android/exoplayer2/text/dvbsubs/DvbSubtitlesParser.java index 37a22a3b8b..7d393b3f7c 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/text/dvbsubs/DvbSubtitlesParser.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/text/dvbsubs/DvbSubtitlesParser.java @@ -28,8 +28,12 @@ import android.util.Log; import android.util.SparseArray; import com.google.android.exoplayer2.core.BuildConfig; +import com.google.android.exoplayer2.text.Cue; import com.google.android.exoplayer2.util.ParsableBitArray; +import java.util.ArrayList; +import java.util.List; + public class DvbSubtitlesParser { @@ -111,17 +115,15 @@ public class DvbSubtitlesParser { /* Constants */ private static final int UNDEF_PAGE = -1; - private final int NUM_BITMAPS = 4; /* instance variables */ - private Paint defaultPaintObject = new Paint(); - private Paint fillRegionPaintObject = new Paint(); - private Paint debugRegionPaintObject = new Paint(); - private Paint debugObjectPaintObject = new Paint(); - private Canvas[] canvasObjects = new Canvas[NUM_BITMAPS]; - private Bitmap[] bitmaps = new Bitmap[NUM_BITMAPS]; - private Bitmap lastValidBitmap; - private int currentBitmap = 0; + private Paint defaultPaint = new Paint(); + private Paint fillRegionPaint = new Paint(); + private Paint debugRegionPaint = new Paint(); + private Paint debugObjectPaint = new Paint(); + private Canvas canvas = new Canvas(); + private Bitmap bitmap; + private static ParsableBitArray tsStream; private SubtitleService subtitleService; @@ -133,13 +135,13 @@ public class DvbSubtitlesParser { // subtitle page DisplayDefinition displayDefinition; PageComposition pageComposition; - SparseArray regions = new SparseArray(); - SparseArray cluts = new SparseArray(); - SparseArray objects = new SparseArray(); + SparseArray regions = new SparseArray<>(); + SparseArray cluts = new SparseArray<>(); + SparseArray objects = new SparseArray<>(); // ancillary page - SparseArray ancillaryCluts = new SparseArray(); - SparseArray ancillaryObjects = new SparseArray(); + SparseArray ancillaryCluts = new SparseArray<>(); + SparseArray ancillaryObjects = new SparseArray<>(); } /* The displays dimensions [7.2.1] */ @@ -157,11 +159,9 @@ public class DvbSubtitlesParser { int displayWindowVerticalPositionMaximum = 575; void updateBitmapResolution() { - for (int i = 0; i < NUM_BITMAPS; i++) { - bitmaps[i] = Bitmap.createBitmap(this.displayWidth + 1, this.displayHeight + 1, - Bitmap.Config.ARGB_8888); - canvasObjects[i] = new Canvas(bitmaps[i]); - } + bitmap = Bitmap.createBitmap(this.displayWidth + 1, this.displayHeight + 1, + Bitmap.Config.ARGB_8888); + canvas = new Canvas(bitmap); } } @@ -171,7 +171,7 @@ public class DvbSubtitlesParser { int pageTimeOut; /* in seconds */ int pageVersionNumber; int pageState; - SparseArray pageRegions = new SparseArray(); + SparseArray pageRegions = new SparseArray<>(); } private class PageRegion { @@ -196,6 +196,7 @@ public class DvbSubtitlesParser { int region4bitPixelCode; int region2bitPixelCode; SparseArray regionObjects = new SparseArray<>(); + Cue cue; } /* The entry in the palette CLUT */ @@ -435,30 +436,27 @@ public class DvbSubtitlesParser { this.subtitleService = new SubtitleService(); this.flags = flags; - this.defaultPaintObject.setStyle(Paint.Style.FILL_AND_STROKE); - this.defaultPaintObject.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC)); - this.defaultPaintObject.setPathEffect(null); + this.defaultPaint.setStyle(Paint.Style.FILL_AND_STROKE); + this.defaultPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC)); + this.defaultPaint.setPathEffect(null); - this.fillRegionPaintObject.setStyle(Paint.Style.FILL); - this.fillRegionPaintObject.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC)); - this.fillRegionPaintObject.setPathEffect(null); + this.fillRegionPaint.setStyle(Paint.Style.FILL); + this.fillRegionPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OVER)); + this.fillRegionPaint.setPathEffect(null); - this.debugRegionPaintObject.setColor(0xff00ff00); - this.debugRegionPaintObject.setStyle(Paint.Style.STROKE); - this.debugRegionPaintObject.setPathEffect(new DashPathEffect(new float[] {2,2}, 0)); + this.debugRegionPaint.setColor(0xff00ff00); + this.debugRegionPaint.setStyle(Paint.Style.STROKE); + this.debugRegionPaint.setPathEffect(new DashPathEffect(new float[] {2,2}, 0)); - this.debugObjectPaintObject.setColor(0xffff0000); - this.debugObjectPaintObject.setStyle(Paint.Style.STROKE); - this.debugObjectPaintObject.setPathEffect(new DashPathEffect(new float[]{10, 20}, 0)); + this.debugObjectPaint.setColor(0xffff0000); + this.debugObjectPaint.setStyle(Paint.Style.STROKE); + this.debugObjectPaint.setPathEffect(new DashPathEffect(new float[]{10, 20}, 0)); this.subtitleService.subtitlePage = subtitlePage; this.subtitleService.ancillaryPage = ancillaryPage; this.subtitleService.displayDefinition = new DisplayDefinition(); this.subtitleService.displayDefinition.updateBitmapResolution(); - - this.currentBitmap = 0; - this.lastValidBitmap = bitmaps[currentBitmap]; } private void parseSubtitlingSegment() { @@ -492,7 +490,6 @@ public class DvbSubtitlesParser { tempDisplay.flags != subtitleService.displayDefinition.flags) { subtitleService.displayDefinition = tempDisplay; subtitleService.displayDefinition.updateBitmapResolution(); - lastValidBitmap = bitmaps[currentBitmap]; } else { subtitleService.displayDefinition.versionNumber = tempDisplay.versionNumber; } @@ -938,7 +935,7 @@ public class DvbSubtitlesParser { ObjectData object = new ObjectData(); object.pageId = tsStream.readBits(16); - int remainingSegmentLength = tsStream.readBits(16); + tsStream.skipBits(16); // remainingSegmentLength object.objectId = tsStream.readBits(16); object.objectVersionNumber = tsStream.readBits(4); object.objectCodingMethod = tsStream.readBits(2); @@ -947,21 +944,17 @@ public class DvbSubtitlesParser { } tsStream.skipBits(1); - remainingSegmentLength -= 3; if (object.objectCodingMethod == DVBSUB_ODS_CHAR_CODED) { /* not implemented yet */ object.numberOfCodes = tsStream.readBits(8); tsStream.skipBits(object.numberOfCodes * 16); - remainingSegmentLength -= object.numberOfCodes * 2; } else if (object.objectCodingMethod == DVBSUB_ODS_PIXEL_CODED) { object.topFieldDataLength = tsStream.readBits(16); object.bottomFieldDataLength = tsStream.readBits(16); - remainingSegmentLength -= 4; object.topFieldData = new byte[object.topFieldDataLength]; System.arraycopy(tsStream.data, tsStream.getPosition() / 8, object.topFieldData, 0, object.topFieldDataLength); tsStream.skipBits(object.topFieldDataLength * 8); - remainingSegmentLength -= object.topFieldDataLength; object.bottomFieldData = new byte[object.bottomFieldDataLength]; if (object.bottomFieldDataLength == 0) { @@ -970,8 +963,6 @@ public class DvbSubtitlesParser { System.arraycopy(tsStream.data, tsStream.getPosition() / 8, object.bottomFieldData, 0, object.bottomFieldDataLength); tsStream.skipBits(object.bottomFieldDataLength * 8); } - remainingSegmentLength -= object.bottomFieldDataLength; - } return object; @@ -1190,9 +1181,9 @@ public class DvbSubtitlesParser { if (runLength != 0 && paint) { colour = clutMapTable != null ? clutEntries[clutMapTable[clutIdx]].ARGB : clutEntries[clutIdx].ARGB; - defaultPaintObject.setColor(colour); - canvasObjects[currentBitmap].drawRect( - column, line, column + runLength, line + lineHeigth, defaultPaintObject); + defaultPaint.setColor(colour); + canvas.drawRect( + column, line, column + runLength, line + lineHeigth, defaultPaint); } column += runLength; @@ -1292,9 +1283,9 @@ public class DvbSubtitlesParser { if (runLength != 0 && paint) { colour = clutMapTable != null ? clutEntries[clutMapTable[clutIdx]].ARGB : clutEntries[clutIdx].ARGB; - defaultPaintObject.setColor(colour); - canvasObjects[currentBitmap].drawRect( - column, line, column + runLength, line + lineHeigth, defaultPaintObject); + defaultPaint.setColor(colour); + canvas.drawRect( + column, line, column + runLength, line + lineHeigth, defaultPaint); } column += runLength; @@ -1359,9 +1350,9 @@ public class DvbSubtitlesParser { if (runLength != 0 && paint) { colour = clutMapTable != null ? clutEntries[clutMapTable[clutIdx]].ARGB : clutEntries[clutIdx].ARGB; - defaultPaintObject.setColor(colour); - canvasObjects[currentBitmap].drawRect( - column, line, column + runLength, line + lineHeigth, defaultPaintObject); + defaultPaint.setColor(colour); + canvas.drawRect( + column, line, column + runLength, line + lineHeigth, defaultPaint); } column += runLength; @@ -1370,7 +1361,7 @@ public class DvbSubtitlesParser { return column - savedColumn; } - public Bitmap dvbSubsDecode(byte[] input, int inputSize) { + List dvbSubsDecode(byte[] input, int inputSize) { /* process PES PACKET. ETSI EN 300 743 7.1 @@ -1416,6 +1407,7 @@ public class DvbSubtitlesParser { if (sync == 0xff || (isSet(FLAG_PES_STRIPPED_DVBSUB) && tsStream.bitsLeft() == 0)) { // end_of_PES_data_field_marker // paint the current Subtitle definition if (subtitleService.pageComposition != null) { + List cueList = new ArrayList<>(); if (BuildConfig.DEBUG) { Log.d(TAG, "Rendering subtitle. w: " + subtitleService.displayDefinition.displayWidth + @@ -1456,7 +1448,7 @@ public class DvbSubtitlesParser { } // clip object drawing to the current region and display definition window - canvasObjects[currentBitmap].clipRect( + canvas.clipRect( baseHorizontalAddress, baseVerticalAddress, Math.min(baseHorizontalAddress + regionComposition.regionWidth, subtitleService.displayDefinition.displayWindowHorizontalPositionMaximum), @@ -1470,26 +1462,6 @@ public class DvbSubtitlesParser { } } - // fill the region if needed - if ((regionComposition.flags & REGION_FILL_FLAG )!= 0) { - int colour; - if (regionComposition.regionDepth == DVBSUB_RCS_BITDEPTH_8) { - colour = clut.clutEntries8bit[regionComposition.region8bitPixelCode].ARGB; - } else if (regionComposition.regionDepth == DVBSUB_RCS_BITDEPTH_4) { - colour = clut.clutEntries4bit[regionComposition.region4bitPixelCode].ARGB; - } else { - colour = clut.clutEntries2bit[regionComposition.region2bitPixelCode].ARGB; - } - - fillRegionPaintObject.setColor(colour); - canvasObjects[currentBitmap].drawRect( - baseHorizontalAddress, baseVerticalAddress, - baseHorizontalAddress + regionComposition.regionWidth , - baseVerticalAddress + regionComposition.regionHeight, - fillRegionPaintObject); - - } - if (BuildConfig.DEBUG) { Log.d(TAG, " Region: " + regionKey + " (x/y/w/h): (" + baseHorizontalAddress + "/" + baseVerticalAddress + "/" + @@ -1497,11 +1469,11 @@ public class DvbSubtitlesParser { (baseVerticalAddress + regionComposition.regionHeight - 1) + ")" ); - canvasObjects[currentBitmap].drawRect( + canvas.drawRect( baseHorizontalAddress, baseVerticalAddress, baseHorizontalAddress + regionComposition.regionWidth - 1, baseVerticalAddress + regionComposition.regionHeight - 1, - debugRegionPaintObject); + debugRegionPaint); } RegionObject regionObject; @@ -1517,12 +1489,12 @@ public class DvbSubtitlesParser { (baseVerticalAddress + regionObject.objectVerticalPosition) + ")" ); - canvasObjects[currentBitmap].drawRect( + canvas.drawRect( baseHorizontalAddress + regionObject.objectHorizontalPosition, baseVerticalAddress + regionObject.objectVerticalPosition, baseHorizontalAddress + regionObject.objectHorizontalPosition + regionComposition.regionWidth - 1, baseVerticalAddress + regionObject.objectVerticalPosition + regionComposition.regionHeight - 1, - debugObjectPaintObject); + debugObjectPaint); } if ((object = subtitleService.objects.get(regionObject.objectId)) == null) { @@ -1536,18 +1508,41 @@ public class DvbSubtitlesParser { baseVerticalAddress + regionObject.objectVerticalPosition); } + + // fill the region if needed + if ((regionComposition.flags & REGION_FILL_FLAG )!= 0) { + int colour; + if (regionComposition.regionDepth == DVBSUB_RCS_BITDEPTH_8) { + colour = clut.clutEntries8bit[regionComposition.region8bitPixelCode].ARGB; + } else if (regionComposition.regionDepth == DVBSUB_RCS_BITDEPTH_4) { + colour = clut.clutEntries4bit[regionComposition.region4bitPixelCode].ARGB; + } else { + colour = clut.clutEntries2bit[regionComposition.region2bitPixelCode].ARGB; + } + + fillRegionPaint.setColor(colour); + + canvas.drawRect( + baseHorizontalAddress, baseVerticalAddress, + baseHorizontalAddress + regionComposition.regionWidth , + baseVerticalAddress + regionComposition.regionHeight, + fillRegionPaint); + + } + + Bitmap cueBitmap = Bitmap.createBitmap(bitmap, + baseHorizontalAddress, baseVerticalAddress, + regionComposition.regionWidth, regionComposition.regionHeight); + canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR); + regionComposition.cue = new Cue(cueBitmap, + (float) baseHorizontalAddress / subtitleService.displayDefinition.displayWidth, Cue.ANCHOR_TYPE_START, + (float) baseVerticalAddress / subtitleService.displayDefinition.displayHeight, Cue.ANCHOR_TYPE_START, + (float) regionComposition.regionWidth / subtitleService.displayDefinition.displayWidth, + (float) subtitleService.displayDefinition.displayWidth / subtitleService.displayDefinition.displayHeight); + cueList.add(regionComposition.cue); } - lastValidBitmap = bitmaps[currentBitmap]; - currentBitmap = (currentBitmap + 1) % NUM_BITMAPS; - canvasObjects[currentBitmap].clipRect(0,0, - subtitleService.displayDefinition.displayWidth, - subtitleService.displayDefinition.displayHeight, - Region.Op.REPLACE); - canvasObjects[currentBitmap].drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR); - - return lastValidBitmap; - } + return cueList; } } else { Log.d(TAG,"Unexpected..."); } From c40b8156e9946fe95f321a33859f4932292f501f Mon Sep 17 00:00:00 2001 From: Alex Telitsine Date: Mon, 27 Mar 2017 03:58:04 -0700 Subject: [PATCH 032/119] Disables codecIsAdaptive for Odroid-XU4 --- .../mediacodec/MediaCodecRenderer.java | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) 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 3fbbfac652..e456d5fd15 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 @@ -337,7 +337,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer { } String codecName = decoderInfo.name; - codecIsAdaptive = decoderInfo.adaptive; + codecIsAdaptive = decoderInfo.adaptive && codecSupportsAdaptive(codecName, format); codecNeedsDiscardToSpsWorkaround = codecNeedsDiscardToSpsWorkaround(codecName, format); codecNeedsFlushWorkaround = codecNeedsFlushWorkaround(codecName); codecNeedsAdaptationWorkaround = codecNeedsAdaptationWorkaround(codecName); @@ -1170,5 +1170,19 @@ public abstract class MediaCodecRenderer extends BaseRenderer { return Util.SDK_INT <= 18 && format.channelCount == 1 && "OMX.MTK.AUDIO.DECODER.MP3".equals(name); } - + /** + * Returns whether the decoder is known to be non adaptive. + *

    + * If false is returned then we explicitly override codecIsAdaptive, + * setting it to false. + * + * @param name The decoder name. + * @param format The input format. + * @return True if the device is known to be non adaptiv . + */ + private static boolean codecSupportsAdaptive(String name, Format format) { + return !( + (Util.SDK_INT == 19 && Util.MODEL.equals("ODROID-XU3") + && ("OMX.Exynos.AVC.Decoder".equals(name) || "OMX.Exynos.AVC.Decoder.secure".equals(name)))); + } } From 9aee406c8ef2755f23b7dc9aa12ad21f6c22ae70 Mon Sep 17 00:00:00 2001 From: Alex Telitsine Date: Mon, 27 Mar 2017 04:10:53 -0700 Subject: [PATCH 033/119] Disables codecIsAdaptive for Odroid-XU4, comment updated --- .../android/exoplayer2/mediacodec/MediaCodecRenderer.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 e456d5fd15..2bc3ff285f 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 @@ -1171,14 +1171,14 @@ public abstract class MediaCodecRenderer extends BaseRenderer { && "OMX.MTK.AUDIO.DECODER.MP3".equals(name); } /** - * Returns whether the decoder is known to be non adaptive. + * Returns whether the decoder is known to be non Adaptive. *

    * If false is returned then we explicitly override codecIsAdaptive, * setting it to false. * * @param name The decoder name. * @param format The input format. - * @return True if the device is known to be non adaptiv . + * @return False if the device is known to be non adaptive . */ private static boolean codecSupportsAdaptive(String name, Format format) { return !( From 6c9656dc9a42f3977a9bf013d722810806114ee5 Mon Sep 17 00:00:00 2001 From: Sergio Moreno Mozota Date: Wed, 29 Mar 2017 00:14:32 +0200 Subject: [PATCH 034/119] some cleaning and class/method documentation --- .../text/dvbsubs/DvbSubtitlesParser.java | 134 ++++++++++++------ 1 file changed, 87 insertions(+), 47 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/text/dvbsubs/DvbSubtitlesParser.java b/library/core/src/main/java/com/google/android/exoplayer2/text/dvbsubs/DvbSubtitlesParser.java index 7d393b3f7c..47432bcf07 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/text/dvbsubs/DvbSubtitlesParser.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/text/dvbsubs/DvbSubtitlesParser.java @@ -34,7 +34,9 @@ import com.google.android.exoplayer2.util.ParsableBitArray; import java.util.ArrayList; import java.util.List; - +/** + * Parse and generate a list of {@link Cue}s from DVB subtitling bitstream + */ public class DvbSubtitlesParser { private static final String TAG = "DVBSubs"; @@ -104,8 +106,6 @@ public class DvbSubtitlesParser { (byte) 0x88, (byte) 0x99, (byte) 0xaa, (byte) 0xbb, (byte) 0xcc, (byte) 0xdd, (byte) 0xee, (byte) 0xff}; - private ClutDefinition defaultClut = new ClutDefinition(); - /* FLAGS */ private final static int DISPLAY_WINDOW_FLAG = 0x01; @@ -121,16 +121,19 @@ public class DvbSubtitlesParser { private Paint fillRegionPaint = new Paint(); private Paint debugRegionPaint = new Paint(); private Paint debugObjectPaint = new Paint(); - private Canvas canvas = new Canvas(); private Bitmap bitmap; + private Canvas canvas = new Canvas(); + private ClutDefinition defaultClut = new ClutDefinition(); private static ParsableBitArray tsStream; private SubtitleService subtitleService; + /* + * Contains the current subtitle service definition + */ private class SubtitleService { - int subtitlePage; - int ancillaryPage; - boolean newSubtitle = false; + int subtitlePageId; + int ancillaryPageId; // subtitle page DisplayDefinition displayDefinition; @@ -144,7 +147,7 @@ public class DvbSubtitlesParser { SparseArray ancillaryObjects = new SparseArray<>(); } - /* The displays dimensions [7.2.1] */ + /* The display definition contains the geometry and active area of the subtitle service [7.2.1] */ private class DisplayDefinition { int pageId; int versionNumber; @@ -165,7 +168,7 @@ public class DvbSubtitlesParser { } } - /* The page final static ints the list of regions [7.2.2] */ + /* The page is the definition and arrangement of regions in the screen [7.2.2] */ private class PageComposition { int pageId; int pageTimeOut; /* in seconds */ @@ -174,14 +177,13 @@ public class DvbSubtitlesParser { SparseArray pageRegions = new SparseArray<>(); } - private class PageRegion { - int regionId; - int regionHorizontalAddress; - int regionVerticalAddress; - } + private class PageRegion { + int regionId; + int regionHorizontalAddress; + int regionVerticalAddress; + } - /* The Region is an area on the image [7.2.3] - * with a list of the object definitions associated and a CLUT */ + /* The Region is an area of the page [7.2.3] composed of a list of objects and a CLUT */ private class RegionComposition { int pageId; int regionId; @@ -196,10 +198,27 @@ public class DvbSubtitlesParser { int region4bitPixelCode; int region2bitPixelCode; SparseArray regionObjects = new SparseArray<>(); + + /* + * We maintain a reference to the Cue to implement future drawing optimizations, no re-render in case of: + * + * - Page updates not affecting region composition (no clut change/redefinition, no object changes) + * - Incremental subtitle display render (e.g. live captions updates) + */ Cue cue; } - /* The entry in the palette CLUT */ + private class RegionObject { + int objectId; + int objectType; + int objectProvider; + int objectHorizontalPosition; + int objectVerticalPosition; + int foregroundPixelCode; + int backgroundPixelCode; + } + + /* An entry in the palette CLUT and associated color space translation methods */ private class ClutEntry { int clutEntryId; byte flags; @@ -279,7 +298,7 @@ public class DvbSubtitlesParser { } } - /* Colours to be applied in a CLUT family [7.2.4] */ + /* CLUT family definition containing the color tables for the three bitdepths defined [7.2.4] */ private class ClutDefinition { int pageId; int clutId; @@ -395,8 +414,7 @@ public class DvbSubtitlesParser { } - /* The object data segment contains the data of an object [7.2.5] - */ + /* The object data segment contains the textual/graphical representation of an object [7.2.5] */ private class ObjectData { int pageId; int objectId; @@ -410,29 +428,42 @@ public class DvbSubtitlesParser { int numberOfCodes; } - private class RegionObject { - int objectId; - int objectType; - int objectProvider; - int objectHorizontalPosition; - int objectVerticalPosition; - int foregroundPixelCode; - int backgroundPixelCode; - } - + /** + * Construct a subtitle service with default subtitle pageId + */ DvbSubtitlesParser() { this(1); } - DvbSubtitlesParser(int subtitlePge) { - this(subtitlePge, UNDEF_PAGE); + /** + * Construct a subtitle service for the given subtitle pageId + * + * @param subtitlePageId The subtitle page Id carrying the selected subtitle track + */ + DvbSubtitlesParser(int subtitlePageId) { + this(subtitlePageId, UNDEF_PAGE); } - DvbSubtitlesParser(int subtitlePage, int ancillaryPage) { - this(subtitlePage, ancillaryPage, 0); + /** + * Construct a subtitle service for the given subtitle and ancillary pageIds + * + * @param subtitlePageId The Id of the subtitle page carrying the selected subtitle track + * @param ancillaryPageId Id of the common subtitle page containing additional data for the current + * subtitle track + */ + DvbSubtitlesParser(int subtitlePageId, int ancillaryPageId) { + this(subtitlePageId, ancillaryPageId, 0); } - DvbSubtitlesParser(int subtitlePage, int ancillaryPage, @Flags int flags) { + /** + * Construct a subtitle service for the given subtitle and ancillary pageIds + * + * @param subtitlePageId The Id of the subtitle page carrying the selected subtitle track + * @param ancillaryPageId Id of the common subtitle page containing additional data for the current + * subtitle track + * @param flags additional initialisation info to properly configure the parser + */ + DvbSubtitlesParser(int subtitlePageId, int ancillaryPageId, @Flags int flags) { this.subtitleService = new SubtitleService(); this.flags = flags; @@ -452,8 +483,8 @@ public class DvbSubtitlesParser { this.debugObjectPaint.setStyle(Paint.Style.STROKE); this.debugObjectPaint.setPathEffect(new DashPathEffect(new float[]{10, 20}, 0)); - this.subtitleService.subtitlePage = subtitlePage; - this.subtitleService.ancillaryPage = ancillaryPage; + this.subtitleService.subtitlePageId = subtitlePageId; + this.subtitleService.ancillaryPageId = ancillaryPageId; this.subtitleService.displayDefinition = new DisplayDefinition(); this.subtitleService.displayDefinition.updateBitmapResolution(); @@ -480,7 +511,7 @@ public class DvbSubtitlesParser { case DVBSUB_ST_DISPLAY_DEFINITION: if (BuildConfig.DEBUG) Log.d(TAG, " Parse Display Definition segment."); DisplayDefinition tempDisplay = parseDisplayDefinitionSegment(); - if (tempDisplay != null && tempDisplay.pageId == subtitleService.subtitlePage) { + if (tempDisplay != null && tempDisplay.pageId == subtitleService.subtitlePageId) { if (tempDisplay.displayWidth != subtitleService.displayDefinition.displayWidth || tempDisplay.displayHeight != subtitleService.displayDefinition.displayHeight || tempDisplay.displayWindowHorizontalPositionMaximum != subtitleService.displayDefinition.displayWindowHorizontalPositionMaximum || @@ -506,7 +537,7 @@ public class DvbSubtitlesParser { case DVBSUB_ST_PAGE_COMPOSITION: if (BuildConfig.DEBUG) Log.d(TAG, " Parse Page Composition segment."); PageComposition tempPage = parsePageCompositionSegment(); - if (tempPage != null && tempPage.pageId == subtitleService.subtitlePage) { + if (tempPage != null && tempPage.pageId == subtitleService.subtitlePageId) { if (tempPage.pageState == DVBSUB_PCS_STATE_NORMAL && subtitleService.pageComposition == null) break; subtitleService.pageComposition = tempPage; @@ -515,7 +546,7 @@ public class DvbSubtitlesParser { case DVBSUB_ST_REGION_COMPOSITION: if (BuildConfig.DEBUG) Log.d(TAG, " Parse Region Composition segment."); RegionComposition tempRegionComposition = parseRegionCompositionSegment(); - if (tempRegionComposition != null && tempRegionComposition.pageId == subtitleService.subtitlePage) { + if (tempRegionComposition != null && tempRegionComposition.pageId == subtitleService.subtitlePageId) { subtitleService.regions.put(tempRegionComposition.regionId, tempRegionComposition); } break; @@ -523,9 +554,9 @@ public class DvbSubtitlesParser { if (BuildConfig.DEBUG) Log.d(TAG, " Parse Clut Definition segment."); ClutDefinition tempClutDefinition = parseClutDefinitionSegment(); if (tempClutDefinition != null ) { - if (tempClutDefinition.pageId == subtitleService.subtitlePage) { + if (tempClutDefinition.pageId == subtitleService.subtitlePageId) { subtitleService.cluts.put(tempClutDefinition.clutId, tempClutDefinition); - } else if (tempClutDefinition.pageId == subtitleService.ancillaryPage) { + } else if (tempClutDefinition.pageId == subtitleService.ancillaryPageId) { subtitleService.ancillaryCluts.put(tempClutDefinition.clutId, tempClutDefinition); } } @@ -534,9 +565,9 @@ public class DvbSubtitlesParser { if (BuildConfig.DEBUG) Log.d(TAG, " Parse Object Data segment."); ObjectData tempObjectData = parseObjectDataSegment(); if (tempObjectData != null) { - if (tempObjectData.pageId == subtitleService.subtitlePage) { + if (tempObjectData.pageId == subtitleService.subtitlePageId) { subtitleService.objects.put(tempObjectData.objectId, tempObjectData); - } else if (tempObjectData.pageId == subtitleService.ancillaryPage) { + } else if (tempObjectData.pageId == subtitleService.ancillaryPageId) { subtitleService.ancillaryObjects.put(tempObjectData.objectId, tempObjectData); } } @@ -659,7 +690,7 @@ public class DvbSubtitlesParser { ); } - } else if (subtitleService.subtitlePage == page.pageId) { + } else if (subtitleService.subtitlePageId == page.pageId) { if (BuildConfig.DEBUG) { if (page.pageState == DVBSUB_PCS_STATE_NORMAL) { Log.d(TAG, " FAILED Page Composition update. pageId: " + page.pageId + @@ -667,7 +698,6 @@ public class DvbSubtitlesParser { } } - subtitleService.newSubtitle = false; subtitleService.pageComposition = null; subtitleService.regions = new SparseArray<>(); subtitleService.cluts = new SparseArray<>(); @@ -1361,6 +1391,15 @@ public class DvbSubtitlesParser { return column - savedColumn; } + /** + * Takes a subtitling packet, parses the included segments and returns the list of {@link Cue}s + * defined in them + * + * @param input + * @param inputSize + * @return list of {@link Cue}s contained in the packet or null if there is an error or subtitle + * is incomplete + */ List dvbSubsDecode(byte[] input, int inputSize) { /* process PES PACKET. ETSI EN 300 743 7.1 @@ -1542,7 +1581,8 @@ public class DvbSubtitlesParser { cueList.add(regionComposition.cue); } - return cueList; } + return cueList; + } } else { Log.d(TAG,"Unexpected..."); } From d34d3f76be7cb4b7423b61cf03d85f2ad1df9dd4 Mon Sep 17 00:00:00 2001 From: Sergio Moreno Mozota Date: Fri, 31 Mar 2017 01:48:29 +0200 Subject: [PATCH 035/119] Pull request review fixes --- library/core/proguard-rules.txt | 3 + .../extractor/mkv/MatroskaExtractor.java | 10 +- .../extractor/ts/DvbSubtitlesReader.java | 104 +- .../exoplayer2/extractor/ts/TsExtractor.java | 1 - .../google/android/exoplayer2/text/Cue.java | 48 +- .../text/SubtitleDecoderFactory.java | 4 +- .../exoplayer2/text/dvb/DvbDecoder.java | 52 + .../exoplayer2/text/dvb/DvbParser.java | 1569 ++++++++++++++++ .../DvbSubtitle.java} | 9 +- .../text/dvbsubs/DvbSubsDecoder.java | 71 - .../text/dvbsubs/DvbSubtitlesParser.java | 1596 ----------------- .../exoplayer2/ui/SubtitlePainter.java | 9 +- 12 files changed, 1710 insertions(+), 1766 deletions(-) create mode 100644 library/core/src/main/java/com/google/android/exoplayer2/text/dvb/DvbDecoder.java create mode 100644 library/core/src/main/java/com/google/android/exoplayer2/text/dvb/DvbParser.java rename library/core/src/main/java/com/google/android/exoplayer2/text/{dvbsubs/DvbSubsSubtitle.java => dvb/DvbSubtitle.java} (87%) delete mode 100644 library/core/src/main/java/com/google/android/exoplayer2/text/dvbsubs/DvbSubsDecoder.java delete mode 100644 library/core/src/main/java/com/google/android/exoplayer2/text/dvbsubs/DvbSubtitlesParser.java diff --git a/library/core/proguard-rules.txt b/library/core/proguard-rules.txt index 75f2d095be..34d881f891 100644 --- a/library/core/proguard-rules.txt +++ b/library/core/proguard-rules.txt @@ -5,3 +5,6 @@ -keepclassmembers class com.google.android.exoplayer2.text.cea.Cea708Decoder { public (int); } +-keepclassmembers class com.google.android.exoplayer2.text.dvb.DvbDecoder { + public (java.util.List); +} \ No newline at end of file diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mkv/MatroskaExtractor.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mkv/MatroskaExtractor.java index 1f3bb0192d..31b2c41f85 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mkv/MatroskaExtractor.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mkv/MatroskaExtractor.java @@ -1235,7 +1235,8 @@ public final class MatroskaExtractor implements Extractor { || CODEC_ID_SUBRIP.equals(codecId) || CODEC_ID_VOBSUB.equals(codecId) || CODEC_ID_PGS.equals(codecId) - || CODEC_ID_DVBSUB.equals(codecId); } + || CODEC_ID_DVBSUB.equals(codecId); + } /** * Returns an array that can store (at least) {@code length} elements, which will be either a new @@ -1464,11 +1465,8 @@ public final class MatroskaExtractor implements Extractor { break; case CODEC_ID_DVBSUB: mimeType = MimeTypes.APPLICATION_DVBSUBS; - initializationData = new ArrayList<>(4); - initializationData.add(null); - initializationData.add(new byte[] {codecPrivate[0], codecPrivate[1]}); - initializationData.add(new byte[] {codecPrivate[2], codecPrivate[3]}); - initializationData.add("mkv".getBytes()); + initializationData = Collections.singletonList(new byte[] { + (byte) 0x01, codecPrivate[0], codecPrivate[1], codecPrivate[2], codecPrivate[3]}); break; default: throw new ParserException("Unrecognized codec identifier."); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/DvbSubtitlesReader.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/DvbSubtitlesReader.java index 0e5931e9fe..6caf0692ae 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/DvbSubtitlesReader.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/DvbSubtitlesReader.java @@ -24,66 +24,68 @@ import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.TrackIdGenerat import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.ParsableByteArray; -import java.util.ArrayList; +import java.util.Collections; import java.util.List; -public class DvbSubtitlesReader implements ElementaryStreamReader { +/** + * Output PES packets to a {@link TrackOutput}. + */ +public final class DvbSubtitlesReader implements ElementaryStreamReader { - private static final String TAG= "DVBSubsReader"; - private final String language; - private List initializationData = new ArrayList<>(); + private final String language; + private List initializationData; - private long sampleTimeUs; - private int totalBytesWritten; - private boolean writingSample; + private long sampleTimeUs; + private int sampleBytesWritten; + private boolean writingSample; - private TrackOutput output; + private TrackOutput output; - public DvbSubtitlesReader(TsPayloadReader.EsInfo esInfo) { - // we only support one subtitle service per PID - this.language = esInfo.language; - this.initializationData.add(new byte[] {esInfo.descriptorBytes[5]}); // subtitle subtype - this.initializationData.add(new byte[] {esInfo.descriptorBytes[6], esInfo.descriptorBytes[7]}); // subtitle compose page - this.initializationData.add(new byte[] {esInfo.descriptorBytes[8], esInfo.descriptorBytes[9]}); // subtitle ancillary page - this.initializationData.add("mp2t".getBytes()); + public DvbSubtitlesReader(TsPayloadReader.EsInfo esInfo) { + this.language = esInfo.language; + initializationData = Collections.singletonList(new byte[] {(byte) 0x00, + esInfo.descriptorBytes[6], esInfo.descriptorBytes[7], + esInfo.descriptorBytes[8], esInfo.descriptorBytes[9]}); + } + + + @Override + public void seek() { + writingSample = false; + } + + @Override + public void createTracks(ExtractorOutput extractorOutput, TrackIdGenerator idGenerator) { + idGenerator.generateNewId(); + this.output = extractorOutput.track(idGenerator.getTrackId(), C.TRACK_TYPE_TEXT); + output.format(Format.createImageSampleFormat(idGenerator.getFormatId(), + MimeTypes.APPLICATION_DVBSUBS, null, Format.NO_VALUE, initializationData, language, null)); + } + + + @Override + public void packetStarted(long pesTimeUs, boolean dataAlignmentIndicator) { + if (!dataAlignmentIndicator) { + return; } + writingSample = true; + sampleTimeUs = pesTimeUs; + sampleBytesWritten = 0; + } + @Override + public void packetFinished() { + output.sampleMetadata(sampleTimeUs, C.BUFFER_FLAG_KEY_FRAME, sampleBytesWritten, 0, null); + writingSample = false; + } - @Override - public void seek() { - writingSample = false; - } - - @Override - public void createTracks(ExtractorOutput extractorOutput, TrackIdGenerator idGenerator) { - idGenerator.generateNewId(); - this.output = extractorOutput.track(idGenerator.getTrackId(), C.TRACK_TYPE_TEXT); - output.format(Format.createImageSampleFormat(idGenerator.getFormatId(), MimeTypes.APPLICATION_DVBSUBS, null, Format.NO_VALUE, initializationData, language, null)); - } - - - @Override - public void packetStarted(long pesTimeUs, boolean dataAlignmentIndicator) { - if (!dataAlignmentIndicator) { - return; - } - writingSample = true; - sampleTimeUs = pesTimeUs; - totalBytesWritten = 0; - } - - @Override - public void packetFinished() { - output.sampleMetadata(sampleTimeUs, C.BUFFER_FLAG_KEY_FRAME, totalBytesWritten, 0, null); - writingSample = false; - } - - @Override - public void consume(ParsableByteArray data) { - if (writingSample) { - totalBytesWritten += data.bytesLeft(); - output.sampleData(data, data.bytesLeft()); - } + @Override + public void consume(ParsableByteArray data) { + if (writingSample) { + int bytesAvailable = data.bytesLeft(); + output.sampleData(data, bytesAvailable); + sampleBytesWritten += bytesAvailable; } + } } \ No newline at end of file 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 3c163541ac..f80c470b59 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 @@ -502,7 +502,6 @@ public final class TsExtractor implements Extractor { // Audio type is ignored. } else if (descriptorTag == TS_PMT_DESC_DVBSUBS) { streamType = TS_STREAM_TYPE_DVBSUBS; - // we only support one subtitle service per PID language = new String(data.data, data.getPosition(), 3).trim(); } // Skip unused bytes of current descriptor. diff --git a/library/core/src/main/java/com/google/android/exoplayer2/text/Cue.java b/library/core/src/main/java/com/google/android/exoplayer2/text/Cue.java index a13dbed624..f31cf4387d 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/text/Cue.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/text/Cue.java @@ -167,6 +167,12 @@ public class Cue { */ public final float size; + /** + * The bitmap height as a fraction of the of the viewport size, or -1 if the bitmap should be + * displayed at its natural height given for its specified {@link #size}. + */ + public final float bitmapHeight; + /** * Specifies whether or not the {@link #windowColor} property is set. */ @@ -177,30 +183,6 @@ public class Cue { */ public final int windowColor; - /** - * The Storage Aspect Ratio of the Cue - */ - public final float sar; - - /** * Creates an image cue. - * - * @param bitmap See {@link #bitmap}. - * @param horizontalPosition The position of the horizontal anchor within the viewport, expressed - * as a fraction of the viewport width. - * @param horizontalPositionAnchor The horizontal anchor. One of {@link #ANCHOR_TYPE_START}, - * {@link #ANCHOR_TYPE_MIDDLE}, {@link #ANCHOR_TYPE_END} and {@link #TYPE_UNSET}. - * @param verticalPosition The position of the vertical anchor within the viewport, expressed as a - * fraction of the viewport height. - * @param verticalPositionAnchor The vertical anchor. One of {@link #ANCHOR_TYPE_START}, - * {@link #ANCHOR_TYPE_MIDDLE}, {@link #ANCHOR_TYPE_END} and {@link #TYPE_UNSET}. - * @param width The width of the cue, expressed as a fraction of the viewport width. - */ - public Cue(Bitmap bitmap, float horizontalPosition, @AnchorType int horizontalPositionAnchor, - float verticalPosition, @AnchorType int verticalPositionAnchor, float width) { - this(null, null, bitmap, verticalPosition, LINE_TYPE_FRACTION, verticalPositionAnchor, - horizontalPosition, horizontalPositionAnchor, width, false, Color.BLACK, (float) 1.7777); - } - /** * Creates an image cue. * @@ -214,12 +196,13 @@ public class Cue { * @param verticalPositionAnchor The vertical anchor. One of {@link #ANCHOR_TYPE_START}, * {@link #ANCHOR_TYPE_MIDDLE}, {@link #ANCHOR_TYPE_END} and {@link #TYPE_UNSET}. * @param width The width of the cue, expressed as a fraction of the viewport width. - * @param sar The Storage Aspect Ratio of the cue, defaults to FHD SAR unless otherwise specified. + * @param height The width of the cue, expressed as a fraction of the viewport width. */ public Cue(Bitmap bitmap, float horizontalPosition, @AnchorType int horizontalPositionAnchor, - float verticalPosition, @AnchorType int verticalPositionAnchor, float width, float sar) { + float verticalPosition, @AnchorType int verticalPositionAnchor, float width, float + height) { this(null, null, bitmap, verticalPosition, LINE_TYPE_FRACTION, verticalPositionAnchor, - horizontalPosition, horizontalPositionAnchor, width, false, Color.BLACK, sar); + horizontalPosition, horizontalPositionAnchor, width, height, false, Color.BLACK); } /** @@ -265,15 +248,16 @@ public class Cue { * @param windowColor See {@link #windowColor}. */ public Cue(CharSequence text, Alignment textAlignment, float line, @LineType int lineType, - @AnchorType int lineAnchor, float position, @AnchorType int positionAnchor, float size, - boolean windowColorSet, int windowColor) { + @AnchorType int lineAnchor, float position, @AnchorType int positionAnchor, float size, + boolean windowColorSet, int windowColor) { this(text, textAlignment, null, line, lineType, lineAnchor, position, positionAnchor, size, - windowColorSet, windowColor, 1); + -1, windowColorSet, windowColor); } private Cue(CharSequence text, Alignment textAlignment, Bitmap bitmap, float line, @LineType int lineType, @AnchorType int lineAnchor, float position, - @AnchorType int positionAnchor, float size, boolean windowColorSet, int windowColor, float sar) { + @AnchorType int positionAnchor, float size, float bitmapHeight, boolean windowColorSet, + int windowColor) { this.text = text; this.textAlignment = textAlignment; this.bitmap = bitmap; @@ -283,9 +267,9 @@ public class Cue { this.position = position; this.positionAnchor = positionAnchor; this.size = size; + this.bitmapHeight = bitmapHeight; this.windowColorSet = windowColorSet; this.windowColor = windowColor; - this.sar = sar; } } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/text/SubtitleDecoderFactory.java b/library/core/src/main/java/com/google/android/exoplayer2/text/SubtitleDecoderFactory.java index b13a267b32..83f5c0a290 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/text/SubtitleDecoderFactory.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/text/SubtitleDecoderFactory.java @@ -85,7 +85,7 @@ public interface SubtitleDecoderFactory { } else if (format.sampleMimeType.equals(MimeTypes.APPLICATION_CEA708)) { return clazz.asSubclass(SubtitleDecoder.class).getConstructor(Integer.TYPE) .newInstance(format.accessibilityChannel); - } else if (format.sampleMimeType.equals(MimeTypes.APPLICATION_DVBSUBS) && format.initializationData != null) { + } else if (format.sampleMimeType.equals(MimeTypes.APPLICATION_DVBSUBS)) { return clazz.asSubclass(SubtitleDecoder.class).getConstructor(List.class).newInstance(format.initializationData); } else { return clazz.asSubclass(SubtitleDecoder.class).getConstructor().newInstance(); @@ -117,7 +117,7 @@ public interface SubtitleDecoderFactory { case MimeTypes.APPLICATION_CEA708: return Class.forName("com.google.android.exoplayer2.text.cea.Cea708Decoder"); case MimeTypes.APPLICATION_DVBSUBS: - return Class.forName("com.google.android.exoplayer2.text.dvbsubs.DvbSubsDecoder"); + return Class.forName("com.google.android.exoplayer2.text.dvb.DvbDecoder"); default: return null; } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/text/dvb/DvbDecoder.java b/library/core/src/main/java/com/google/android/exoplayer2/text/dvb/DvbDecoder.java new file mode 100644 index 0000000000..6b2d3dc5e3 --- /dev/null +++ b/library/core/src/main/java/com/google/android/exoplayer2/text/dvb/DvbDecoder.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.exoplayer2.text.dvb; + +import com.google.android.exoplayer2.text.SimpleSubtitleDecoder; + +import java.util.List; + +/** + * A {@link SimpleSubtitleDecoder} for DVB Subtitles. + */ +public final class DvbDecoder extends SimpleSubtitleDecoder { + + private final DvbParser parser; + + public DvbDecoder(List initializationData) { + super("DvbDecoder"); + + int subtitleCompositionPage = 1; + int subtitleAncillaryPage = 1; + int flags = 0; + byte[] tempByteArray; + + if ((tempByteArray = initializationData.get(0)) != null && tempByteArray.length == 5) { + if (tempByteArray[0] == 0x01) { + flags |= DvbParser.FLAG_PES_STRIPPED_DVBSUB; + } + subtitleCompositionPage = ((tempByteArray[1] & 0xFF) << 8) | (tempByteArray[2] & 0xFF); + subtitleAncillaryPage = ((tempByteArray[3] & 0xFF) << 8) | (tempByteArray[4] & 0xFF); + } + + parser = new DvbParser(subtitleCompositionPage, subtitleAncillaryPage, flags); + } + + @Override + protected DvbSubtitle decode(byte[] data, int length) { + return new DvbSubtitle(parser.dvbSubsDecode(data, length)); + } +} \ No newline at end of file diff --git a/library/core/src/main/java/com/google/android/exoplayer2/text/dvb/DvbParser.java b/library/core/src/main/java/com/google/android/exoplayer2/text/dvb/DvbParser.java new file mode 100644 index 0000000000..e2254cf007 --- /dev/null +++ b/library/core/src/main/java/com/google/android/exoplayer2/text/dvb/DvbParser.java @@ -0,0 +1,1569 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.exoplayer2.text.dvb; + +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.PorterDuff; +import android.graphics.DashPathEffect; +import android.graphics.PorterDuffXfermode; +import android.graphics.Region; +import android.support.annotation.IntDef; +import android.util.Log; +import android.util.SparseArray; + +import com.google.android.exoplayer2.core.BuildConfig; +import com.google.android.exoplayer2.text.Cue; +import com.google.android.exoplayer2.util.ParsableBitArray; + +import java.util.ArrayList; +import java.util.List; + +/** + * Parse and generate a list of {@link Cue}s from DVB subtitling bitstream + */ +public class DvbParser { + + private static final String TAG = "DVBSubs"; + + @IntDef(flag = true, value = {FLAG_PES_STRIPPED_DVBSUB}) + public @interface Flags { + } + + public static final int FLAG_PES_STRIPPED_DVBSUB = 1; + + @Flags + private final int flags; + + /* List of different SEGMENT TYPES */ + /* According to EN 300-743, table 2 */ + private final static int DVBSUB_ST_PAGE_COMPOSITION = 0x10; + private final static int DVBSUB_ST_REGION_COMPOSITION = 0x11; + private final static int DVBSUB_ST_CLUT_DEFINITION = 0x12; + private final static int DVBSUB_ST_OBJECT_DATA = 0x13; + private final static int DVBSUB_ST_DISPLAY_DEFINITION = 0x14; + private final static int DVBSUB_ST_ENDOFDISPLAY = 0x80; + private final static int DVBSUB_ST_STUFFING = 0xff; + + /* List of different Page Composition Segment state */ + /* According to EN 300-743, 7.2.1 table 3 */ + private final static int DVBSUB_PCS_STATE_NORMAL = 0b00; // Update. Only changed elements. + private final static int DVBSUB_PCS_STATE_ACQUISITION = 0b01; // Refresh. All subtitle elements. + private final static int DVBSUB_PCS_STATE_CHANGE = 0b10; // New. All subtitle elements. + + /* List of different Region Composition Segments CLUT level oc compatibility */ + /* According to EN 300-743, 7.2.1 table 4 */ + private final static int DVBSUB_RCS_CLUT_2 = 0x01; + private final static int DVBSUB_RCS_CLUT_4 = 0x02; + private final static int DVBSUB_RCS_CLUT_8 = 0x03; + + /* List of different Region Composition Segments bit depths */ + /* According to EN 300-743, 7.2.1 table 5 */ + private final static int DVBSUB_RCS_BITDEPTH_2 = 0x01; + private final static int DVBSUB_RCS_BITDEPTH_4 = 0x02; + private final static int DVBSUB_RCS_BITDEPTH_8 = 0x03; + + /* List of different object types in the Region Composition Segment */ + /* According to EN 300-743, table 6 */ + private final static int DVBSUB_OT_BASIC_BITMAP = 0x00; + private final static int DVBSUB_OT_BASIC_CHAR = 0x01; + private final static int DVBSUB_OT_COMPOSITE_STRING = 0x02; + + /* List of different object coding methods in the Object Data Segment */ + /* According to EN 300-743, table 8 */ + private static final int DVBSUB_ODS_PIXEL_CODED = 0x00; + private static final int DVBSUB_ODS_CHAR_CODED = 0x01; + + /* Pixel DATA TYPES */ + /* According to EN 300-743, table 9 */ + private final static int DVBSUB_DT_2BP_CODE_STRING = 0x10; + private final static int DVBSUB_DT_4BP_CODE_STRING = 0x11; + private final static int DVBSUB_DT_8BP_CODE_STRING = 0x12; + private final static int DVBSUB_DT_24_TABLE_DATA = 0x20; + private final static int DVBSUB_DT_28_TABLE_DATA = 0x21; + private final static int DVBSUB_DT_48_TABLE_DATA = 0x22; + private final static int DVBSUB_DT_END_LINE = 0xf0; + + /* Clut mapping tables */ + /* According to EN 300-743, 10.4 10.5 10.6 */ + private byte[] defaultMap24 = {(byte) 0x00, (byte) 0x07, (byte) 0x08, (byte) 0x0f}; + private byte[] defaultMap28 = {(byte) 0x00, (byte) 0x77, (byte) 0x88, (byte) 0xff}; + private byte[] defaultMap48 = {(byte) 0x00, (byte) 0x11, (byte) 0x22, (byte) 0x33, + (byte) 0x44, (byte) 0x55, (byte) 0x66, (byte) 0x77, + (byte) 0x88, (byte) 0x99, (byte) 0xaa, (byte) 0xbb, + (byte) 0xcc, (byte) 0xdd, (byte) 0xee, (byte) 0xff}; + + /* FLAGS */ + private final static int DISPLAY_WINDOW_FLAG = 0x01; + + private final static int REGION_FILL_FLAG = 0x01; + + private final static int OBJECT_NON_MODIFYING_COLOUR_FLAG = 0x01; + + /* instance variables */ + private Paint defaultPaint = new Paint(); + private Paint fillRegionPaint = new Paint(); + private Paint debugRegionPaint = new Paint(); + private Paint debugObjectPaint = new Paint(); + private Bitmap bitmap; + private Canvas canvas = new Canvas(); + private ClutDefinition defaultClut = new ClutDefinition(); + + private static ParsableBitArray tsStream; + private SubtitleService subtitleService; + + /* + * Contains the current subtitle service definition + */ + private class SubtitleService { + int subtitlePageId; + int ancillaryPageId; + + // subtitle page + DisplayDefinition displayDefinition; + PageComposition pageComposition; + SparseArray regions = new SparseArray<>(); + SparseArray cluts = new SparseArray<>(); + SparseArray objects = new SparseArray<>(); + + // ancillary page + SparseArray ancillaryCluts = new SparseArray<>(); + SparseArray ancillaryObjects = new SparseArray<>(); + } + + /* The display definition contains the geometry and active area of the subtitle service [7.2.1] */ + private class DisplayDefinition { + int pageId; + int versionNumber; + + int displayWidth = 719; + int displayHeight = 575; + + int flags; + int displayWindowHorizontalPositionMinimum = 0; + int displayWindowHorizontalPositionMaximum = 719; + int displayWindowVerticalPositionMinimum = 0; + int displayWindowVerticalPositionMaximum = 575; + + void updateBitmapResolution() { + bitmap = Bitmap.createBitmap(this.displayWidth + 1, this.displayHeight + 1, + Bitmap.Config.ARGB_8888); + canvas = new Canvas(bitmap); + } + } + + /* The page is the definition and arrangement of regions in the screen [7.2.2] */ + private class PageComposition { + int pageId; + int pageTimeOut; /* in seconds */ + int pageVersionNumber; + int pageState; + SparseArray pageRegions = new SparseArray<>(); + } + + private class PageRegion { + int regionId; + int regionHorizontalAddress; + int regionVerticalAddress; + } + + /* The Region is an area of the page [7.2.3] composed of a list of objects and a CLUT */ + private class RegionComposition { + int pageId; + int regionId; + int regionVersionNumber; + int flags; + int regionWidth; + int regionHeight; + int regionLevelOfCompatibility; + int regionDepth; + int clutId; + int region8bitPixelCode; + int region4bitPixelCode; + int region2bitPixelCode; + SparseArray regionObjects = new SparseArray<>(); + + /* + * We maintain a reference to the Cue to implement future drawing optimizations, no re-render in case of: + * + * - Page updates not affecting region composition (no clut change/redefinition, no object changes) + * - Incremental subtitle display render (e.g. live captions updates) + */ + Cue cue; + } + + private class RegionObject { + int objectId; + int objectType; + int objectProvider; + int objectHorizontalPosition; + int objectVerticalPosition; + int foregroundPixelCode; + int backgroundPixelCode; + } + + /* An entry in the palette CLUT and associated color space translation methods */ + private class ClutEntry { + int clutEntryId; + byte flags; + byte Y; + byte Cr; + byte Cb; + byte T; + + byte A; + byte R; + byte G; + byte B; + int ARGB; + + void clutYCbCrT(int Y, int Cb, int Cr, int T) { + + this.Y = (byte) Y; + this.Cb = (byte) Cb; + this.Cr = (byte) Cr; + this.T = (byte) T; + + int R = (int) (Y + 1.40200 * (Cr - 128)); + int G = (int) (Y - 0.34414 * (Cb - 128) - 0.71414 * (Cr - 128)); + int B = (int) (Y + 1.77200 * (Cb - 128)); + + if (R > 255) this.R = (byte) 255; + else if (R < 0) this.R = 0; + else this.R = (byte) R; + + if (G > 255) this.G = (byte) 255; + else if (G < 0) this.G = 0; + else this.G = (byte) G; + + if (B > 255) this.B = (byte) 255; + else if (B < 0) this.B = 0; + else this.B = (byte) B; + + this.A = (byte) (0xFF - (this.T & 0xFF)); + this.ARGB = + ((this.A & 0xFF) << 24) | + ((this.R & 0xFF) << 16) | + ((this.G & 0xFF) << 8) | + (this.B & 0xFF); + + } + + void clutRGBA(int R, int G, int B, int A) { + + this.A = (byte) A; + this.R = (byte) R; + this.G = (byte) G; + this.B = (byte) B; + + this.ARGB = + ((A & 0xFF) << 24) | + ((R & 0xFF) << 16) | + ((G & 0xFF) << 8) | + (B & 0xFF); + + int y = (int) (0.299000 * R + 0.587000 * G + 0.114000 * B); + int Cb = 128 + (int) (-0.168736 * R + -0.331264 * G + 0.500000 * B); + int Cr = 128 + (int) (0.500000 * R + -0.418688 * G + -0.081312 * B); + + if (y > 255) this.Y = (byte) 255; + else if (y < 0) this.Y = 0; + else this.Y = (byte) y; + + if (Cb > 255) this.Cb = (byte) 255; + else if (Cb < 0) this.Cb = 0; + else this.Cb = (byte) Cb; + + if (Cr > 255) this.Cr = (byte) 255; + else if (Cr < 0) this.Cr = 0; + else this.Cr = (byte) Cr; + + this.T = (byte) (0xFF - (this.A & 0xFF)); + } + } + + /* CLUT family definition containing the color tables for the three bitdepths defined [7.2.4] */ + private class ClutDefinition { + int pageId; + int clutId; + int clutVersionNumber; + ClutEntry[] clutEntries2bit; + ClutEntry[] clutEntries4bit; + ClutEntry[] clutEntries8bit; + + ClutEntry[] generateDefault2bitClut() { + ClutEntry[] entries = new ClutEntry[4]; + + entries[0] = new ClutEntry(); + entries[0].clutRGBA(0x00, 0x00, 0x00, 0x00); + entries[1] = new ClutEntry(); + entries[1].clutRGBA(0xFF, 0xFF, 0xFF, 0xFF); + entries[2] = new ClutEntry(); + entries[2].clutRGBA(0x00, 0x00, 0x00, 0xFF); + entries[3] = new ClutEntry(); + entries[3].clutRGBA(0x7F, 0x7F, 0x7F, 0xFF); + + return entries; + } + + ClutEntry[] generateDefault4bitClut() { + ClutEntry[] entries = new ClutEntry[16]; + + entries[0] = new ClutEntry(); + entries[0].clutRGBA(0x00, 0x00, 0x00, 0x00); + + int i = 15; + while (i > 0) { + entries[i] = new ClutEntry(); + if (i < 8) { + entries[i].clutRGBA( + ((i & 0x01) != 0 ? 0xFF : 0x00), + ((i & 0x02) != 0 ? 0xFF : 0x00), + ((i & 0x04) != 0 ? 0xFF : 0x00), + 0xFF); + } else { + entries[i].clutRGBA( + ((i & 0x01) != 0 ? 0x7F : 0x00), + ((i & 0x02) != 0 ? 0x7F : 0x00), + ((i & 0x04) != 0 ? 0x7F : 0x00), + 0xFF); + } + + i--; + } + + return entries; + } + + ClutEntry[] generateDefault8bitClut() { + ClutEntry[] entries = new ClutEntry[256]; + + entries[0] = new ClutEntry(); + entries[0].clutRGBA(0x00, 0x00, 0x00, 0x00); + + int i = 255; + while (i > 0) { + entries[i] = new ClutEntry(); + if (i < 8) { + entries[i].clutRGBA( + ((i & 0x01) != 0 ? 0xFF : 0x00), + ((i & 0x02) != 0 ? 0xFF : 0x00), + ((i & 0x04) != 0 ? 0xFF : 0x00), + 0x3F); + } else { + switch (i & 0x88) { + case 0x00: + entries[i].clutRGBA( + (((i & 0x01) != 0 ? 0x55 : 0x00) + ((i & 0x10) != 0 ? 0xAA : 0x00)), + (((i & 0x02) != 0 ? 0x55 : 0x00) + ((i & 0x20) != 0 ? 0xAA : 0x00)), + (((i & 0x04) != 0 ? 0x55 : 0x00) + ((i & 0x40) != 0 ? 0xAA : 0x00)), + 0xFF); + break; + case 0x08: + entries[i].clutRGBA( + (((i & 0x01) != 0 ? 0x55 : 0x00) + ((i & 0x10) != 0 ? 0xAA : 0x00)), + (((i & 0x02) != 0 ? 0x55 : 0x00) + ((i & 0x20) != 0 ? 0xAA : 0x00)), + (((i & 0x04) != 0 ? 0x55 : 0x00) + ((i & 0x40) != 0 ? 0xAA : 0x00)), + 0x7F); + break; + case 0x80: + entries[i].clutRGBA( + (127 + ((i & 0x01) != 0 ? 0x2B : 0x00) + ((i & 0x10) != 0 ? 0x55 : 0x00)), + (127 + ((i & 0x02) != 0 ? 0x2B : 0x00) + ((i & 0x20) != 0 ? 0x55 : 0x00)), + (127 + ((i & 0x04) != 0 ? 0x2B : 0x00) + ((i & 0x40) != 0 ? 0x55 : 0x00)), + 0xFF); + break; + case 0x88: + entries[i].clutRGBA( + (((i & 0x01) != 0 ? 0x2B : 0x00) + ((i & 0x10) != 0 ? 0x55 : 0x00)), + (((i & 0x02) != 0 ? 0x2B : 0x00) + ((i & 0x20) != 0 ? 0x55 : 0x00)), + (((i & 0x04) != 0 ? 0x2B : 0x00) + ((i & 0x40) != 0 ? 0x55 : 0x00)), + 0xFF); + break; + } + + } + + i--; + } + + return entries; + } + + ClutDefinition() { + clutEntries2bit = generateDefault2bitClut(); + clutEntries4bit = generateDefault4bitClut(); + clutEntries8bit = generateDefault8bitClut(); + } + + } + + /* The object data segment contains the textual/graphical representation of an object [7.2.5] */ + private class ObjectData { + int pageId; + int objectId; + int objectVersionNumber; + int objectCodingMethod; + byte flags; + int topFieldDataLength; + byte[] topFieldData; + int bottomFieldDataLength; + byte[] bottomFieldData; + int numberOfCodes; + } + + /** + * Construct a subtitle service for the given subtitle and ancillary pageIds + * + * @param subtitlePageId The Id of the subtitle page carrying the selected subtitle track + * @param ancillaryPageId Id of the common subtitle page containing additional data for the current + * subtitle track + * @param flags additional initialisation info to properly configure the parser + */ + DvbParser(int subtitlePageId, int ancillaryPageId, @Flags int flags) { + this.subtitleService = new SubtitleService(); + this.flags = flags; + + this.defaultPaint.setStyle(Paint.Style.FILL_AND_STROKE); + this.defaultPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC)); + this.defaultPaint.setPathEffect(null); + + this.fillRegionPaint.setStyle(Paint.Style.FILL); + this.fillRegionPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OVER)); + this.fillRegionPaint.setPathEffect(null); + + this.debugRegionPaint.setColor(0xff00ff00); + this.debugRegionPaint.setStyle(Paint.Style.STROKE); + this.debugRegionPaint.setPathEffect(new DashPathEffect(new float[]{2, 2}, 0)); + + this.debugObjectPaint.setColor(0xffff0000); + this.debugObjectPaint.setStyle(Paint.Style.STROKE); + this.debugObjectPaint.setPathEffect(new DashPathEffect(new float[]{10, 20}, 0)); + + this.subtitleService.subtitlePageId = subtitlePageId; + this.subtitleService.ancillaryPageId = ancillaryPageId; + + this.subtitleService.displayDefinition = new DisplayDefinition(); + this.subtitleService.displayDefinition.updateBitmapResolution(); + } + + private void parseSubtitlingSegment() { + + /* Parse subtitling segment. ETSI EN 300 743 7.2 + + SYNTAX SIZE SEMANTICS + --------------------------------------- ---- ------------------------------------------ + Subtitling_segment() { + sync_byte 8 An 8-bit field that shall be coded with the value '0000 1111' + segment_type 8 Indicates the type of data contained in the segment data field + page_id 16 Identifies the subtitle service of the data contained in this subtitling_segment + segment_length 16 Number of bytes contained in the segment_data_field + segment_data_field() This is the payload of the segment + + */ + + int pageId, segmentId, segmentLength; + segmentId = tsStream.readBits(8); + switch (segmentId) { + case DVBSUB_ST_DISPLAY_DEFINITION: + if (BuildConfig.DEBUG) Log.d(TAG, " Parse Display Definition segment."); + DisplayDefinition tempDisplay = parseDisplayDefinitionSegment(); + if (tempDisplay != null && tempDisplay.pageId == subtitleService.subtitlePageId) { + if (tempDisplay.displayWidth != subtitleService.displayDefinition.displayWidth || + tempDisplay.displayHeight != subtitleService.displayDefinition.displayHeight || + tempDisplay.displayWindowHorizontalPositionMaximum != subtitleService.displayDefinition.displayWindowHorizontalPositionMaximum || + tempDisplay.displayWindowHorizontalPositionMinimum != subtitleService.displayDefinition.displayWindowHorizontalPositionMinimum || + tempDisplay.displayWindowVerticalPositionMaximum != subtitleService.displayDefinition.displayWindowVerticalPositionMaximum || + tempDisplay.displayWindowVerticalPositionMinimum != subtitleService.displayDefinition.displayWindowVerticalPositionMinimum || + tempDisplay.flags != subtitleService.displayDefinition.flags) { + subtitleService.displayDefinition = tempDisplay; + subtitleService.displayDefinition.updateBitmapResolution(); + } else { + subtitleService.displayDefinition.versionNumber = tempDisplay.versionNumber; + } + + if (BuildConfig.DEBUG) + Log.d(TAG + "/DDS", " [versionNumber] = " + tempDisplay.versionNumber + + " [width/height] = " + (tempDisplay.displayWidth + 1) + "/" + (tempDisplay.displayHeight + 1) + + " Window[minX/minY/maxX/maxY] = " + tempDisplay.displayWindowHorizontalPositionMinimum + + "/" + tempDisplay.displayWindowVerticalPositionMinimum + + "/" + tempDisplay.displayWindowHorizontalPositionMaximum + + "/" + tempDisplay.displayWindowVerticalPositionMaximum + ); + } + break; + case DVBSUB_ST_PAGE_COMPOSITION: + if (BuildConfig.DEBUG) Log.d(TAG, " Parse Page Composition segment."); + PageComposition tempPage = parsePageCompositionSegment(); + if (tempPage != null && tempPage.pageId == subtitleService.subtitlePageId) { + if (tempPage.pageState == DVBSUB_PCS_STATE_NORMAL && subtitleService.pageComposition == null) + break; + subtitleService.pageComposition = tempPage; + } + break; + case DVBSUB_ST_REGION_COMPOSITION: + if (BuildConfig.DEBUG) Log.d(TAG, " Parse Region Composition segment."); + RegionComposition tempRegionComposition = parseRegionCompositionSegment(); + if (tempRegionComposition != null && tempRegionComposition.pageId == subtitleService.subtitlePageId) { + subtitleService.regions.put(tempRegionComposition.regionId, tempRegionComposition); + } + break; + case DVBSUB_ST_CLUT_DEFINITION: + if (BuildConfig.DEBUG) Log.d(TAG, " Parse Clut Definition segment."); + ClutDefinition tempClutDefinition = parseClutDefinitionSegment(); + if (tempClutDefinition != null) { + if (tempClutDefinition.pageId == subtitleService.subtitlePageId) { + subtitleService.cluts.put(tempClutDefinition.clutId, tempClutDefinition); + } else if (tempClutDefinition.pageId == subtitleService.ancillaryPageId) { + subtitleService.ancillaryCluts.put(tempClutDefinition.clutId, tempClutDefinition); + } + } + break; + case DVBSUB_ST_OBJECT_DATA: + if (BuildConfig.DEBUG) Log.d(TAG, " Parse Object Data segment."); + ObjectData tempObjectData = parseObjectDataSegment(); + if (tempObjectData != null) { + if (tempObjectData.pageId == subtitleService.subtitlePageId) { + subtitleService.objects.put(tempObjectData.objectId, tempObjectData); + } else if (tempObjectData.pageId == subtitleService.ancillaryPageId) { + subtitleService.ancillaryObjects.put(tempObjectData.objectId, tempObjectData); + } + } + break; + case DVBSUB_ST_ENDOFDISPLAY: + pageId = tsStream.readBits(16); + segmentLength = tsStream.readBits(16); + if (BuildConfig.DEBUG) + Log.d(TAG, "pageId " + pageId + "end of display size = " + segmentLength); + tsStream.skipBits(segmentLength * 8); + break; + case DVBSUB_ST_STUFFING: + pageId = tsStream.readBits(16); + segmentLength = tsStream.readBits(16); + if (BuildConfig.DEBUG) Log.d(TAG, "pageId " + pageId + "stuffing size = " + segmentLength); + tsStream.skipBits(segmentLength * 8); + break; + default: + break; + } + } + + private DisplayDefinition parseDisplayDefinitionSegment() { + + /* Parse display definition segment. ETSI EN 300 743 7.2.1 + + SYNTAX SIZE SEMANTICS + --------------------------------------- ---- ------------------------------------------ + display_definition_segment(){ + sync_byte 8 An 8-bit field that shall be coded with the value '0000 1111' + segment_type 8 Indicates the type of data contained in the segment data field + page_id 16 Identifies the subtitle service of the data contained in this subtitling_segment + segment_length 16 Number of bytes contained in the segment_data_field + dds_version_number 4 Incremented when any of the contents of this segment change + display_window_flag 1 if "1" display the subtitle in the defined window + reserved 3 + display_width 16 Specifies the maximum horizontal width of the display in pixels minus 1 + display_height 16 Specifies the maximum vertical height of the display in lines minus 1 + if (display_window_flag == 1) { With origin in the top-left of the screen: + display_window_horizontal_position_minimum + 16 Specifies the left-hand most pixel of this DVB subtitle display set + display_window_horizontal_position_maximum + 16 Specifies the right-hand most pixel of this DVB subtitle display set + display_window_vertical_position_minimum + 16 Specifies the upper most line of this DVB subtitle display set + display_window_vertical_position_maximum + 16 Specifies the bottom line of this DVB subtitle display set + } + } + */ + + DisplayDefinition display = new DisplayDefinition(); + + display.pageId = tsStream.readBits(16); + tsStream.skipBits(16); + display.versionNumber = tsStream.readBits(4); + if (tsStream.readBits(1) == 1) { + display.flags |= DISPLAY_WINDOW_FLAG; + } + tsStream.skipBits(3); + display.displayWidth = tsStream.readBits(16); + display.displayHeight = tsStream.readBits(16); + if ((display.flags & DISPLAY_WINDOW_FLAG) != 0) { + display.displayWindowHorizontalPositionMinimum = tsStream.readBits(16); + display.displayWindowHorizontalPositionMaximum = tsStream.readBits(16); + display.displayWindowVerticalPositionMinimum = tsStream.readBits(16); + display.displayWindowVerticalPositionMaximum = tsStream.readBits(16); + } else { + display.displayWindowHorizontalPositionMinimum = 0; + display.displayWindowHorizontalPositionMaximum = display.displayWidth; + display.displayWindowVerticalPositionMinimum = 0; + display.displayWindowVerticalPositionMaximum = display.displayHeight; + } + + return display; + } + + private PageComposition parsePageCompositionSegment() { + + /* Parse page composition segment. ETSI EN 300 743 7.2.2 + + SYNTAX SIZE SEMANTICS + --------------------------------------- ---- ------------------------------------------ + page_composition_segment() { + sync_byte 8 + segment_type 8 + page_id 16 + segment_length 16 + page_time_out 8 The period after the page instace should be erased + page_version_number 4 Incremented when any of the contents of this segment change + page_state 2 The status of the subtitling page instance + reserved 2 + while (processed_length < segment_length) { Page region list + region_id 8 Uniquely identifies a region within a page + reserved 8 + region_horizontal_address 16 Horizontal address of the top left pixel of this region + region_vertical_address 16 Vertical address of the top line of this region + } + } + */ + + PageComposition page = new PageComposition(); + + page.pageId = tsStream.readBits(16); + int remainingSegmentLength = tsStream.readBits(16); + page.pageTimeOut = tsStream.readBits(8); + page.pageVersionNumber = tsStream.readBits(4); + page.pageState = tsStream.readBits(2); + tsStream.skipBits(2); + + if (page.pageState == DVBSUB_PCS_STATE_NORMAL && + subtitleService.pageComposition != null && + subtitleService.pageComposition.pageId == page.pageId && + (subtitleService.pageComposition.pageVersionNumber + 1) % 16 == page.pageVersionNumber) { + //page.pageRegions = subtitleService.pageComposition.pageRegions; + + if (BuildConfig.DEBUG) { + Log.d(TAG, " Updated Page Composition. pageId: " + page.pageId + + " version: " + page.pageVersionNumber + + " timeout: " + page.pageTimeOut + ); + } + + } else if (subtitleService.subtitlePageId == page.pageId) { + if (BuildConfig.DEBUG) { + if (page.pageState == DVBSUB_PCS_STATE_NORMAL) { + Log.d(TAG, " FAILED Page Composition update. pageId: " + page.pageId + + " Version(Old/New): " + (subtitleService.pageComposition != null ? subtitleService.pageComposition.pageVersionNumber : "NaN") + "/" + page.pageVersionNumber); + } + } + + subtitleService.pageComposition = null; + subtitleService.regions = new SparseArray<>(); + subtitleService.cluts = new SparseArray<>(); + subtitleService.objects = new SparseArray<>(); + + if (BuildConfig.DEBUG) { + if (page.pageState != DVBSUB_PCS_STATE_NORMAL) { + Log.d(TAG, " New Page Composition. pageId: " + page.pageId + + " version: " + page.pageVersionNumber + + " timeout: " + page.pageTimeOut + ); + } + } + } + + remainingSegmentLength -= 2; + while (remainingSegmentLength > 0) { + PageRegion region = new PageRegion(); + + region.regionId = tsStream.readBits(8); + tsStream.skipBits(8); + region.regionHorizontalAddress = tsStream.readBits(16); + region.regionVerticalAddress = tsStream.readBits(16); + + if (BuildConfig.DEBUG) { + Log.d(TAG, " " + + (page.pageRegions.get(region.regionId) == null ? "New" : "Upd.") + + " Page Region. regionId: " + region.regionId + + " (x/y): (" + region.regionHorizontalAddress + "/" + region.regionVerticalAddress + ")"); + } + + page.pageRegions.put(region.regionId, region); + + remainingSegmentLength -= 6; + } + + return page; + } + + private RegionComposition parseRegionCompositionSegment() { + + /* Parse region composition segment. ETSI EN 300 743 7.2.3 + + SYNTAX SIZE SEMANTICS + --------------------------------------- ---- ------------------------------------------ + region_composition_segment() { + sync_byte 8 + segment_type 8 + page_id 16 + segment_length 16 + region_id 8 Uniquely identifies the region + region_version_number 4 Indicates the version of this region + region_fill_flag 1 If set the region is to be filled region_n-bit_pixel_code clut index + reserved 3 + region_width 16 Specifies the horizontal length of this region + region_height 16 Specifies the vertical length of the region + region_level_of_compatibility 3 Code that indicates the minimum bithdepth of CLUT + region_depth 3 Identifies the intended pixel depth for this region + reserved 2 + CLUT_id 8 Identifies the family of CLUTs that applies to this region + region_8-bit_pixel_code 8 Specifies the entry of the applied 8-bit CLUT as background colour + region_4-bit_pixel-code 4 Specifies the entry of the applied 4-bit CLUT as background colour + region_2-bit_pixel-code 2 Specifies the entry of the applied 2-bit CLUT as background colour + reserved 2 + while (processed_length < segment_length) { list of region objects + object_id 16 Identifies an object that is shown in the region + object_type 2 Identifies the type of object + object_provider_flag 2 How this object is provided + object_horizontal_position 12 Specifies the horizontal position of the top left pixel of this object + reserved 4 + object_vertical_position 12 Specifies the vertical position of the top left pixel of this object + if (object_type ==0x01 or object_type == 0x02){ UNSUPPORTED + foreground_pixel_code 8 + background_pixel_code 8 + } + } + } + */ + + RegionComposition region = new RegionComposition(); + + region.pageId = tsStream.readBits(16); + int remainingSegmentLength = tsStream.readBits(16); + region.regionId = tsStream.readBits(8); + region.regionVersionNumber = tsStream.readBits(4); + if (tsStream.readBits(1) == 1) { + region.flags |= REGION_FILL_FLAG; + } + tsStream.skipBits(3); + region.regionWidth = tsStream.readBits(16); + region.regionHeight = tsStream.readBits(16); + region.regionLevelOfCompatibility = tsStream.readBits(3); + region.regionDepth = tsStream.readBits(3); + tsStream.skipBits(2); + region.clutId = tsStream.readBits(8); + tsStream.skipBits(16); + + if (BuildConfig.DEBUG) { + Log.d(TAG, " New Region Composition. regionId: " + region.regionId + + " (w/h): (" + region.regionWidth + "/" + region.regionHeight + ")"); + } + + int arrayIndex = 0; // index by an incremental counter to allow repeating objects in one region + + if (subtitleService.pageComposition != null && subtitleService.pageComposition.pageId == region.pageId && + subtitleService.pageComposition.pageState == DVBSUB_PCS_STATE_NORMAL) { + RegionComposition tempRegion = subtitleService.regions.get(region.regionId); + if (tempRegion != null) { + region.regionObjects = tempRegion.regionObjects; + arrayIndex = region.regionObjects.size(); + } + } + + remainingSegmentLength -= 10; + RegionObject object; + while (remainingSegmentLength > 0) { + object = new RegionObject(); + + object.objectId = tsStream.readBits(16); + object.objectType = tsStream.readBits(2); + object.objectProvider = tsStream.readBits(2); + object.objectHorizontalPosition = tsStream.readBits(12); + tsStream.skipBits(4); + object.objectVerticalPosition = tsStream.readBits(12); + remainingSegmentLength -= 6; + + if (object.objectType == 0x01 || object.objectType == 0x02) { // Only seems to affect to char subtitles + object.foregroundPixelCode = tsStream.readBits(8); + object.backgroundPixelCode = tsStream.readBits(8); + remainingSegmentLength -= 2; + } + + if (BuildConfig.DEBUG) { + Log.d(TAG, " New Region Object[" + arrayIndex + "]." + + " objectId: " + object.objectId + + " (x/y): (" + object.objectHorizontalPosition + "/" + object.objectVerticalPosition + ")"); + } + + region.regionObjects.put(arrayIndex++, object); + } + + + return region; + } + + private ClutDefinition parseClutDefinitionSegment() { + + /* Parse CLUT definition segment. ETSI EN 300 743 7.2.4 + + SYNTAX SIZE SEMANTICS + --------------------------------------- ---- ------------------------------------------ + CLUT_definition_segment() { + sync_byte 8 + segment_type 8 + page_id 16 + segment_length 16 + CLUT-id 8 Uniquely identifies within a page the CLUT family + CLUT_version_number 4 Indicates the version of this segment data + reserved 4 + while (processed_length < segment_length) { Clut entries list + CLUT_entry_id 8 Specifies the entry number of the CLUT + 2-bit/entry_CLUT_flag 1 Indicates this entry is to be loaded into the 2-bit/entry CLUT + 4-bit/entry_CLUT_flag 1 Indicates this entry is to be loaded into the 4-bit/entry CLUT + 8-bit/entry_CLUT_flag 1 Indicates this entry is to be loaded into the 8-bit/entry CLUT + reserved 4 + full_range_flag 1 Indicates that the Y_value, Cr_value, Cb_value and T_value + fields have the full 8-bit resolution + if full_range_flag =='1' { + Y-value 8 The Y value for this CLUT entry. + Cr-value 8 The Cr value for this CLUT entry. + Cb-value 8 The Cb value for this CLUT entry. + T-value 8 The Transparency value for this CLUT entry. 0 = no transparency + } else { + Y-value 6 The Y value for this CLUT entry. + Cr-value 4 The Cr value for this CLUT entry. + Cb-value 4 The Cb value for this CLUT entry. + T-value 2 The Transparency value for this CLUT entry. 0 = no transparency + } + } + } + */ + + ClutDefinition clut = new ClutDefinition(); + clut.pageId = tsStream.readBits(16); + int remainingSegmentLength = tsStream.readBits(16); + clut.clutId = tsStream.readBits(8); + clut.clutVersionNumber = tsStream.readBits(4); + tsStream.skipBits(4); + + remainingSegmentLength -= 2; + ClutEntry entry; + int Y, Cb, Cr, T; + int entryId, entryFlags; + while (remainingSegmentLength > 0) { + entryId = tsStream.readBits(8); + entryFlags = tsStream.readBits(8); + + if ((entryFlags & 0x80) != 0) { + entry = clut.clutEntries2bit[entryId]; + } else if ((entryFlags & 0x40) != 0) { + entry = clut.clutEntries4bit[entryId]; + } else { + entry = clut.clutEntries8bit[entryId]; + } + + entry.flags = (byte) (entryFlags & 0xE1); + if ((entry.flags & 0x01) != 0) { + Y = tsStream.readBits(8); + Cr = tsStream.readBits(8); + Cb = tsStream.readBits(8); + T = tsStream.readBits(8); + remainingSegmentLength -= 6; + } else { + Y = tsStream.readBits(6) << 2; + Cr = tsStream.readBits(4) << 4; + Cb = tsStream.readBits(4) << 4; + T = tsStream.readBits(2) << 6; + remainingSegmentLength -= 4; + } + + if (Y == 0x00) { + Cr = 0x00; + Cb = 0x00; + T = 0xFF; + } + + entry.clutYCbCrT(Y, Cb, Cr, T); + } + return clut; + } + + private ObjectData parseObjectDataSegment() { + + /* Parse object data segment. ETSI EN 300 743 7.2.5 + + SYNTAX SIZE SEMANTICS + --------------------------------------- ---- ------------------------------------------ + object_data_segment() { + sync_byte 8 + segment_type 8 + page_id 16 + segment_length 16 + object_id 16 Uniquely identifies within the page the object + object_version_number 4 Indicates the version of this segment data + object_coding_method 2 Specifies the method used to code the object + non_modifying_colour_flag 1 Indicates that the CLUT entry value '1' is a non modifying colour + reserved 1 + if (object_coding_method == '00'){ + top_field_data_block_length 16 Specifies the number of bytes contained in his pixel-data_sub-blocks + bottom_field_data_block_length 16 Specifies the number of bytes contained in his pixel-data_sub-blocks + while(processed_length 0) { + switch (data.readBits(8)) { + case DVBSUB_DT_2BP_CODE_STRING: + if (regionDepth == DVBSUB_RCS_BITDEPTH_8) { + clutMapTable = clutMapTable28 == null ? defaultMap28 : clutMapTable28; + } else if (regionDepth == DVBSUB_RCS_BITDEPTH_4) { + clutMapTable = clutMapTable24 == null ? defaultMap24 : clutMapTable24; + } else { + clutMapTable = null; + } + column += dvbSub2BitPixelCodeString(data, lineHeight, clutEntries, clutMapTable, + column, line, (object.flags & OBJECT_NON_MODIFYING_COLOUR_FLAG) == 0); + if ((i = data.getPosition() % 8) != 0) { + data.skipBits(7 - i + 1); + } + break; + case DVBSUB_DT_4BP_CODE_STRING: + if (regionDepth == DVBSUB_RCS_BITDEPTH_8) + clutMapTable = clutMapTable48 == null ? defaultMap48 : clutMapTable48; + else + clutMapTable = null; + column += dvbSub4BitPixelCodeString(data, lineHeight, clutEntries, clutMapTable, + column, line, (object.flags & OBJECT_NON_MODIFYING_COLOUR_FLAG) == 0); + if ((i = data.getPosition() % 8) != 0) { + data.skipBits(7 - i + 1); + } + break; + case DVBSUB_DT_8BP_CODE_STRING: + column += dvbSub8BitPixelCodeString(data, lineHeight, clutEntries, null, + column, line, (object.flags & OBJECT_NON_MODIFYING_COLOUR_FLAG) == 0); + break; + case DVBSUB_DT_24_TABLE_DATA: + clutMapTable24 = new byte[4]; + for (i = 0; i < 4; i++) { + clutMapTable24[i] = (byte) data.readBits(4); + } + break; + case DVBSUB_DT_28_TABLE_DATA: + clutMapTable28 = new byte[4]; + for (i = 0; i < 4; i++) { + clutMapTable28[i] = (byte) data.readBits(8); + } + break; + case DVBSUB_DT_48_TABLE_DATA: + clutMapTable48 = new byte[16]; + for (i = 0; i < 4; i++) { + clutMapTable48[i] = (byte) data.readBits(8); + } + break; + case DVBSUB_DT_END_LINE: + column = horizontalAddress; + line += 2; + break; + default: + break; + } + } + field += lineHeight; + } + + return null; + } + + private int dvbSub2BitPixelCodeString(ParsableBitArray data, int lineHeigth, + ClutEntry[] clutEntries, byte[] clutMapTable, + int column, int line, boolean paint) { + + /* Parse 2-bit/pixel code string. ETSI EN 300 743 7.2.5.2 + + SYNTAX SIZE + --------------------------------------- ---- + 2-bit/pixel_code_string() { + if (nextbits() != '00') { + 2-bit_pixel-code 2 + } else { + 2-bit_zero 2 + switch_1 1 bslbf + if (switch_1 == '1') { + run_length_3-10 3 + 2-bit_pixel-code 2 + } else { + switch_2 1 + if (switch_2 == '0') { + switch_3 2 + if (switch_3 == '10') { + run_length_12-27 4 + 2-bit_pixel-code 2 + } + if (switch_3 == '11') { + run_length_29-284 8 + 2-bit_pixel-code 2 + } + } + } + } + } + */ + + int savedColumn = column, peek, runLength, clutIdx = 0x00, colour; + boolean endOfPixelCodeString = false; + + while (!endOfPixelCodeString) { + runLength = 0; + peek = data.readBits(2); + if (peek != 0x00) { + runLength = 1; + clutIdx = peek; + } else { + peek = data.readBits(1); + if (peek == 0x01) { + runLength = 3 + data.readBits(3); + clutIdx = data.readBits(2); + } else { + peek = data.readBits(1); + if (peek == 0x00) { + peek = data.readBits(2); + switch (peek) { + case 0x00: + endOfPixelCodeString = true; + break; + case 0x01: + runLength = 2; + clutIdx = 0x00; + break; + case 0x02: + runLength = 12 + data.readBits(4); + clutIdx = data.readBits(2); + break; + case 0x03: + runLength = 29 + data.readBits(8); + clutIdx = data.readBits(2); + break; + } + } + } + } + + if (runLength != 0 && paint) { + colour = clutMapTable != null ? clutEntries[clutMapTable[clutIdx]].ARGB + : clutEntries[clutIdx].ARGB; + defaultPaint.setColor(colour); + canvas.drawRect( + column, line, column + runLength, line + lineHeigth, defaultPaint); + } + + column += runLength; + } + + return column - savedColumn; + } + + private int dvbSub4BitPixelCodeString(ParsableBitArray data, int lineHeigth, + ClutEntry[] clutEntries, byte[] clutMapTable, + int column, int line, boolean paint) { + + /* Parse 4-bit/pixel code string. ETSI EN 300 743 7.2.5.2 + + SYNTAX SIZE + --------------------------------------- ---- + 4-bit/pixel_code_string() { + if (nextbits() != '0000') { + 4-bit_pixel-code 4 + } else { + 4-bit_zero 4 + switch_1 1 + if (switch_1 == '0') { + if (nextbits() != '000') + run_length_3-9 3 + else + end_of_string_signal 3 + } else { + switch_2 1 + if (switch_2 == '0') { + run_length_4-7 2 + 4-bit_pixel-code 4 + } else { + switch_3 2 + if (switch_3 == '10') { + run_length_9-24 4 + 4-bit_pixel-code 4 + } + if (switch_3 == '11') { + run_length_25-280 8 + 4-bit_pixel-code 4 + } + } + } + } + } + */ + + int savedColumn = column, peek, runLength, clutIdx = 0x00, colour; + boolean endOfPixelCodeString = false; + + while (!endOfPixelCodeString) { + runLength = 0; + peek = data.readBits(4); + if (peek != 0x00) { + runLength = 1; + clutIdx = peek; + } else { + peek = data.readBits(1); + if (peek == 0x00) { + peek = data.readBits(3); + if (peek != 0x00) { + runLength = 2 + peek; + clutIdx = 0x00; + } else { + endOfPixelCodeString = true; + } + } else { + peek = data.readBits(1); + if (peek == 0x00) { + runLength = 4 + data.readBits(2); + clutIdx = data.readBits(4); + } else { + peek = data.readBits(2); + switch (peek) { + case 0x00: + runLength = 1; + clutIdx = 0x00; + break; + case 0x01: + runLength = 2; + clutIdx = 0x00; + break; + case 0x02: + runLength = 9 + data.readBits(4); + clutIdx = data.readBits(4); + break; + case 0x03: + runLength = 25 + data.readBits(8); + clutIdx = data.readBits(4); + break; + } + } + } + } + + if (runLength != 0 && paint) { + colour = clutMapTable != null ? clutEntries[clutMapTable[clutIdx]].ARGB + : clutEntries[clutIdx].ARGB; + defaultPaint.setColor(colour); + canvas.drawRect( + column, line, column + runLength, line + lineHeigth, defaultPaint); + } + + column += runLength; + } + + return column - savedColumn; + } + + private int dvbSub8BitPixelCodeString(ParsableBitArray data, int lineHeigth, + ClutEntry[] clutEntries, byte[] clutMapTable, + int column, int line, boolean paint) { + + /* Parse 8-bit/pixel code string. ETSI EN 300 743 7.2.5.2 + + SYNTAX SIZE + --------------------------------------- ---- + + 8-bit/pixel_code_string() { + if (nextbits() != '0000 0000') { + 8-bit_pixel-code 8 + } else { + 8-bit_zero 8 + switch_1 1 + if switch_1 == '0' { + if nextbits() != '000 0000' + run_length_1-127 7 + else + end_of_string_signal 7 + } else { + run_length_3-127 7 + 8-bit_pixel-code 8 + } + } + } + */ + + int savedColumn = column, peek, runLength, clutIdx = 0x00, colour; + boolean endOfPixelCodeString = false; + + while (!endOfPixelCodeString) { + runLength = 0; + peek = data.readBits(8); + if (peek != 0x00) { + runLength = 1; + clutIdx = peek; + } else { + peek = data.readBits(1); + if (peek == 0x00) { + peek = data.readBits(7); + if (peek != 0x00) { + runLength = peek; + clutIdx = 0x00; + } else { + endOfPixelCodeString = true; + } + } else { + runLength = data.readBits(7); + clutIdx = data.readBits(8); + } + } + + if (runLength != 0 && paint) { + colour = clutMapTable != null ? clutEntries[clutMapTable[clutIdx]].ARGB + : clutEntries[clutIdx].ARGB; + defaultPaint.setColor(colour); + canvas.drawRect( + column, line, column + runLength, line + lineHeigth, defaultPaint); + } + + column += runLength; + } + + return column - savedColumn; + } + + /** + * Takes a subtitling packet, parses the included segments and returns the list of {@link Cue}s + * defined in them + * + * @param input + * @param inputSize + * @return list of {@link Cue}s contained in the packet or null if there is an error or subtitle + * is incomplete + */ + List dvbSubsDecode(byte[] input, int inputSize) { + + /* process PES PACKET. ETSI EN 300 743 7.1 + + SYNTAX SIZE SEMANTICS + --------------------------------------- ---- ------------------------------------------ + PES_data_field() { + data_identifier 8 For DVB subtitle streams it shall be 0x20 + subtitle_stream_id 8 For DVB subtitling stream it shall be 0x00 + while nextbits() == '0000 1111' { + Subtitling_segment() + } + end_of_PES_data_field_marker 8 An 8-bit field with fixed contents '1111 1111' + + */ + + if (input != null) { + tsStream = new ParsableBitArray(input, inputSize); + } else { + return null; + } + if (!isSet(FLAG_PES_STRIPPED_DVBSUB)) { + if (tsStream.readBits(8) != 0x20) { // data_identifier + return null; + } + if (tsStream.readBits(8) != 0x00) { // subtitle_stream_id + return null; + } + } + + if (BuildConfig.DEBUG) Log.d(TAG, "New PES subtitle packet."); + + int sync = tsStream.readBits(8); + // test for segment Sync Byte and account for possible additional wordalign byte in Object data segment + while (sync == 0x0f || (sync == 0x00 && (sync = tsStream.readBits(8)) == 0x0f)) { + parseSubtitlingSegment(); + if (isSet(FLAG_PES_STRIPPED_DVBSUB) && tsStream.bitsLeft() == 0) { + break; + } + sync = tsStream.readBits(8); + + } + + if (sync == 0xff || (isSet(FLAG_PES_STRIPPED_DVBSUB) && tsStream.bitsLeft() == 0)) { // end_of_PES_data_field_marker + // paint the current Subtitle definition + if (subtitleService.pageComposition != null) { + List cueList = new ArrayList<>(); + + if (BuildConfig.DEBUG) { + Log.d(TAG, "Rendering subtitle. w: " + subtitleService.displayDefinition.displayWidth + + " h: " + subtitleService.displayDefinition.displayHeight); + + if ((subtitleService.displayDefinition.flags & DISPLAY_WINDOW_FLAG) != 0) { + Log.d(TAG, " Window dimensions (x/y/w/h): (" + + subtitleService.displayDefinition.displayWindowHorizontalPositionMinimum + "/" + + subtitleService.displayDefinition.displayWindowVerticalPositionMinimum + "/" + + (subtitleService.displayDefinition.displayWindowHorizontalPositionMaximum - + subtitleService.displayDefinition.displayWindowHorizontalPositionMinimum) + "/" + + (subtitleService.displayDefinition.displayWindowVerticalPositionMaximum - + subtitleService.displayDefinition.displayWindowVerticalPositionMinimum) + ")"); + } + } + + int a, b; + PageRegion pageRegion; + RegionComposition regionComposition; + int baseHorizontalAddress, baseVerticalAddress; + ObjectData object; + ClutDefinition clut; + int regionKey; + // process page regions + for (a = 0; a < subtitleService.pageComposition.pageRegions.size(); a++) { + regionKey = subtitleService.pageComposition.pageRegions.keyAt(a); + pageRegion = subtitleService.pageComposition.pageRegions.get(regionKey); + regionComposition = subtitleService.regions.get(regionKey); + + baseHorizontalAddress = pageRegion.regionHorizontalAddress; + baseVerticalAddress = pageRegion.regionVerticalAddress; + + if ((subtitleService.displayDefinition.flags & DISPLAY_WINDOW_FLAG) != 0) { + baseHorizontalAddress += + subtitleService.displayDefinition.displayWindowHorizontalPositionMinimum; + baseVerticalAddress += + subtitleService.displayDefinition.displayWindowVerticalPositionMinimum; + } + + // clip object drawing to the current region and display definition window + canvas.clipRect( + baseHorizontalAddress, baseVerticalAddress, + Math.min(baseHorizontalAddress + regionComposition.regionWidth, + subtitleService.displayDefinition.displayWindowHorizontalPositionMaximum), + Math.min(baseVerticalAddress + regionComposition.regionHeight, + subtitleService.displayDefinition.displayWindowVerticalPositionMaximum), + Region.Op.REPLACE); + + if ((clut = subtitleService.cluts.get(regionComposition.clutId)) == null) { + if ((clut = subtitleService.ancillaryCluts.get(regionComposition.clutId)) == null) { + clut = defaultClut; + } + } + + if (BuildConfig.DEBUG) { + Log.d(TAG, " Region: " + regionKey + " (x/y/w/h): (" + + baseHorizontalAddress + "/" + baseVerticalAddress + "/" + + (baseHorizontalAddress + regionComposition.regionWidth - 1) + "/" + + (baseVerticalAddress + regionComposition.regionHeight - 1) + ")" + ); + + canvas.drawRect( + baseHorizontalAddress, baseVerticalAddress, + baseHorizontalAddress + regionComposition.regionWidth - 1, + baseVerticalAddress + regionComposition.regionHeight - 1, + debugRegionPaint); + } + + RegionObject regionObject; + int objectKey; + // process regions compositions + for (b = 0; b < regionComposition.regionObjects.size(); b++) { + objectKey = regionComposition.regionObjects.keyAt(b); + regionObject = regionComposition.regionObjects.get(objectKey); + + if (BuildConfig.DEBUG) { + Log.d(TAG, " Object[" + objectKey + "]. objectId: " + regionObject.objectId + " (x/y): (" + + (baseHorizontalAddress + regionObject.objectHorizontalPosition) + "/" + + (baseVerticalAddress + regionObject.objectVerticalPosition) + ")" + ); + + canvas.drawRect( + baseHorizontalAddress + regionObject.objectHorizontalPosition, + baseVerticalAddress + regionObject.objectVerticalPosition, + baseHorizontalAddress + regionObject.objectHorizontalPosition + regionComposition.regionWidth - 1, + baseVerticalAddress + regionObject.objectVerticalPosition + regionComposition.regionHeight - 1, + debugObjectPaint); + } + + if ((object = subtitleService.objects.get(regionObject.objectId)) == null) { + if ((object = subtitleService.ancillaryObjects.get(regionObject.objectId)) == null) { + continue; + } + } + + parsePixelDataSubBlocks(object, clut, regionComposition.regionDepth, + baseHorizontalAddress + regionObject.objectHorizontalPosition, + baseVerticalAddress + regionObject.objectVerticalPosition); + + } + + // fill the region if needed + if ((regionComposition.flags & REGION_FILL_FLAG) != 0) { + int colour; + if (regionComposition.regionDepth == DVBSUB_RCS_BITDEPTH_8) { + colour = clut.clutEntries8bit[regionComposition.region8bitPixelCode].ARGB; + } else if (regionComposition.regionDepth == DVBSUB_RCS_BITDEPTH_4) { + colour = clut.clutEntries4bit[regionComposition.region4bitPixelCode].ARGB; + } else { + colour = clut.clutEntries2bit[regionComposition.region2bitPixelCode].ARGB; + } + + fillRegionPaint.setColor(colour); + + canvas.drawRect( + baseHorizontalAddress, baseVerticalAddress, + baseHorizontalAddress + regionComposition.regionWidth, + baseVerticalAddress + regionComposition.regionHeight, + fillRegionPaint); + } + + Bitmap cueBitmap = Bitmap.createBitmap(bitmap, + baseHorizontalAddress, baseVerticalAddress, + regionComposition.regionWidth, regionComposition.regionHeight); + canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR); + regionComposition.cue = new Cue(cueBitmap, + (float) baseHorizontalAddress / subtitleService.displayDefinition.displayWidth, Cue.ANCHOR_TYPE_START, + (float) baseVerticalAddress / subtitleService.displayDefinition.displayHeight, Cue.ANCHOR_TYPE_START, + (float) regionComposition.regionWidth / subtitleService.displayDefinition.displayWidth, + (float) regionComposition.regionHeight / subtitleService.displayDefinition.displayHeight); + cueList.add(regionComposition.cue); + } + + return cueList; + } + } else { + Log.d(TAG, "Unexpected..."); + } + return null; + } + + private boolean isSet(@Flags int flag) { + return (flags & flag) != 0; + } + +} diff --git a/library/core/src/main/java/com/google/android/exoplayer2/text/dvbsubs/DvbSubsSubtitle.java b/library/core/src/main/java/com/google/android/exoplayer2/text/dvb/DvbSubtitle.java similarity index 87% rename from library/core/src/main/java/com/google/android/exoplayer2/text/dvbsubs/DvbSubsSubtitle.java rename to library/core/src/main/java/com/google/android/exoplayer2/text/dvb/DvbSubtitle.java index ae4444b7a7..d614f1c498 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/text/dvbsubs/DvbSubsSubtitle.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/text/dvb/DvbSubtitle.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.google.android.exoplayer2.text.dvbsubs; +package com.google.android.exoplayer2.text.dvb; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.text.Cue; @@ -22,10 +22,13 @@ import com.google.android.exoplayer2.text.Subtitle; import java.util.Collections; import java.util.List; -final class DvbSubsSubtitle implements Subtitle { +/** + * A representation of a DVB subtitle. + */ +/* package */ final class DvbSubtitle implements Subtitle { private final List cues; - public DvbSubsSubtitle(List cues) { + public DvbSubtitle(List cues) { if (cues == null) { this.cues = Collections.emptyList(); } else { diff --git a/library/core/src/main/java/com/google/android/exoplayer2/text/dvbsubs/DvbSubsDecoder.java b/library/core/src/main/java/com/google/android/exoplayer2/text/dvbsubs/DvbSubsDecoder.java deleted file mode 100644 index 0f4811f339..0000000000 --- a/library/core/src/main/java/com/google/android/exoplayer2/text/dvbsubs/DvbSubsDecoder.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.google.android.exoplayer2.text.dvbsubs; - -import com.google.android.exoplayer2.text.SimpleSubtitleDecoder; - -import java.util.List; - -public final class DvbSubsDecoder extends SimpleSubtitleDecoder { - private final String TAG = "DVBSubs Decoder"; - - private int subtitilingType; - private int subtitleCompositionPage; - private int subtitleAncillaryPage; - private String subtitleContainer; - - private int flags = 0; - - DvbSubtitlesParser parser; - - public DvbSubsDecoder() { - super("dvbsubs"); - parser = new DvbSubtitlesParser(); - } - - public DvbSubsDecoder(List initializationData) { - super("dvbsubs"); - - byte[] tempByteArray; - - tempByteArray = initializationData.get(0); - subtitilingType = tempByteArray != null ? tempByteArray[0] & 0xFF: -1; - - tempByteArray = initializationData.get(3); - if (tempByteArray != null ) { - subtitleContainer = new String(tempByteArray); - if (subtitleContainer.equals("mkv")) { - flags |= DvbSubtitlesParser.FLAG_PES_STRIPPED_DVBSUB; - } - } - - if ((tempByteArray = initializationData.get(1)) != null) { - this.subtitleCompositionPage = ((tempByteArray[0] & 0xFF) << 8) | (tempByteArray[1] & 0xFF); - if ((tempByteArray = initializationData.get(2)) != null) { - this.subtitleAncillaryPage = ((tempByteArray[0] & 0xFF) << 8) | (tempByteArray[1] & 0xFF); - parser = new DvbSubtitlesParser(this.subtitleCompositionPage, this.subtitleAncillaryPage, flags); - } - } else { - parser = new DvbSubtitlesParser(); - } - - } - - @Override - protected DvbSubsSubtitle decode(byte[] data, int length) { - return new DvbSubsSubtitle(parser.dvbSubsDecode(data, length)); - } -} \ No newline at end of file diff --git a/library/core/src/main/java/com/google/android/exoplayer2/text/dvbsubs/DvbSubtitlesParser.java b/library/core/src/main/java/com/google/android/exoplayer2/text/dvbsubs/DvbSubtitlesParser.java deleted file mode 100644 index 47432bcf07..0000000000 --- a/library/core/src/main/java/com/google/android/exoplayer2/text/dvbsubs/DvbSubtitlesParser.java +++ /dev/null @@ -1,1596 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.google.android.exoplayer2.text.dvbsubs; - -import android.graphics.Bitmap; -import android.graphics.Canvas; -import android.graphics.Color; -import android.graphics.Paint; -import android.graphics.PorterDuff; -import android.graphics.DashPathEffect; -import android.graphics.PorterDuffXfermode; -import android.graphics.Region; -import android.support.annotation.IntDef; -import android.util.Log; -import android.util.SparseArray; - -import com.google.android.exoplayer2.core.BuildConfig; -import com.google.android.exoplayer2.text.Cue; -import com.google.android.exoplayer2.util.ParsableBitArray; - -import java.util.ArrayList; -import java.util.List; - -/** - * Parse and generate a list of {@link Cue}s from DVB subtitling bitstream - */ -public class DvbSubtitlesParser { - - private static final String TAG = "DVBSubs"; - - @IntDef(flag = true, value = {FLAG_PES_STRIPPED_DVBSUB}) - public @interface Flags { - } - public static final int FLAG_PES_STRIPPED_DVBSUB = 1; - - @Flags private final int flags; - - /* List of different SEGMENT TYPES */ - /* According to EN 300-743, table 2 */ - private final static int DVBSUB_ST_PAGE_COMPOSITION = 0x10; - private final static int DVBSUB_ST_REGION_COMPOSITION = 0x11; - private final static int DVBSUB_ST_CLUT_DEFINITION = 0x12; - private final static int DVBSUB_ST_OBJECT_DATA = 0x13; - private final static int DVBSUB_ST_DISPLAY_DEFINITION = 0x14; - private final static int DVBSUB_ST_ENDOFDISPLAY = 0x80; - private final static int DVBSUB_ST_STUFFING = 0xff; - - /* List of different Page Composition Segment state */ - /* According to EN 300-743, 7.2.1 table 3 */ - private final static int DVBSUB_PCS_STATE_NORMAL = 0b00; // Update. Only changed elements. - private final static int DVBSUB_PCS_STATE_ACQUISITION = 0b01; // Refresh. All subtitle elements. - private final static int DVBSUB_PCS_STATE_CHANGE = 0b10; // New. All subtitle elements. - - /* List of different Region Composition Segments CLUT level oc compatibility */ - /* According to EN 300-743, 7.2.1 table 4 */ - private final static int DVBSUB_RCS_CLUT_2 = 0x01; - private final static int DVBSUB_RCS_CLUT_4 = 0x02; - private final static int DVBSUB_RCS_CLUT_8 = 0x03; - - /* List of different Region Composition Segments bit depths */ - /* According to EN 300-743, 7.2.1 table 5 */ - private final static int DVBSUB_RCS_BITDEPTH_2 = 0x01; - private final static int DVBSUB_RCS_BITDEPTH_4 = 0x02; - private final static int DVBSUB_RCS_BITDEPTH_8 = 0x03; - - /* List of different object types in the Region Composition Segment */ - /* According to EN 300-743, table 6 */ - private final static int DVBSUB_OT_BASIC_BITMAP = 0x00; - private final static int DVBSUB_OT_BASIC_CHAR = 0x01; - private final static int DVBSUB_OT_COMPOSITE_STRING = 0x02; - - /* List of different object coding methods in the Object Data Segment */ - /* According to EN 300-743, table 8 */ - private static final int DVBSUB_ODS_PIXEL_CODED = 0x00; - private static final int DVBSUB_ODS_CHAR_CODED = 0x01; - - /* Pixel DATA TYPES */ - /* According to EN 300-743, table 9 */ - private final static int DVBSUB_DT_2BP_CODE_STRING = 0x10; - private final static int DVBSUB_DT_4BP_CODE_STRING = 0x11; - private final static int DVBSUB_DT_8BP_CODE_STRING = 0x12; - private final static int DVBSUB_DT_24_TABLE_DATA = 0x20; - private final static int DVBSUB_DT_28_TABLE_DATA = 0x21; - private final static int DVBSUB_DT_48_TABLE_DATA = 0x22; - private final static int DVBSUB_DT_END_LINE = 0xf0; - - /* Clut mapping tables */ - /* According to EN 300-743, 10.4 10.5 10.6 */ - private byte[] defaultMap24 = {(byte) 0x00, (byte) 0x07, (byte) 0x08, (byte) 0x0f }; - private byte[] defaultMap28 = {(byte) 0x00, (byte) 0x77, (byte) 0x88, (byte) 0xff }; - private byte[] defaultMap48 = {(byte) 0x00, (byte) 0x11, (byte) 0x22, (byte) 0x33, - (byte) 0x44, (byte) 0x55, (byte) 0x66, (byte) 0x77, - (byte) 0x88, (byte) 0x99, (byte) 0xaa, (byte) 0xbb, - (byte) 0xcc, (byte) 0xdd, (byte) 0xee, (byte) 0xff}; - - /* FLAGS */ - private final static int DISPLAY_WINDOW_FLAG = 0x01; - - private final static int REGION_FILL_FLAG = 0x01; - - private final static int OBJECT_NON_MODIFYING_COLOUR_FLAG = 0x01; - - /* Constants */ - private static final int UNDEF_PAGE = -1; - - /* instance variables */ - private Paint defaultPaint = new Paint(); - private Paint fillRegionPaint = new Paint(); - private Paint debugRegionPaint = new Paint(); - private Paint debugObjectPaint = new Paint(); - private Bitmap bitmap; - private Canvas canvas = new Canvas(); - private ClutDefinition defaultClut = new ClutDefinition(); - - private static ParsableBitArray tsStream; - private SubtitleService subtitleService; - - /* - * Contains the current subtitle service definition - */ - private class SubtitleService { - int subtitlePageId; - int ancillaryPageId; - - // subtitle page - DisplayDefinition displayDefinition; - PageComposition pageComposition; - SparseArray regions = new SparseArray<>(); - SparseArray cluts = new SparseArray<>(); - SparseArray objects = new SparseArray<>(); - - // ancillary page - SparseArray ancillaryCluts = new SparseArray<>(); - SparseArray ancillaryObjects = new SparseArray<>(); - } - - /* The display definition contains the geometry and active area of the subtitle service [7.2.1] */ - private class DisplayDefinition { - int pageId; - int versionNumber; - - int displayWidth = 719; - int displayHeight = 575; - - int flags; - int displayWindowHorizontalPositionMinimum = 0; - int displayWindowHorizontalPositionMaximum = 719; - int displayWindowVerticalPositionMinimum = 0; - int displayWindowVerticalPositionMaximum = 575; - - void updateBitmapResolution() { - bitmap = Bitmap.createBitmap(this.displayWidth + 1, this.displayHeight + 1, - Bitmap.Config.ARGB_8888); - canvas = new Canvas(bitmap); - } - } - - /* The page is the definition and arrangement of regions in the screen [7.2.2] */ - private class PageComposition { - int pageId; - int pageTimeOut; /* in seconds */ - int pageVersionNumber; - int pageState; - SparseArray pageRegions = new SparseArray<>(); - } - - private class PageRegion { - int regionId; - int regionHorizontalAddress; - int regionVerticalAddress; - } - - /* The Region is an area of the page [7.2.3] composed of a list of objects and a CLUT */ - private class RegionComposition { - int pageId; - int regionId; - int regionVersionNumber; - int flags; - int regionWidth; - int regionHeight; - int regionLevelOfCompatibility; - int regionDepth; - int clutId; - int region8bitPixelCode; - int region4bitPixelCode; - int region2bitPixelCode; - SparseArray regionObjects = new SparseArray<>(); - - /* - * We maintain a reference to the Cue to implement future drawing optimizations, no re-render in case of: - * - * - Page updates not affecting region composition (no clut change/redefinition, no object changes) - * - Incremental subtitle display render (e.g. live captions updates) - */ - Cue cue; - } - - private class RegionObject { - int objectId; - int objectType; - int objectProvider; - int objectHorizontalPosition; - int objectVerticalPosition; - int foregroundPixelCode; - int backgroundPixelCode; - } - - /* An entry in the palette CLUT and associated color space translation methods */ - private class ClutEntry { - int clutEntryId; - byte flags; - byte Y; - byte Cr; - byte Cb; - byte T; - - byte A; - byte R; - byte G; - byte B; - int ARGB; - - void clutYCbCrT (int Y, int Cb, int Cr, int T) { - - this.Y = (byte) Y; - this.Cb = (byte) Cb; - this.Cr = (byte) Cr; - this.T = (byte) T; - - int R = (int) (Y + 1.40200 * (Cr - 128)); - int G = (int) (Y - 0.34414 * (Cb - 128) - 0.71414 * (Cr - 128)); - int B = (int) (Y + 1.77200 * (Cb - 128)); - - if (R > 255) this.R = (byte) 255; - else if (R < 0) this.R = 0; - else this.R = (byte) R; - - if (G > 255) this.G = (byte) 255; - else if (G < 0) this.G = 0; - else this.G = (byte) G; - - if (B > 255) this.B = (byte) 255; - else if (B < 0) this.B = 0; - else this.B = (byte) B; - - this.A = (byte) (0xFF - (this.T & 0xFF)); - this.ARGB = - ((this.A & 0xFF) << 24) | - ((this.R & 0xFF) << 16) | - ((this.G & 0xFF) << 8) | - (this.B & 0xFF); - - } - - void clutRGBA (int R, int G, int B, int A) { - - this.A = (byte) A; - this.R = (byte) R; - this.G = (byte) G; - this.B = (byte) B; - - this.ARGB = - ((A & 0xFF) << 24) | - ((R & 0xFF) << 16) | - ((G & 0xFF) << 8) | - (B & 0xFF); - - int y = (int) ( 0.299000 * R + 0.587000 * G + 0.114000 * B); - int Cb = 128 + (int) (-0.168736 * R + -0.331264 * G + 0.500000 * B); - int Cr = 128 + (int) ( 0.500000 * R + -0.418688 * G + -0.081312 * B); - - if (y > 255) this.Y = (byte) 255; - else if (y < 0) this.Y = 0; - else this.Y = (byte) y; - - if (Cb > 255) this.Cb = (byte) 255; - else if (Cb < 0) this.Cb = 0; - else this.Cb = (byte) Cb; - - if (Cr > 255) this.Cr = (byte) 255; - else if (Cr < 0) this.Cr = 0; - else this.Cr = (byte) Cr; - - this.T = (byte) (0xFF - (this.A & 0xFF)); - } - } - - /* CLUT family definition containing the color tables for the three bitdepths defined [7.2.4] */ - private class ClutDefinition { - int pageId; - int clutId; - int clutVersionNumber; - ClutEntry[] clutEntries2bit; - ClutEntry[] clutEntries4bit; - ClutEntry[] clutEntries8bit; - - ClutEntry[] generateDefault2bitClut() { - ClutEntry[] entries = new ClutEntry[4]; - - entries[0] = new ClutEntry(); - entries[0].clutRGBA(0x00, 0x00, 0x00, 0x00); - entries[1] = new ClutEntry(); - entries[1].clutRGBA(0xFF, 0xFF, 0xFF, 0xFF); - entries[2] = new ClutEntry(); - entries[2].clutRGBA(0x00, 0x00, 0x00, 0xFF); - entries[3] = new ClutEntry(); - entries[3].clutRGBA(0x7F, 0x7F, 0x7F, 0xFF); - - return entries; - } - - ClutEntry[] generateDefault4bitClut() { - ClutEntry[] entries = new ClutEntry[16]; - - entries[0] = new ClutEntry(); - entries[0].clutRGBA(0x00, 0x00, 0x00, 0x00); - - int i = 15; - while (i > 0) { - entries[i] = new ClutEntry(); - if (i < 8) { - entries[i].clutRGBA( - ((i & 0x01) != 0 ? 0xFF : 0x00), - ((i & 0x02) != 0 ? 0xFF : 0x00), - ((i & 0x04) != 0 ? 0xFF : 0x00), - 0xFF); - } else { - entries[i].clutRGBA( - ((i & 0x01) != 0 ? 0x7F : 0x00), - ((i & 0x02) != 0 ? 0x7F : 0x00), - ((i & 0x04) != 0 ? 0x7F : 0x00), - 0xFF); - } - - i--; - } - - return entries; - } - - ClutEntry[] generateDefault8bitClut() { - ClutEntry[] entries = new ClutEntry[256]; - - entries[0] = new ClutEntry(); - entries[0].clutRGBA(0x00, 0x00, 0x00, 0x00); - - int i = 255; - while (i > 0) { - entries[i] = new ClutEntry(); - if (i < 8) { - entries[i].clutRGBA( - ((i & 0x01) != 0 ? 0xFF : 0x00), - ((i & 0x02) != 0 ? 0xFF : 0x00), - ((i & 0x04) != 0 ? 0xFF : 0x00), - 0x3F); - } else { - switch (i & 0x88) { - case 0x00: - entries[i].clutRGBA( - (((i & 0x01) != 0 ? 0x55 : 0x00) + ((i & 0x10) != 0 ? 0xAA : 0x00)), - (((i & 0x02) != 0 ? 0x55 : 0x00) + ((i & 0x20) != 0 ? 0xAA : 0x00)), - (((i & 0x04) != 0 ? 0x55 : 0x00) + ((i & 0x40) != 0 ? 0xAA : 0x00)), - 0xFF); - break; - case 0x08: - entries[i].clutRGBA( - (((i & 0x01) != 0 ? 0x55 : 0x00) + ((i & 0x10) != 0 ? 0xAA : 0x00)), - (((i & 0x02) != 0 ? 0x55 : 0x00) + ((i & 0x20) != 0 ? 0xAA : 0x00)), - (((i & 0x04) != 0 ? 0x55 : 0x00) + ((i & 0x40) != 0 ? 0xAA : 0x00)), - 0x7F); - break; - case 0x80: - entries[i].clutRGBA( - (127 + ((i & 0x01) != 0 ? 0x2B : 0x00) + ((i & 0x10) != 0 ? 0x55 : 0x00)), - (127 + ((i & 0x02) != 0 ? 0x2B : 0x00) + ((i & 0x20) != 0 ? 0x55 : 0x00)), - (127 + ((i & 0x04) != 0 ? 0x2B : 0x00) + ((i & 0x40) != 0 ? 0x55 : 0x00)), - 0xFF); - break; - case 0x88: - entries[i].clutRGBA( - (((i & 0x01) != 0 ? 0x2B : 0x00) + ((i & 0x10) != 0 ? 0x55 : 0x00)), - (((i & 0x02) != 0 ? 0x2B : 0x00) + ((i & 0x20) != 0 ? 0x55 : 0x00)), - (((i & 0x04) != 0 ? 0x2B : 0x00) + ((i & 0x40) != 0 ? 0x55 : 0x00)), - 0xFF); - break; - } - - } - - i--; - } - - return entries; - } - - ClutDefinition () { - clutEntries2bit = generateDefault2bitClut(); - clutEntries4bit = generateDefault4bitClut(); - clutEntries8bit = generateDefault8bitClut(); - } - - } - - /* The object data segment contains the textual/graphical representation of an object [7.2.5] */ - private class ObjectData { - int pageId; - int objectId; - int objectVersionNumber; - int objectCodingMethod; - byte flags; - int topFieldDataLength; - byte[] topFieldData; - int bottomFieldDataLength; - byte[] bottomFieldData; - int numberOfCodes; - } - - /** - * Construct a subtitle service with default subtitle pageId - */ - DvbSubtitlesParser() { - this(1); - } - - /** - * Construct a subtitle service for the given subtitle pageId - * - * @param subtitlePageId The subtitle page Id carrying the selected subtitle track - */ - DvbSubtitlesParser(int subtitlePageId) { - this(subtitlePageId, UNDEF_PAGE); - } - - /** - * Construct a subtitle service for the given subtitle and ancillary pageIds - * - * @param subtitlePageId The Id of the subtitle page carrying the selected subtitle track - * @param ancillaryPageId Id of the common subtitle page containing additional data for the current - * subtitle track - */ - DvbSubtitlesParser(int subtitlePageId, int ancillaryPageId) { - this(subtitlePageId, ancillaryPageId, 0); - } - - /** - * Construct a subtitle service for the given subtitle and ancillary pageIds - * - * @param subtitlePageId The Id of the subtitle page carrying the selected subtitle track - * @param ancillaryPageId Id of the common subtitle page containing additional data for the current - * subtitle track - * @param flags additional initialisation info to properly configure the parser - */ - DvbSubtitlesParser(int subtitlePageId, int ancillaryPageId, @Flags int flags) { - this.subtitleService = new SubtitleService(); - this.flags = flags; - - this.defaultPaint.setStyle(Paint.Style.FILL_AND_STROKE); - this.defaultPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC)); - this.defaultPaint.setPathEffect(null); - - this.fillRegionPaint.setStyle(Paint.Style.FILL); - this.fillRegionPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OVER)); - this.fillRegionPaint.setPathEffect(null); - - this.debugRegionPaint.setColor(0xff00ff00); - this.debugRegionPaint.setStyle(Paint.Style.STROKE); - this.debugRegionPaint.setPathEffect(new DashPathEffect(new float[] {2,2}, 0)); - - this.debugObjectPaint.setColor(0xffff0000); - this.debugObjectPaint.setStyle(Paint.Style.STROKE); - this.debugObjectPaint.setPathEffect(new DashPathEffect(new float[]{10, 20}, 0)); - - this.subtitleService.subtitlePageId = subtitlePageId; - this.subtitleService.ancillaryPageId = ancillaryPageId; - - this.subtitleService.displayDefinition = new DisplayDefinition(); - this.subtitleService.displayDefinition.updateBitmapResolution(); - } - - private void parseSubtitlingSegment() { - - /* Parse subtitling segment. ETSI EN 300 743 7.2 - - SYNTAX SIZE SEMANTICS - --------------------------------------- ---- ------------------------------------------ - Subtitling_segment() { - sync_byte 8 An 8-bit field that shall be coded with the value '0000 1111' - segment_type 8 Indicates the type of data contained in the segment data field - page_id 16 Identifies the subtitle service of the data contained in this subtitling_segment - segment_length 16 Number of bytes contained in the segment_data_field - segment_data_field() This is the payload of the segment - - */ - - int pageId, segmentId, segmentLength; - segmentId = tsStream.readBits(8); - switch (segmentId) { - case DVBSUB_ST_DISPLAY_DEFINITION: - if (BuildConfig.DEBUG) Log.d(TAG, " Parse Display Definition segment."); - DisplayDefinition tempDisplay = parseDisplayDefinitionSegment(); - if (tempDisplay != null && tempDisplay.pageId == subtitleService.subtitlePageId) { - if (tempDisplay.displayWidth != subtitleService.displayDefinition.displayWidth || - tempDisplay.displayHeight != subtitleService.displayDefinition.displayHeight || - tempDisplay.displayWindowHorizontalPositionMaximum != subtitleService.displayDefinition.displayWindowHorizontalPositionMaximum || - tempDisplay.displayWindowHorizontalPositionMinimum != subtitleService.displayDefinition.displayWindowHorizontalPositionMinimum || - tempDisplay.displayWindowVerticalPositionMaximum != subtitleService.displayDefinition.displayWindowVerticalPositionMaximum || - tempDisplay.displayWindowVerticalPositionMinimum != subtitleService.displayDefinition.displayWindowVerticalPositionMinimum || - tempDisplay.flags != subtitleService.displayDefinition.flags) { - subtitleService.displayDefinition = tempDisplay; - subtitleService.displayDefinition.updateBitmapResolution(); - } else { - subtitleService.displayDefinition.versionNumber = tempDisplay.versionNumber; - } - - if (BuildConfig.DEBUG) Log.d(TAG + "/DDS", " [versionNumber] = " + tempDisplay.versionNumber + - " [width/height] = " + (tempDisplay.displayWidth + 1) + "/" + (tempDisplay.displayHeight + 1) + - " Window[minX/minY/maxX/maxY] = " + tempDisplay.displayWindowHorizontalPositionMinimum + - "/" + tempDisplay.displayWindowVerticalPositionMinimum + - "/" + tempDisplay.displayWindowHorizontalPositionMaximum + - "/" + tempDisplay.displayWindowVerticalPositionMaximum - ); - } - break; - case DVBSUB_ST_PAGE_COMPOSITION: - if (BuildConfig.DEBUG) Log.d(TAG, " Parse Page Composition segment."); - PageComposition tempPage = parsePageCompositionSegment(); - if (tempPage != null && tempPage.pageId == subtitleService.subtitlePageId) { - if (tempPage.pageState == DVBSUB_PCS_STATE_NORMAL && subtitleService.pageComposition == null) - break; - subtitleService.pageComposition = tempPage; - } - break; - case DVBSUB_ST_REGION_COMPOSITION: - if (BuildConfig.DEBUG) Log.d(TAG, " Parse Region Composition segment."); - RegionComposition tempRegionComposition = parseRegionCompositionSegment(); - if (tempRegionComposition != null && tempRegionComposition.pageId == subtitleService.subtitlePageId) { - subtitleService.regions.put(tempRegionComposition.regionId, tempRegionComposition); - } - break; - case DVBSUB_ST_CLUT_DEFINITION: - if (BuildConfig.DEBUG) Log.d(TAG, " Parse Clut Definition segment."); - ClutDefinition tempClutDefinition = parseClutDefinitionSegment(); - if (tempClutDefinition != null ) { - if (tempClutDefinition.pageId == subtitleService.subtitlePageId) { - subtitleService.cluts.put(tempClutDefinition.clutId, tempClutDefinition); - } else if (tempClutDefinition.pageId == subtitleService.ancillaryPageId) { - subtitleService.ancillaryCluts.put(tempClutDefinition.clutId, tempClutDefinition); - } - } - break; - case DVBSUB_ST_OBJECT_DATA: - if (BuildConfig.DEBUG) Log.d(TAG, " Parse Object Data segment."); - ObjectData tempObjectData = parseObjectDataSegment(); - if (tempObjectData != null) { - if (tempObjectData.pageId == subtitleService.subtitlePageId) { - subtitleService.objects.put(tempObjectData.objectId, tempObjectData); - } else if (tempObjectData.pageId == subtitleService.ancillaryPageId) { - subtitleService.ancillaryObjects.put(tempObjectData.objectId, tempObjectData); - } - } - break; - case DVBSUB_ST_ENDOFDISPLAY: - pageId = tsStream.readBits(16); - segmentLength = tsStream.readBits(16); - if (BuildConfig.DEBUG) Log.d(TAG, "pageId " + pageId + "end of display size = " + segmentLength); - tsStream.skipBits(segmentLength * 8); - break; - case DVBSUB_ST_STUFFING: - pageId = tsStream.readBits(16); - segmentLength = tsStream.readBits(16); - if (BuildConfig.DEBUG) Log.d(TAG, "pageId " + pageId + "stuffing size = " + segmentLength); - tsStream.skipBits(segmentLength * 8); - break; - default: - break; - } - } - - private DisplayDefinition parseDisplayDefinitionSegment() { - - /* Parse display definition segment. ETSI EN 300 743 7.2.1 - - SYNTAX SIZE SEMANTICS - --------------------------------------- ---- ------------------------------------------ - display_definition_segment(){ - sync_byte 8 An 8-bit field that shall be coded with the value '0000 1111' - segment_type 8 Indicates the type of data contained in the segment data field - page_id 16 Identifies the subtitle service of the data contained in this subtitling_segment - segment_length 16 Number of bytes contained in the segment_data_field - dds_version_number 4 Incremented when any of the contents of this segment change - display_window_flag 1 if "1" display the subtitle in the defined window - reserved 3 - display_width 16 Specifies the maximum horizontal width of the display in pixels minus 1 - display_height 16 Specifies the maximum vertical height of the display in lines minus 1 - if (display_window_flag == 1) { With origin in the top-left of the screen: - display_window_horizontal_position_minimum - 16 Specifies the left-hand most pixel of this DVB subtitle display set - display_window_horizontal_position_maximum - 16 Specifies the right-hand most pixel of this DVB subtitle display set - display_window_vertical_position_minimum - 16 Specifies the upper most line of this DVB subtitle display set - display_window_vertical_position_maximum - 16 Specifies the bottom line of this DVB subtitle display set - } - } - */ - - DisplayDefinition display = new DisplayDefinition(); - - display.pageId = tsStream.readBits(16); - tsStream.skipBits(16); - display.versionNumber = tsStream.readBits(4); - if (tsStream.readBits(1) == 1) { - display.flags |= DISPLAY_WINDOW_FLAG; - } - tsStream.skipBits(3); - display.displayWidth = tsStream.readBits(16); - display.displayHeight = tsStream.readBits(16); - if ((display.flags & DISPLAY_WINDOW_FLAG) != 0) { - display.displayWindowHorizontalPositionMinimum = tsStream.readBits(16); - display.displayWindowHorizontalPositionMaximum = tsStream.readBits(16); - display.displayWindowVerticalPositionMinimum = tsStream.readBits(16); - display.displayWindowVerticalPositionMaximum = tsStream.readBits(16); - } else { - display.displayWindowHorizontalPositionMinimum = 0; - display.displayWindowHorizontalPositionMaximum = display.displayWidth; - display.displayWindowVerticalPositionMinimum = 0; - display.displayWindowVerticalPositionMaximum = display.displayHeight; - } - - return display; - } - - private PageComposition parsePageCompositionSegment() { - - /* Parse page composition segment. ETSI EN 300 743 7.2.2 - - SYNTAX SIZE SEMANTICS - --------------------------------------- ---- ------------------------------------------ - page_composition_segment() { - sync_byte 8 - segment_type 8 - page_id 16 - segment_length 16 - page_time_out 8 The period after the page instace should be erased - page_version_number 4 Incremented when any of the contents of this segment change - page_state 2 The status of the subtitling page instance - reserved 2 - while (processed_length < segment_length) { Page region list - region_id 8 Uniquely identifies a region within a page - reserved 8 - region_horizontal_address 16 Horizontal address of the top left pixel of this region - region_vertical_address 16 Vertical address of the top line of this region - } - } - */ - - PageComposition page = new PageComposition(); - - page.pageId = tsStream.readBits(16); - int remainingSegmentLength = tsStream.readBits(16); - page.pageTimeOut = tsStream.readBits(8); - page.pageVersionNumber = tsStream.readBits(4); - page.pageState = tsStream.readBits(2); - tsStream.skipBits(2); - - if (page.pageState == DVBSUB_PCS_STATE_NORMAL && - subtitleService.pageComposition != null && - subtitleService.pageComposition.pageId == page.pageId && - (subtitleService.pageComposition.pageVersionNumber + 1) % 16 == page.pageVersionNumber) { - //page.pageRegions = subtitleService.pageComposition.pageRegions; - - if (BuildConfig.DEBUG) { - Log.d(TAG, " Updated Page Composition. pageId: " + page.pageId + - " version: " + page.pageVersionNumber + - " timeout: " + page.pageTimeOut - ); - } - - } else if (subtitleService.subtitlePageId == page.pageId) { - if (BuildConfig.DEBUG) { - if (page.pageState == DVBSUB_PCS_STATE_NORMAL) { - Log.d(TAG, " FAILED Page Composition update. pageId: " + page.pageId + - " Version(Old/New): " + (subtitleService.pageComposition != null ? subtitleService.pageComposition.pageVersionNumber : "NaN") + "/" + page.pageVersionNumber); - } - } - - subtitleService.pageComposition = null; - subtitleService.regions = new SparseArray<>(); - subtitleService.cluts = new SparseArray<>(); - subtitleService.objects = new SparseArray<>(); - - if (BuildConfig.DEBUG) { - if (page.pageState != DVBSUB_PCS_STATE_NORMAL) { - Log.d(TAG, " New Page Composition. pageId: " + page.pageId + - " version: " + page.pageVersionNumber + - " timeout: " + page.pageTimeOut - ); - } - } - } - - remainingSegmentLength -= 2; - while (remainingSegmentLength > 0) { - PageRegion region = new PageRegion(); - - region.regionId = tsStream.readBits(8); - tsStream.skipBits(8); - region.regionHorizontalAddress = tsStream.readBits(16); - region.regionVerticalAddress = tsStream.readBits(16); - - if (BuildConfig.DEBUG) { - Log.d(TAG, " " + - (page.pageRegions.get(region.regionId) == null ? "New" : "Upd.") + - " Page Region. regionId: " + region.regionId + - " (x/y): (" + region.regionHorizontalAddress + "/" + region.regionVerticalAddress + ")"); - } - - page.pageRegions.put(region.regionId, region); - - remainingSegmentLength -= 6; - } - - return page; - } - - private RegionComposition parseRegionCompositionSegment() { - - /* Parse region composition segment. ETSI EN 300 743 7.2.3 - - SYNTAX SIZE SEMANTICS - --------------------------------------- ---- ------------------------------------------ - region_composition_segment() { - sync_byte 8 - segment_type 8 - page_id 16 - segment_length 16 - region_id 8 Uniquely identifies the region - region_version_number 4 Indicates the version of this region - region_fill_flag 1 If set the region is to be filled region_n-bit_pixel_code clut index - reserved 3 - region_width 16 Specifies the horizontal length of this region - region_height 16 Specifies the vertical length of the region - region_level_of_compatibility 3 Code that indicates the minimum bithdepth of CLUT - region_depth 3 Identifies the intended pixel depth for this region - reserved 2 - CLUT_id 8 Identifies the family of CLUTs that applies to this region - region_8-bit_pixel_code 8 Specifies the entry of the applied 8-bit CLUT as background colour - region_4-bit_pixel-code 4 Specifies the entry of the applied 4-bit CLUT as background colour - region_2-bit_pixel-code 2 Specifies the entry of the applied 2-bit CLUT as background colour - reserved 2 - while (processed_length < segment_length) { list of region objects - object_id 16 Identifies an object that is shown in the region - object_type 2 Identifies the type of object - object_provider_flag 2 How this object is provided - object_horizontal_position 12 Specifies the horizontal position of the top left pixel of this object - reserved 4 - object_vertical_position 12 Specifies the vertical position of the top left pixel of this object - if (object_type ==0x01 or object_type == 0x02){ UNSUPPORTED - foreground_pixel_code 8 - background_pixel_code 8 - } - } - } - */ - - RegionComposition region = new RegionComposition(); - - region.pageId = tsStream.readBits(16); - int remainingSegmentLength = tsStream.readBits(16); - region.regionId = tsStream.readBits(8); - region.regionVersionNumber = tsStream.readBits(4); - if (tsStream.readBits(1) == 1) { - region.flags |= REGION_FILL_FLAG; - } - tsStream.skipBits(3); - region.regionWidth = tsStream.readBits(16); - region.regionHeight = tsStream.readBits(16); - region.regionLevelOfCompatibility = tsStream.readBits(3); - region.regionDepth = tsStream.readBits(3); - tsStream.skipBits(2); - region.clutId = tsStream.readBits(8); - tsStream.skipBits(16); - - if (BuildConfig.DEBUG) { - Log.d(TAG, " New Region Composition. regionId: " + region.regionId + - " (w/h): (" + region.regionWidth + "/" + region.regionHeight + ")"); - } - - int arrayIndex = 0; // index by an incremental counter to allow repeating objects in one region - - if (subtitleService.pageComposition != null && subtitleService.pageComposition.pageId == region.pageId && - subtitleService.pageComposition.pageState == DVBSUB_PCS_STATE_NORMAL) { - RegionComposition tempRegion = subtitleService.regions.get(region.regionId); - if (tempRegion != null) { - region.regionObjects = tempRegion.regionObjects; - arrayIndex = region.regionObjects.size(); - } - } - - remainingSegmentLength -= 10; - RegionObject object; - while (remainingSegmentLength > 0) { - object = new RegionObject(); - - object.objectId = tsStream.readBits(16); - object.objectType = tsStream.readBits(2); - object.objectProvider = tsStream.readBits(2); - object.objectHorizontalPosition = tsStream.readBits(12); - tsStream.skipBits(4); - object.objectVerticalPosition = tsStream.readBits(12); - remainingSegmentLength -= 6; - - if (object.objectType == 0x01 || object.objectType == 0x02) { // Only seems to affect to char subtitles - object.foregroundPixelCode = tsStream.readBits(8); - object.backgroundPixelCode = tsStream.readBits(8); - remainingSegmentLength -= 2; - } - - if (BuildConfig.DEBUG) { - Log.d(TAG, " New Region Object[" + arrayIndex + "]." + - " objectId: " + object.objectId + - " (x/y): (" + object.objectHorizontalPosition + "/" + object.objectVerticalPosition + ")"); - } - - region.regionObjects.put(arrayIndex++, object); - } - - - return region; - } - - private ClutDefinition parseClutDefinitionSegment() { - - /* Parse CLUT definition segment. ETSI EN 300 743 7.2.4 - - SYNTAX SIZE SEMANTICS - --------------------------------------- ---- ------------------------------------------ - CLUT_definition_segment() { - sync_byte 8 - segment_type 8 - page_id 16 - segment_length 16 - CLUT-id 8 Uniquely identifies within a page the CLUT family - CLUT_version_number 4 Indicates the version of this segment data - reserved 4 - while (processed_length < segment_length) { Clut entries list - CLUT_entry_id 8 Specifies the entry number of the CLUT - 2-bit/entry_CLUT_flag 1 Indicates this entry is to be loaded into the 2-bit/entry CLUT - 4-bit/entry_CLUT_flag 1 Indicates this entry is to be loaded into the 4-bit/entry CLUT - 8-bit/entry_CLUT_flag 1 Indicates this entry is to be loaded into the 8-bit/entry CLUT - reserved 4 - full_range_flag 1 Indicates that the Y_value, Cr_value, Cb_value and T_value - fields have the full 8-bit resolution - if full_range_flag =='1' { - Y-value 8 The Y value for this CLUT entry. - Cr-value 8 The Cr value for this CLUT entry. - Cb-value 8 The Cb value for this CLUT entry. - T-value 8 The Transparency value for this CLUT entry. 0 = no transparency - } else { - Y-value 6 The Y value for this CLUT entry. - Cr-value 4 The Cr value for this CLUT entry. - Cb-value 4 The Cb value for this CLUT entry. - T-value 2 The Transparency value for this CLUT entry. 0 = no transparency - } - } - } - */ - - ClutDefinition clut = new ClutDefinition(); - clut.pageId = tsStream.readBits(16); - int remainingSegmentLength = tsStream.readBits(16); - clut.clutId = tsStream.readBits(8); - clut.clutVersionNumber = tsStream.readBits(4); - tsStream.skipBits(4); - - remainingSegmentLength -= 2; - ClutEntry entry; - int Y, Cb, Cr, T; - int entryId, entryFlags; - while (remainingSegmentLength > 0) { - entryId = tsStream.readBits(8); - entryFlags = tsStream.readBits(8); - - if ((entryFlags & 0x80) != 0) { - entry = clut.clutEntries2bit[entryId]; - } else if ((entryFlags & 0x40) != 0) { - entry = clut.clutEntries4bit[entryId]; - } else { - entry = clut.clutEntries8bit[entryId]; - } - - entry.flags = (byte) (entryFlags & 0xE1); - if ((entry.flags & 0x01) != 0) { - Y = tsStream.readBits(8); - Cr = tsStream.readBits(8); - Cb = tsStream.readBits(8); - T = tsStream.readBits(8); - remainingSegmentLength -= 6; - } else { - Y = tsStream.readBits(6) << 2; - Cr = tsStream.readBits(4) << 4; - Cb = tsStream.readBits(4) << 4; - T = tsStream.readBits(2) << 6; - remainingSegmentLength -= 4; - } - - if (Y == 0x00) { - Cr = 0x00; - Cb = 0x00; - T = 0xFF; - } - - entry.clutYCbCrT(Y, Cb, Cr, T); - } - return clut; - } - - private ObjectData parseObjectDataSegment() { - - /* Parse object data segment. ETSI EN 300 743 7.2.5 - - SYNTAX SIZE SEMANTICS - --------------------------------------- ---- ------------------------------------------ - object_data_segment() { - sync_byte 8 - segment_type 8 - page_id 16 - segment_length 16 - object_id 16 Uniquely identifies within the page the object - object_version_number 4 Indicates the version of this segment data - object_coding_method 2 Specifies the method used to code the object - non_modifying_colour_flag 1 Indicates that the CLUT entry value '1' is a non modifying colour - reserved 1 - if (object_coding_method == '00'){ - top_field_data_block_length 16 Specifies the number of bytes contained in his pixel-data_sub-blocks - bottom_field_data_block_length 16 Specifies the number of bytes contained in his pixel-data_sub-blocks - while(processed_length 0) { - switch (data.readBits(8)) { - case DVBSUB_DT_2BP_CODE_STRING: - if (regionDepth == DVBSUB_RCS_BITDEPTH_8) { - clutMapTable = clutMapTable28 == null ? defaultMap28 : clutMapTable28; - } else if (regionDepth == DVBSUB_RCS_BITDEPTH_4) { - clutMapTable = clutMapTable24 == null ? defaultMap24 : clutMapTable24; - } else { - clutMapTable = null; - } - column += dvbSub2BitPixelCodeString(data, lineHeight, clutEntries, clutMapTable, - column, line, (object.flags & OBJECT_NON_MODIFYING_COLOUR_FLAG) == 0); - if ((i = data.getPosition() % 8) != 0) { - data.skipBits(7 - i + 1); - } - break; - case DVBSUB_DT_4BP_CODE_STRING: - if (regionDepth == DVBSUB_RCS_BITDEPTH_8) - clutMapTable = clutMapTable48 == null ? defaultMap48 : clutMapTable48; - else - clutMapTable = null; - column += dvbSub4BitPixelCodeString(data, lineHeight, clutEntries, clutMapTable, - column, line, (object.flags & OBJECT_NON_MODIFYING_COLOUR_FLAG) == 0); - if ((i = data.getPosition() % 8) != 0) { - data.skipBits(7 - i + 1); - } - break; - case DVBSUB_DT_8BP_CODE_STRING: - column += dvbSub8BitPixelCodeString(data, lineHeight, clutEntries, null, - column, line, (object.flags & OBJECT_NON_MODIFYING_COLOUR_FLAG) == 0); - break; - case DVBSUB_DT_24_TABLE_DATA: - clutMapTable24 = new byte[4]; - for (i = 0; i < 4; i++) { - clutMapTable24[i] = (byte) data.readBits(4); - } - break; - case DVBSUB_DT_28_TABLE_DATA: - clutMapTable28 = new byte[4]; - for (i = 0; i < 4; i++) { - clutMapTable28[i] = (byte) data.readBits(8); - } - break; - case DVBSUB_DT_48_TABLE_DATA: - clutMapTable48 = new byte[16]; - for (i = 0; i < 4; i++) { - clutMapTable48[i] = (byte) data.readBits(8); - } - break; - case DVBSUB_DT_END_LINE: - column = horizontalAddress; - line += 2; - break; - default: - break; - } - } - field += lineHeight; - } - - return null; - } - - private int dvbSub2BitPixelCodeString(ParsableBitArray data, int lineHeigth, - ClutEntry[] clutEntries, byte[] clutMapTable, - int column, int line, boolean paint) { - - /* Parse 2-bit/pixel code string. ETSI EN 300 743 7.2.5.2 - - SYNTAX SIZE - --------------------------------------- ---- - 2-bit/pixel_code_string() { - if (nextbits() != '00') { - 2-bit_pixel-code 2 - } else { - 2-bit_zero 2 - switch_1 1 bslbf - if (switch_1 == '1') { - run_length_3-10 3 - 2-bit_pixel-code 2 - } else { - switch_2 1 - if (switch_2 == '0') { - switch_3 2 - if (switch_3 == '10') { - run_length_12-27 4 - 2-bit_pixel-code 2 - } - if (switch_3 == '11') { - run_length_29-284 8 - 2-bit_pixel-code 2 - } - } - } - } - } - */ - - int savedColumn = column, peek, runLength, clutIdx = 0x00, colour; - boolean endOfPixelCodeString = false; - - while (!endOfPixelCodeString) { - runLength = 0; - peek = data.readBits(2); - if (peek != 0x00) { - runLength = 1; - clutIdx = peek; - } else { - peek = data.readBits(1); - if (peek == 0x01) { - runLength = 3 + data.readBits(3); - clutIdx = data.readBits(2); - } else { - peek = data.readBits(1); - if (peek == 0x00) { - peek = data.readBits(2); - switch (peek) { - case 0x00: - endOfPixelCodeString = true; - break; - case 0x01: - runLength = 2; - clutIdx = 0x00; - break; - case 0x02: - runLength = 12 + data.readBits(4); - clutIdx = data.readBits(2); - break; - case 0x03: - runLength = 29 + data.readBits(8); - clutIdx = data.readBits(2); - break; - } - } - } - } - - if (runLength != 0 && paint) { - colour = clutMapTable != null ? clutEntries[clutMapTable[clutIdx]].ARGB - : clutEntries[clutIdx].ARGB; - defaultPaint.setColor(colour); - canvas.drawRect( - column, line, column + runLength, line + lineHeigth, defaultPaint); - } - - column += runLength; - } - - return column - savedColumn; - } - - private int dvbSub4BitPixelCodeString(ParsableBitArray data, int lineHeigth, - ClutEntry[] clutEntries, byte[] clutMapTable, - int column, int line, boolean paint) { - - /* Parse 4-bit/pixel code string. ETSI EN 300 743 7.2.5.2 - - SYNTAX SIZE - --------------------------------------- ---- - 4-bit/pixel_code_string() { - if (nextbits() != '0000') { - 4-bit_pixel-code 4 - } else { - 4-bit_zero 4 - switch_1 1 - if (switch_1 == '0') { - if (nextbits() != '000') - run_length_3-9 3 - else - end_of_string_signal 3 - } else { - switch_2 1 - if (switch_2 == '0') { - run_length_4-7 2 - 4-bit_pixel-code 4 - } else { - switch_3 2 - if (switch_3 == '10') { - run_length_9-24 4 - 4-bit_pixel-code 4 - } - if (switch_3 == '11') { - run_length_25-280 8 - 4-bit_pixel-code 4 - } - } - } - } - } - */ - - int savedColumn = column, peek, runLength, clutIdx = 0x00, colour; - boolean endOfPixelCodeString = false; - - while (!endOfPixelCodeString) { - runLength = 0; - peek = data.readBits(4); - if (peek != 0x00) { - runLength = 1; - clutIdx = peek; - } else { - peek = data.readBits(1); - if (peek == 0x00) { - peek = data.readBits(3); - if (peek != 0x00) { - runLength = 2 + peek; - clutIdx = 0x00; - } else { - endOfPixelCodeString = true; - } - } else { - peek = data.readBits(1); - if (peek == 0x00) { - runLength = 4 + data.readBits(2); - clutIdx = data.readBits(4); - } else { - peek = data.readBits(2); - switch (peek) { - case 0x00: - runLength = 1; - clutIdx = 0x00; - break; - case 0x01: - runLength = 2; - clutIdx = 0x00; - break; - case 0x02: - runLength = 9 + data.readBits(4); - clutIdx = data.readBits(4); - break; - case 0x03: - runLength = 25 + data.readBits(8); - clutIdx = data.readBits(4); - break; - } - } - } - } - - if (runLength != 0 && paint) { - colour = clutMapTable != null ? clutEntries[clutMapTable[clutIdx]].ARGB - : clutEntries[clutIdx].ARGB; - defaultPaint.setColor(colour); - canvas.drawRect( - column, line, column + runLength, line + lineHeigth, defaultPaint); - } - - column += runLength; - } - - return column - savedColumn; - } - - private int dvbSub8BitPixelCodeString(ParsableBitArray data, int lineHeigth, - ClutEntry[] clutEntries, byte[] clutMapTable, - int column, int line, boolean paint) { - - /* Parse 8-bit/pixel code string. ETSI EN 300 743 7.2.5.2 - - SYNTAX SIZE - --------------------------------------- ---- - - 8-bit/pixel_code_string() { - if (nextbits() != '0000 0000') { - 8-bit_pixel-code 8 - } else { - 8-bit_zero 8 - switch_1 1 - if switch_1 == '0' { - if nextbits() != '000 0000' - run_length_1-127 7 - else - end_of_string_signal 7 - } else { - run_length_3-127 7 - 8-bit_pixel-code 8 - } - } - } - */ - - int savedColumn = column, peek, runLength, clutIdx = 0x00, colour; - boolean endOfPixelCodeString = false; - - while (!endOfPixelCodeString) { - runLength = 0; - peek = data.readBits(8); - if (peek != 0x00) { - runLength = 1; - clutIdx = peek; - } else { - peek = data.readBits(1); - if (peek == 0x00) { - peek = data.readBits(7); - if (peek != 0x00) { - runLength = peek; - clutIdx = 0x00; - } else { - endOfPixelCodeString = true; - } - } else { - runLength = data.readBits(7); - clutIdx = data.readBits(8); - } - } - - if (runLength != 0 && paint) { - colour = clutMapTable != null ? clutEntries[clutMapTable[clutIdx]].ARGB - : clutEntries[clutIdx].ARGB; - defaultPaint.setColor(colour); - canvas.drawRect( - column, line, column + runLength, line + lineHeigth, defaultPaint); - } - - column += runLength; - } - - return column - savedColumn; - } - - /** - * Takes a subtitling packet, parses the included segments and returns the list of {@link Cue}s - * defined in them - * - * @param input - * @param inputSize - * @return list of {@link Cue}s contained in the packet or null if there is an error or subtitle - * is incomplete - */ - List dvbSubsDecode(byte[] input, int inputSize) { - - /* process PES PACKET. ETSI EN 300 743 7.1 - - SYNTAX SIZE SEMANTICS - --------------------------------------- ---- ------------------------------------------ - PES_data_field() { - data_identifier 8 For DVB subtitle streams it shall be 0x20 - subtitle_stream_id 8 For DVB subtitling stream it shall be 0x00 - while nextbits() == '0000 1111' { - Subtitling_segment() - } - end_of_PES_data_field_marker 8 An 8-bit field with fixed contents '1111 1111' - - */ - - if (input != null) { - tsStream = new ParsableBitArray(input, inputSize); - } else { - return null; - } - if (!isSet(FLAG_PES_STRIPPED_DVBSUB)) { - if (tsStream.readBits(8) != 0x20) { // data_identifier - return null; - } - if (tsStream.readBits(8) != 0x00) { // subtitle_stream_id - return null; - } - } - - if (BuildConfig.DEBUG) Log.d(TAG,"New PES subtitle packet."); - - int sync = tsStream.readBits(8); - // test for segment Sync Byte and account for possible additional wordalign byte in Object data segment - while (sync == 0x0f || (sync == 0x00 && (sync = tsStream.readBits(8)) == 0x0f)) { - parseSubtitlingSegment(); - if (isSet(FLAG_PES_STRIPPED_DVBSUB) && tsStream.bitsLeft() == 0) { - break; - } - sync = tsStream.readBits(8); - - } - - if (sync == 0xff || (isSet(FLAG_PES_STRIPPED_DVBSUB) && tsStream.bitsLeft() == 0)) { // end_of_PES_data_field_marker - // paint the current Subtitle definition - if (subtitleService.pageComposition != null) { - List cueList = new ArrayList<>(); - - if (BuildConfig.DEBUG) { - Log.d(TAG, "Rendering subtitle. w: " + subtitleService.displayDefinition.displayWidth + - " h: " + subtitleService.displayDefinition.displayHeight); - - if ((subtitleService.displayDefinition.flags & DISPLAY_WINDOW_FLAG) != 0) { - Log.d(TAG, " Window dimensions (x/y/w/h): (" + - subtitleService.displayDefinition.displayWindowHorizontalPositionMinimum + "/" + - subtitleService.displayDefinition.displayWindowVerticalPositionMinimum + "/" + - (subtitleService.displayDefinition.displayWindowHorizontalPositionMaximum - - subtitleService.displayDefinition.displayWindowHorizontalPositionMinimum) + "/" + - (subtitleService.displayDefinition.displayWindowVerticalPositionMaximum - - subtitleService.displayDefinition.displayWindowVerticalPositionMinimum) + ")" ); - } - } - - int a,b; - PageRegion pageRegion; - RegionComposition regionComposition; - int baseHorizontalAddress, baseVerticalAddress; - ObjectData object; - ClutDefinition clut; - int regionKey; - // process page regions - for (a = 0; a < subtitleService.pageComposition.pageRegions.size(); a++) { - regionKey = subtitleService.pageComposition.pageRegions.keyAt(a); - pageRegion = subtitleService.pageComposition.pageRegions.get(regionKey); - regionComposition = subtitleService.regions.get(regionKey); - - baseHorizontalAddress = pageRegion.regionHorizontalAddress; - baseVerticalAddress = pageRegion.regionVerticalAddress; - - if ((subtitleService.displayDefinition.flags & DISPLAY_WINDOW_FLAG) != 0) { - baseHorizontalAddress += - subtitleService.displayDefinition.displayWindowHorizontalPositionMinimum; - baseVerticalAddress += - subtitleService.displayDefinition.displayWindowVerticalPositionMinimum; - } - - // clip object drawing to the current region and display definition window - canvas.clipRect( - baseHorizontalAddress, baseVerticalAddress, - Math.min(baseHorizontalAddress + regionComposition.regionWidth, - subtitleService.displayDefinition.displayWindowHorizontalPositionMaximum), - Math.min(baseVerticalAddress + regionComposition.regionHeight, - subtitleService.displayDefinition.displayWindowVerticalPositionMaximum), - Region.Op.REPLACE); - - if ((clut = subtitleService.cluts.get(regionComposition.clutId)) == null) { - if ((clut = subtitleService.ancillaryCluts.get(regionComposition.clutId)) == null) { - clut = defaultClut; - } - } - - if (BuildConfig.DEBUG) { - Log.d(TAG, " Region: " + regionKey + " (x/y/w/h): (" + - baseHorizontalAddress + "/" + baseVerticalAddress + "/" + - (baseHorizontalAddress + regionComposition.regionWidth - 1) + "/" + - (baseVerticalAddress + regionComposition.regionHeight - 1) + ")" - ); - - canvas.drawRect( - baseHorizontalAddress, baseVerticalAddress, - baseHorizontalAddress + regionComposition.regionWidth - 1, - baseVerticalAddress + regionComposition.regionHeight - 1, - debugRegionPaint); - } - - RegionObject regionObject; - int objectKey; - // process regions compositions - for ( b = 0; b < regionComposition.regionObjects.size(); b++) { - objectKey = regionComposition.regionObjects.keyAt(b); - regionObject = regionComposition.regionObjects.get(objectKey); - - if (BuildConfig.DEBUG) { - Log.d(TAG, " Object[" + objectKey + "]. objectId: " + regionObject.objectId + " (x/y): (" + - (baseHorizontalAddress + regionObject.objectHorizontalPosition) + "/" + - (baseVerticalAddress + regionObject.objectVerticalPosition) + ")" - ); - - canvas.drawRect( - baseHorizontalAddress + regionObject.objectHorizontalPosition, - baseVerticalAddress + regionObject.objectVerticalPosition, - baseHorizontalAddress + regionObject.objectHorizontalPosition + regionComposition.regionWidth - 1, - baseVerticalAddress + regionObject.objectVerticalPosition + regionComposition.regionHeight - 1, - debugObjectPaint); - } - - if ((object = subtitleService.objects.get(regionObject.objectId)) == null) { - if ((object = subtitleService.ancillaryObjects.get(regionObject.objectId)) == null) { - continue; - } - } - - parsePixelDataSubBlocks(object, clut, regionComposition.regionDepth, - baseHorizontalAddress + regionObject.objectHorizontalPosition, - baseVerticalAddress + regionObject.objectVerticalPosition); - - } - - // fill the region if needed - if ((regionComposition.flags & REGION_FILL_FLAG )!= 0) { - int colour; - if (regionComposition.regionDepth == DVBSUB_RCS_BITDEPTH_8) { - colour = clut.clutEntries8bit[regionComposition.region8bitPixelCode].ARGB; - } else if (regionComposition.regionDepth == DVBSUB_RCS_BITDEPTH_4) { - colour = clut.clutEntries4bit[regionComposition.region4bitPixelCode].ARGB; - } else { - colour = clut.clutEntries2bit[regionComposition.region2bitPixelCode].ARGB; - } - - fillRegionPaint.setColor(colour); - - canvas.drawRect( - baseHorizontalAddress, baseVerticalAddress, - baseHorizontalAddress + regionComposition.regionWidth , - baseVerticalAddress + regionComposition.regionHeight, - fillRegionPaint); - - } - - Bitmap cueBitmap = Bitmap.createBitmap(bitmap, - baseHorizontalAddress, baseVerticalAddress, - regionComposition.regionWidth, regionComposition.regionHeight); - canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR); - regionComposition.cue = new Cue(cueBitmap, - (float) baseHorizontalAddress / subtitleService.displayDefinition.displayWidth, Cue.ANCHOR_TYPE_START, - (float) baseVerticalAddress / subtitleService.displayDefinition.displayHeight, Cue.ANCHOR_TYPE_START, - (float) regionComposition.regionWidth / subtitleService.displayDefinition.displayWidth, - (float) subtitleService.displayDefinition.displayWidth / subtitleService.displayDefinition.displayHeight); - cueList.add(regionComposition.cue); - } - - return cueList; - } - } else { - Log.d(TAG,"Unexpected..."); - } - return null; - } - - private boolean isSet(@Flags int flag) { - return (flags & flag) != 0; - } - -} \ No newline at end of file diff --git a/library/ui/src/main/java/com/google/android/exoplayer2/ui/SubtitlePainter.java b/library/ui/src/main/java/com/google/android/exoplayer2/ui/SubtitlePainter.java index 4bf7ce0b74..9255f35302 100644 --- a/library/ui/src/main/java/com/google/android/exoplayer2/ui/SubtitlePainter.java +++ b/library/ui/src/main/java/com/google/android/exoplayer2/ui/SubtitlePainter.java @@ -77,7 +77,7 @@ import com.google.android.exoplayer2.util.Util; @Cue.AnchorType private int cuePositionAnchor; private float cueSize; - private float cueSar; + private float cueBitmapHeight; private boolean applyEmbeddedStyles; private int foregroundColor; private int backgroundColor; @@ -174,7 +174,7 @@ import com.google.android.exoplayer2.util.Util; && this.cuePosition == cue.position && Util.areEqual(this.cuePositionAnchor, cue.positionAnchor) && this.cueSize == cue.size - && this.cueSar == cue.sar + && this.cueBitmapHeight == cue.bitmapHeight && this.applyEmbeddedStyles == applyEmbeddedStyles && this.foregroundColor == style.foregroundColor && this.backgroundColor == style.backgroundColor @@ -202,7 +202,7 @@ import com.google.android.exoplayer2.util.Util; this.cuePosition = cue.position; this.cuePositionAnchor = cue.positionAnchor; this.cueSize = cue.size; - this.cueSar = cue.sar; + this.cueBitmapHeight = cue.bitmapHeight; this.applyEmbeddedStyles = applyEmbeddedStyles; this.foregroundColor = style.foregroundColor; this.backgroundColor = style.backgroundColor; @@ -315,7 +315,8 @@ import com.google.android.exoplayer2.util.Util; float anchorX = parentLeft + (parentWidth * cuePosition); float anchorY = parentTop + (parentHeight * cueLine); int width = Math.round(parentWidth * cueSize); - int height = Math.round(width * ((float) cueBitmap.getHeight() / cueBitmap.getWidth()) / (((float) parentWidth / parentHeight) / cueSar)); + int height = cueBitmapHeight != -1 ? Math.round(parentHeight * cueBitmapHeight) + : Math.round(width * ((float) cueBitmap.getHeight() / cueBitmap.getWidth())); int x = Math.round(cueLineAnchor == Cue.ANCHOR_TYPE_END ? (anchorX - width) : cueLineAnchor == Cue.ANCHOR_TYPE_MIDDLE ? (anchorX - (width / 2)) : anchorX); int y = Math.round(cuePositionAnchor == Cue.ANCHOR_TYPE_END ? (anchorY - height) From 42f3dcf0da5ad2b3e352346647d630ac4f8684c6 Mon Sep 17 00:00:00 2001 From: Alex Telitsine Date: Thu, 30 Mar 2017 19:50:09 -0700 Subject: [PATCH 036/119] Disables Adaptive workaround for Odroid-XU4 up to SDK 19 in Samsung's Exynos AVC and AVC secure decoders --- .../exoplayer2/mediacodec/MediaCodecRenderer.java | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) 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 2bc3ff285f..529704c62e 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 @@ -337,7 +337,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer { } String codecName = decoderInfo.name; - codecIsAdaptive = decoderInfo.adaptive && codecSupportsAdaptive(codecName, format); + codecIsAdaptive = decoderInfo.adaptive && (codecNeedsDisableAdaptationWorkaround(codecName)==false); codecNeedsDiscardToSpsWorkaround = codecNeedsDiscardToSpsWorkaround(codecName, format); codecNeedsFlushWorkaround = codecNeedsFlushWorkaround(codecName); codecNeedsAdaptationWorkaround = codecNeedsAdaptationWorkaround(codecName); @@ -1171,18 +1171,16 @@ public abstract class MediaCodecRenderer extends BaseRenderer { && "OMX.MTK.AUDIO.DECODER.MP3".equals(name); } /** - * Returns whether the decoder is known to be non Adaptive. + * Returns whether the decoder is needs Apaptive workaround disabled *

    * If false is returned then we explicitly override codecIsAdaptive, * setting it to false. * - * @param name The decoder name. - * @param format The input format. - * @return False if the device is known to be non adaptive . + * @return TRUE if the device needs Adaptive workaround disabled */ - private static boolean codecSupportsAdaptive(String name, Format format) { - return !( - (Util.SDK_INT == 19 && Util.MODEL.equals("ODROID-XU3") + private static boolean codecNeedsDisableAdaptationWorkaround(String name) { + return ( + (Util.SDK_INT <= 19 && Util.MODEL.equals("ODROID-XU3") && ("OMX.Exynos.AVC.Decoder".equals(name) || "OMX.Exynos.AVC.Decoder.secure".equals(name)))); } } From 96f56716ce11115fd01fce14512ec4e67cdf957a Mon Sep 17 00:00:00 2001 From: Alex Telitsine Date: Thu, 30 Mar 2017 19:52:15 -0700 Subject: [PATCH 037/119] Disables Adaptive workaround for Odroid-XU4 up to SDK 19 in Samsung's Exynos AVC and AVC secure decoders --- .../android/exoplayer2/mediacodec/MediaCodecRenderer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 529704c62e..6caf2237a3 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 @@ -1175,7 +1175,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer { *

    * If false is returned then we explicitly override codecIsAdaptive, * setting it to false. - * + * @param name The decoder name. * @return TRUE if the device needs Adaptive workaround disabled */ private static boolean codecNeedsDisableAdaptationWorkaround(String name) { From d84733c9cf73b9a2ab39cbef660fe6801c973045 Mon Sep 17 00:00:00 2001 From: Alex Telitsine Date: Thu, 30 Mar 2017 19:53:19 -0700 Subject: [PATCH 038/119] Disables Adaptive workaround for Odroid-XU4 up to SDK 19 in Samsung's Exynos AVC and AVC secure decoders --- .../android/exoplayer2/mediacodec/MediaCodecRenderer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 6caf2237a3..c797920f32 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 @@ -1173,7 +1173,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer { /** * Returns whether the decoder is needs Apaptive workaround disabled *

    - * If false is returned then we explicitly override codecIsAdaptive, + * If TRUE is returned then we explicitly override codecIsAdaptive, * setting it to false. * @param name The decoder name. * @return TRUE if the device needs Adaptive workaround disabled From 9d20a8d41c0cc6cd01b20bd72f3bbfcf11e49cae Mon Sep 17 00:00:00 2001 From: andrewlewis Date: Fri, 24 Mar 2017 05:23:02 -0700 Subject: [PATCH 039/119] Add a custom time bar view. Also add an isAd flag to Timeline.Period so that periods can be declared as containing ads. The times of these periods are indicated using ad markers in the new TimeBar. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=151116208 --- .../android/exoplayer2/ExoPlayerTest.java | 2 +- .../google/android/exoplayer2/Timeline.java | 8 +- .../exoplayer2/source/LoopingMediaSource.java | 1 + .../source/SinglePeriodTimeline.java | 2 +- .../android/exoplayer2/util/ClosedSource.java | 31 - .../google/android/exoplayer2/util/Util.java | 34 ++ library/core/src/main/proguard-rules.txt | 7 - .../source/dash/DashMediaSource.java | 2 +- .../android/exoplayer2/ui/DefaultTimeBar.java | 574 ++++++++++++++++++ .../exoplayer2/ui/PlaybackControlView.java | 281 ++++++--- .../exoplayer2/ui/SimpleExoPlayerView.java | 10 + .../google/android/exoplayer2/ui/TimeBar.java | 120 ++++ .../res/layout/exo_playback_control_view.xml | 7 +- library/ui/src/main/res/values/attrs.xml | 12 + 14 files changed, 951 insertions(+), 140 deletions(-) delete mode 100644 library/core/src/main/java/com/google/android/exoplayer2/util/ClosedSource.java delete mode 100644 library/core/src/main/proguard-rules.txt create mode 100644 library/ui/src/main/java/com/google/android/exoplayer2/ui/DefaultTimeBar.java create mode 100644 library/ui/src/main/java/com/google/android/exoplayer2/ui/TimeBar.java diff --git a/library/core/src/androidTest/java/com/google/android/exoplayer2/ExoPlayerTest.java b/library/core/src/androidTest/java/com/google/android/exoplayer2/ExoPlayerTest.java index daa845298b..9fc55d5c77 100644 --- a/library/core/src/androidTest/java/com/google/android/exoplayer2/ExoPlayerTest.java +++ b/library/core/src/androidTest/java/com/google/android/exoplayer2/ExoPlayerTest.java @@ -334,7 +334,7 @@ public final class ExoPlayerTest extends TestCase { public Period getPeriod(int periodIndex, Period period, boolean setIds) { TimelineWindowDefinition windowDefinition = windowDefinitions[periodIndex]; Object id = setIds ? periodIndex : null; - return period.set(id, id, periodIndex, windowDefinition.durationUs, 0); + return period.set(id, id, periodIndex, windowDefinition.durationUs, 0, false); } @Override diff --git a/library/core/src/main/java/com/google/android/exoplayer2/Timeline.java b/library/core/src/main/java/com/google/android/exoplayer2/Timeline.java index 333dd25cbe..eb3966ae4d 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/Timeline.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/Timeline.java @@ -383,18 +383,24 @@ public abstract class Timeline { */ public long durationUs; + /** + * Whether this period contains an ad. + */ + public boolean isAd; + private long positionInWindowUs; /** * Sets the data held by this period. */ public Period set(Object id, Object uid, int windowIndex, long durationUs, - long positionInWindowUs) { + long positionInWindowUs, boolean isAd) { this.id = id; this.uid = uid; this.windowIndex = windowIndex; this.durationUs = durationUs; this.positionInWindowUs = positionInWindowUs; + this.isAd = isAd; return this; } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/LoopingMediaSource.java b/library/core/src/main/java/com/google/android/exoplayer2/source/LoopingMediaSource.java index d893d60262..b26ae3a6ac 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/LoopingMediaSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/LoopingMediaSource.java @@ -158,6 +158,7 @@ public final class LoopingMediaSource implements MediaSource { int periodIndexOffset = loopCount * childPeriodCount; return childTimeline.getIndexOfPeriod(loopCountAndChildUid.second) + periodIndexOffset; } + } } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/SinglePeriodTimeline.java b/library/core/src/main/java/com/google/android/exoplayer2/source/SinglePeriodTimeline.java index ae367ef14c..447839392e 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/SinglePeriodTimeline.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/SinglePeriodTimeline.java @@ -99,7 +99,7 @@ public final class SinglePeriodTimeline extends Timeline { public Period getPeriod(int periodIndex, Period period, boolean setIds) { Assertions.checkIndex(periodIndex, 0, 1); Object id = setIds ? ID : null; - return period.set(id, id, 0, periodDurationUs, -windowPositionInPeriodUs); + return period.set(id, id, 0, periodDurationUs, -windowPositionInPeriodUs, false); } @Override diff --git a/library/core/src/main/java/com/google/android/exoplayer2/util/ClosedSource.java b/library/core/src/main/java/com/google/android/exoplayer2/util/ClosedSource.java deleted file mode 100644 index ea70920d20..0000000000 --- a/library/core/src/main/java/com/google/android/exoplayer2/util/ClosedSource.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.google.android.exoplayer2.util; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * An annotation for classes and interfaces that should not be open sourced. - */ -@Target(ElementType.TYPE) -@Retention(RetentionPolicy.SOURCE) -@ClosedSource(reason = "Not required") -public @interface ClosedSource { - String reason(); -} diff --git a/library/core/src/main/java/com/google/android/exoplayer2/util/Util.java b/library/core/src/main/java/com/google/android/exoplayer2/util/Util.java index e481066720..8fa89dea28 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/util/Util.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/util/Util.java @@ -45,6 +45,7 @@ import java.nio.charset.Charset; import java.util.Arrays; import java.util.Calendar; import java.util.Collections; +import java.util.Formatter; import java.util.GregorianCalendar; import java.util.List; import java.util.Locale; @@ -309,6 +310,18 @@ public final class Util { return Math.max(min, Math.min(value, max)); } + /** + * Constrains a value to the specified bounds. + * + * @param value The value to constrain. + * @param min The lower bound. + * @param max The upper bound. + * @return The constrained value {@code Math.max(min, Math.min(value, max))}. + */ + public static long constrainValue(long value, long min, long max) { + return Math.max(min, Math.min(value, max)); + } + /** * Constrains a value to the specified bounds. * @@ -835,6 +848,27 @@ public final class Util { } } + /** + * Returns the specified millisecond time formatted as a string. + * + * @param builder The builder that {@code formatter} will write to. + * @param formatter The formatter. + * @param timeMs The time to format as a string, in milliseconds. + * @return The time formatted as a string. + */ + public static String getStringForTime(StringBuilder builder, Formatter formatter, long timeMs) { + if (timeMs == C.TIME_UNSET) { + timeMs = 0; + } + long totalSeconds = (timeMs + 500) / 1000; + long seconds = totalSeconds % 60; + long minutes = (totalSeconds / 60) % 60; + long hours = totalSeconds / 3600; + builder.setLength(0); + return hours > 0 ? formatter.format("%d:%02d:%02d", hours, minutes, seconds).toString() + : formatter.format("%02d:%02d", minutes, seconds).toString(); + } + /** * Maps a {@link C} {@code TRACK_TYPE_*} constant to the corresponding {@link C} * {@code DEFAULT_*_BUFFER_SIZE} constant. diff --git a/library/core/src/main/proguard-rules.txt b/library/core/src/main/proguard-rules.txt deleted file mode 100644 index 75f2d095be..0000000000 --- a/library/core/src/main/proguard-rules.txt +++ /dev/null @@ -1,7 +0,0 @@ -# Accessed via reflection in SubtitleDecoderFactory.DEFAULT --keepclassmembers class com.google.android.exoplayer2.text.cea.Cea608Decoder { - public (java.lang.String, int); -} --keepclassmembers class com.google.android.exoplayer2.text.cea.Cea708Decoder { - public (int); -} diff --git a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaSource.java b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaSource.java index eec99521f1..10f88c6460 100644 --- a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaSource.java +++ b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaSource.java @@ -648,7 +648,7 @@ public final class DashMediaSource implements MediaSource { + Assertions.checkIndex(periodIndex, 0, manifest.getPeriodCount()) : null; return period.set(id, uid, 0, manifest.getPeriodDurationUs(periodIndex), C.msToUs(manifest.getPeriod(periodIndex).startMs - manifest.getPeriod(0).startMs) - - offsetInFirstPeriodUs); + - offsetInFirstPeriodUs, false); } @Override diff --git a/library/ui/src/main/java/com/google/android/exoplayer2/ui/DefaultTimeBar.java b/library/ui/src/main/java/com/google/android/exoplayer2/ui/DefaultTimeBar.java new file mode 100644 index 0000000000..85e30d207d --- /dev/null +++ b/library/ui/src/main/java/com/google/android/exoplayer2/ui/DefaultTimeBar.java @@ -0,0 +1,574 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.exoplayer2.ui; + +import android.annotation.TargetApi; +import android.content.Context; +import android.content.res.Resources; +import android.content.res.TypedArray; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.Point; +import android.graphics.Rect; +import android.os.Bundle; +import android.support.annotation.Nullable; +import android.util.AttributeSet; +import android.util.DisplayMetrics; +import android.view.KeyEvent; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewParent; +import android.view.accessibility.AccessibilityEvent; +import android.view.accessibility.AccessibilityNodeInfo; +import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction; +import com.google.android.exoplayer2.C; +import com.google.android.exoplayer2.util.Assertions; +import com.google.android.exoplayer2.util.Util; +import java.util.Formatter; +import java.util.Locale; + +/** + * A time bar that shows a current position, buffered position, duration and ad markers. + */ +public class DefaultTimeBar extends View implements TimeBar { + + /** + * The threshold in dps above the bar at which touch events trigger fine scrub mode. + */ + private static final int FINE_SCRUB_Y_THRESHOLD = -50; + /** + * The ratio by which times are reduced in fine scrub mode. + */ + private static final int FINE_SCRUB_RATIO = 3; + /** + * The time after which the scrubbing listener is notified that scrubbing has stopped after + * performing an incremental scrub using key input. + */ + private static final long STOP_SCRUBBING_TIMEOUT_MS = 1000; + private static final int DEFAULT_INCREMENT_COUNT = 20; + private static final int DEFAULT_BAR_HEIGHT = 4; + private static final int DEFAULT_TOUCH_TARGET_HEIGHT = 24; + private static final int DEFAULT_PLAYED_COLOR = 0x33FFFFFF; + private static final int DEFAULT_BUFFERED_COLOR = 0xCCFFFFFF; + private static final int DEFAULT_AD_MARKER_COLOR = 0xB2FFFF00; + private static final int DEFAULT_AD_MARKER_WIDTH = 4; + private static final int DEFAULT_SCRUBBER_ENABLED_SIZE = 12; + private static final int DEFAULT_SCRUBBER_DISABLED_SIZE = 0; + private static final int DEFAULT_SCRUBBER_DRAGGED_SIZE = 16; + private static final int OPAQUE_COLOR = 0xFF000000; + + private final Rect seekBounds; + private final Rect progressBar; + private final Rect bufferedBar; + private final Rect scrubberBar; + private final Paint progressPaint; + private final Paint bufferedPaint; + private final Paint scrubberPaint; + private final Paint adMarkerPaint; + private final int barHeight; + private final int touchTargetHeight; + private final int adMarkerWidth; + private final int scrubberEnabledSize; + private final int scrubberDisabledSize; + private final int scrubberDraggedSize; + private final int scrubberPadding; + private final int fineScrubYThreshold; + private final StringBuilder formatBuilder; + private final Formatter formatter; + private final Runnable stopScrubbingRunnable; + + private int scrubberSize; + private OnScrubListener listener; + private int keyCountIncrement; + private long keyTimeIncrement; + private int lastCoarseScrubXPosition; + private int[] locationOnScreen; + private Point touchPosition; + + private boolean scrubbing; + private long scrubPosition; + private long duration; + private long position; + private long bufferedPosition; + private int adBreakCount; + private long[] adBreakTimesMs; + + /** + * Creates a new time bar. + */ + public DefaultTimeBar(Context context, AttributeSet attrs) { + super(context, attrs); + seekBounds = new Rect(); + progressBar = new Rect(); + bufferedBar = new Rect(); + scrubberBar = new Rect(); + progressPaint = new Paint(); + bufferedPaint = new Paint(); + scrubberPaint = new Paint(); + adMarkerPaint = new Paint(); + + // Calculate the dimensions and paints for drawn elements. + Resources res = context.getResources(); + DisplayMetrics displayMetrics = res.getDisplayMetrics(); + fineScrubYThreshold = dpToPx(displayMetrics, FINE_SCRUB_Y_THRESHOLD); + int defaultBarHeight = dpToPx(displayMetrics, DEFAULT_BAR_HEIGHT); + int defaultTouchTargetHeight = dpToPx(displayMetrics, DEFAULT_TOUCH_TARGET_HEIGHT); + int defaultAdMarkerWidth = dpToPx(displayMetrics, DEFAULT_AD_MARKER_WIDTH); + int defaultScrubberEnabledSize = dpToPx(displayMetrics, DEFAULT_SCRUBBER_ENABLED_SIZE); + int defaultScrubberDisabledSize = dpToPx(displayMetrics, DEFAULT_SCRUBBER_DISABLED_SIZE); + int defaultScrubberDraggedSize = dpToPx(displayMetrics, DEFAULT_SCRUBBER_DRAGGED_SIZE); + if (attrs != null) { + TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.DefaultTimeBar, 0, + 0); + try { + barHeight = a.getDimensionPixelSize(R.styleable.DefaultTimeBar_bar_height, + defaultBarHeight); + touchTargetHeight = a.getDimensionPixelSize(R.styleable.DefaultTimeBar_touch_target_height, + defaultTouchTargetHeight); + adMarkerWidth = a.getDimensionPixelSize(R.styleable.DefaultTimeBar_ad_marker_width, + defaultAdMarkerWidth); + scrubberEnabledSize = a.getDimensionPixelSize( + R.styleable.DefaultTimeBar_scrubber_enabled_size, defaultScrubberEnabledSize); + scrubberDisabledSize = a.getDimensionPixelSize( + R.styleable.DefaultTimeBar_scrubber_disabled_size, defaultScrubberDisabledSize); + scrubberDraggedSize = a.getDimensionPixelSize( + R.styleable.DefaultTimeBar_scrubber_dragged_size, defaultScrubberDraggedSize); + int playedColor = a.getInt(R.styleable.DefaultTimeBar_played_color, DEFAULT_PLAYED_COLOR); + int bufferedColor = a.getInt(R.styleable.DefaultTimeBar_buffered_color, + DEFAULT_BUFFERED_COLOR); + int adMarkerColor = a.getInt(R.styleable.DefaultTimeBar_ad_marker_color, + DEFAULT_AD_MARKER_COLOR); + progressPaint.setColor(playedColor); + scrubberPaint.setColor(OPAQUE_COLOR | playedColor); + bufferedPaint.setColor(bufferedColor); + adMarkerPaint.setColor(adMarkerColor); + } finally { + a.recycle(); + } + } else { + barHeight = defaultBarHeight; + touchTargetHeight = defaultTouchTargetHeight; + adMarkerWidth = defaultAdMarkerWidth; + scrubberEnabledSize = defaultScrubberEnabledSize; + scrubberDisabledSize = defaultScrubberDisabledSize; + scrubberDraggedSize = defaultScrubberDraggedSize; + scrubberPaint.setColor(OPAQUE_COLOR | DEFAULT_PLAYED_COLOR); + progressPaint.setColor(DEFAULT_PLAYED_COLOR); + bufferedPaint.setColor(DEFAULT_BUFFERED_COLOR); + adMarkerPaint.setColor(DEFAULT_AD_MARKER_COLOR); + } + formatBuilder = new StringBuilder(); + formatter = new Formatter(formatBuilder, Locale.getDefault()); + stopScrubbingRunnable = new Runnable() { + @Override + public void run() { + stopScrubbing(false); + } + }; + scrubberSize = scrubberEnabledSize; + scrubberPadding = + (Math.max(scrubberDisabledSize, Math.max(scrubberEnabledSize, scrubberDraggedSize)) + 1) + / 2; + duration = C.TIME_UNSET; + keyTimeIncrement = C.TIME_UNSET; + keyCountIncrement = DEFAULT_INCREMENT_COUNT; + setFocusable(true); + if (Util.SDK_INT >= 16 + && getImportantForAccessibility() == View.IMPORTANT_FOR_ACCESSIBILITY_AUTO) { + setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES); + } + } + + @Override + public void setListener(OnScrubListener listener) { + this.listener = listener; + } + + @Override + public void setKeyTimeIncrement(long time) { + Assertions.checkArgument(time > 0); + keyCountIncrement = C.INDEX_UNSET; + keyTimeIncrement = time; + } + + @Override + public void setKeyCountIncrement(int count) { + Assertions.checkArgument(count > 0); + keyCountIncrement = count; + keyTimeIncrement = C.TIME_UNSET; + } + + @Override + public void setPosition(long position) { + this.position = position; + setContentDescription(getProgressText()); + } + + @Override + public void setBufferedPosition(long bufferedPosition) { + this.bufferedPosition = bufferedPosition; + } + + @Override + public void setDuration(long duration) { + this.duration = duration; + if (scrubbing && duration == C.TIME_UNSET) { + stopScrubbing(true); + } else { + updateScrubberState(); + } + } + + @Override + public void setAdBreakTimesMs(@Nullable long[] adBreakTimesMs, int adBreakCount) { + Assertions.checkArgument(adBreakCount == 0 || adBreakTimesMs != null); + this.adBreakCount = adBreakCount; + this.adBreakTimesMs = adBreakTimesMs; + } + + @Override + public void setEnabled(boolean enabled) { + super.setEnabled(enabled); + updateScrubberState(); + if (scrubbing && !enabled) { + stopScrubbing(true); + } + } + + @Override + public void onDraw(Canvas canvas) { + canvas.save(); + drawTimeBar(canvas); + drawPlayhead(canvas); + canvas.restore(); + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + if (!isEnabled() || duration <= 0) { + return false; + } + Point touchPosition = resolveRelativeTouchPosition(event); + int x = touchPosition.x; + int y = touchPosition.y; + switch (event.getAction()) { + case MotionEvent.ACTION_DOWN: + if (isInSeekBar(x, y)) { + startScrubbing(); + positionScrubber(x); + scrubPosition = getScrubberPosition(); + update(); + invalidate(); + return true; + } + break; + case MotionEvent.ACTION_MOVE: + if (scrubbing) { + if (y < fineScrubYThreshold) { + int relativeX = x - lastCoarseScrubXPosition; + positionScrubber(lastCoarseScrubXPosition + relativeX / FINE_SCRUB_RATIO); + } else { + lastCoarseScrubXPosition = x; + positionScrubber(x); + } + scrubPosition = getScrubberPosition(); + if (listener != null) { + listener.onScrubMove(this, scrubPosition); + } + update(); + invalidate(); + return true; + } + break; + case MotionEvent.ACTION_UP: + case MotionEvent.ACTION_CANCEL: + if (scrubbing) { + stopScrubbing(event.getAction() == MotionEvent.ACTION_CANCEL); + return true; + } + break; + default: + // Do nothing. + } + return false; + } + + @Override + public boolean onKeyDown(int keyCode, KeyEvent event) { + if (isEnabled()) { + long positionIncrement = getPositionIncrement(); + switch (keyCode) { + case KeyEvent.KEYCODE_DPAD_LEFT: + positionIncrement = -positionIncrement; + // Fall through. + case KeyEvent.KEYCODE_DPAD_RIGHT: + if (scrubIncrementally(positionIncrement)) { + removeCallbacks(stopScrubbingRunnable); + postDelayed(stopScrubbingRunnable, STOP_SCRUBBING_TIMEOUT_MS); + return true; + } + break; + case KeyEvent.KEYCODE_DPAD_CENTER: + case KeyEvent.KEYCODE_ENTER: + if (scrubbing) { + removeCallbacks(stopScrubbingRunnable); + stopScrubbingRunnable.run(); + return true; + } + break; + default: + // Do nothing. + } + } + return super.onKeyDown(keyCode, event); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + int measureWidth = MeasureSpec.getSize(widthMeasureSpec); + int measureHeight = MeasureSpec.getSize(heightMeasureSpec); + setMeasuredDimension(measureWidth, measureHeight); + } + + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + int width = right - left; + int height = bottom - top; + int barY = height - touchTargetHeight; + int seekLeft = getPaddingLeft(); + int seekRight = width - getPaddingRight(); + int progressY = barY + (touchTargetHeight - barHeight) / 2; + seekBounds.set(seekLeft, barY, seekRight, barY + touchTargetHeight); + progressBar.set(seekBounds.left + scrubberPadding, progressY, + seekBounds.right - scrubberPadding, progressY + barHeight); + update(); + } + + @Override + protected void onSizeChanged(int width, int height, int oldWidth, int oldHeight) { + super.onSizeChanged(width, height, oldWidth, oldHeight); + } + + @TargetApi(14) + @Override + public void onInitializeAccessibilityEvent(AccessibilityEvent event) { + super.onInitializeAccessibilityEvent(event); + if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_SELECTED) { + event.getText().add(getProgressText()); + } + event.setClassName(DefaultTimeBar.class.getName()); + } + + @TargetApi(14) + @Override + public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfo(info); + info.setClassName(DefaultTimeBar.class.getCanonicalName()); + info.setContentDescription(getProgressText()); + if (duration <= 0) { + return; + } + if (Util.SDK_INT >= 21) { + info.addAction(AccessibilityAction.ACTION_SCROLL_FORWARD); + info.addAction(AccessibilityAction.ACTION_SCROLL_BACKWARD); + } else if (Util.SDK_INT >= 16) { + info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD); + info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD); + } + } + + @TargetApi(16) + @Override + public boolean performAccessibilityAction(int action, Bundle args) { + if (super.performAccessibilityAction(action, args)) { + return true; + } + if (duration <= 0) { + return false; + } + if (action == AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD) { + if (scrubIncrementally(-getPositionIncrement())) { + stopScrubbing(false); + } + } else if (action == AccessibilityNodeInfo.ACTION_SCROLL_FORWARD) { + if (scrubIncrementally(getPositionIncrement())) { + stopScrubbing(false); + } + } else { + return false; + } + sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED); + return true; + } + + // Internal methods. + + private void startScrubbing() { + scrubbing = true; + updateScrubberState(); + ViewParent parent = getParent(); + if (parent != null) { + parent.requestDisallowInterceptTouchEvent(true); + } + if (listener != null) { + listener.onScrubStart(this); + } + } + + private void stopScrubbing(boolean canceled) { + scrubbing = false; + ViewParent parent = getParent(); + if (parent != null) { + parent.requestDisallowInterceptTouchEvent(false); + } + updateScrubberState(); + invalidate(); + if (listener != null) { + listener.onScrubStop(this, getScrubberPosition(), canceled); + } + } + + private void updateScrubberState() { + scrubberSize = scrubbing ? scrubberDraggedSize + : (isEnabled() && duration >= 0 ? scrubberEnabledSize : scrubberDisabledSize); + } + + private void update() { + bufferedBar.set(progressBar); + scrubberBar.set(progressBar); + long newScrubberTime = scrubbing ? scrubPosition : position; + if (duration > 0) { + int bufferedPixelWidth = + (int) ((progressBar.width() * bufferedPosition) / duration); + bufferedBar.right = progressBar.left + bufferedPixelWidth; + int scrubberPixelPosition = + (int) ((progressBar.width() * newScrubberTime) / duration); + scrubberBar.right = progressBar.left + scrubberPixelPosition; + } else { + bufferedBar.right = progressBar.left; + scrubberBar.right = progressBar.left; + } + invalidate(seekBounds); + } + + private void positionScrubber(float xPosition) { + scrubberBar.right = Util.constrainValue((int) xPosition, progressBar.left, progressBar.right); + } + + private Point resolveRelativeTouchPosition(MotionEvent motionEvent) { + if (locationOnScreen == null) { + locationOnScreen = new int[2]; + touchPosition = new Point(); + } + getLocationOnScreen(locationOnScreen); + touchPosition.set( + ((int) motionEvent.getRawX()) - locationOnScreen[0], + ((int) motionEvent.getRawY()) - locationOnScreen[1]); + return touchPosition; + } + + private long getScrubberPosition() { + if (progressBar.width() <= 0 || duration == C.TIME_UNSET) { + return 0; + } + return (scrubberBar.width() * duration) / progressBar.width(); + } + + private boolean isInSeekBar(float x, float y) { + return seekBounds.contains((int) x, (int) y); + } + + private void drawTimeBar(Canvas canvas) { + int progressBarHeight = progressBar.height(); + int barTop = progressBar.centerY() - progressBarHeight / 2; + int barBottom = barTop + progressBarHeight; + if (duration <= 0) { + canvas.drawRect(progressBar.left, barTop, progressBar.right, barBottom, progressPaint); + return; + } + int bufferedLeft = bufferedBar.left; + int bufferedRight = bufferedBar.right; + int progressLeft = Math.max(Math.max(progressBar.left, bufferedRight), scrubberBar.right); + if (progressLeft < progressBar.right) { + canvas.drawRect(progressLeft, barTop, progressBar.right, barBottom, progressPaint); + } + bufferedLeft = Math.max(bufferedLeft, scrubberBar.right); + if (bufferedRight > bufferedLeft) { + canvas.drawRect(bufferedLeft, barTop, bufferedRight, barBottom, bufferedPaint); + } + if (scrubberBar.width() > 0) { + canvas.drawRect(scrubberBar.left, barTop, scrubberBar.right, barBottom, scrubberPaint); + } + int adMarkerOffset = adMarkerWidth / 2; + for (int i = 0; i < adBreakCount; i++) { + long adBreakTimeMs = Util.constrainValue(adBreakTimesMs[i], 0, duration); + int markerPositionOffset = + (int) (progressBar.width() * adBreakTimeMs / duration) - adMarkerOffset; + int markerLeft = progressBar.left + Math.min(progressBar.width() - adMarkerWidth, + Math.max(0, markerPositionOffset)); + canvas.drawRect(markerLeft, barTop, markerLeft + adMarkerWidth, barBottom, adMarkerPaint); + } + } + + private void drawPlayhead(Canvas canvas) { + if (duration <= 0) { + return; + } + int playheadRadius = scrubberSize / 2; + int playheadCenter = Util.constrainValue(scrubberBar.right, scrubberBar.left, + progressBar.right); + canvas.drawCircle(playheadCenter, scrubberBar.centerY(), playheadRadius, scrubberPaint); + } + + private String getProgressText() { + return Util.getStringForTime(formatBuilder, formatter, position); + } + + private long getPositionIncrement() { + return keyTimeIncrement == C.TIME_UNSET + ? (duration == C.TIME_UNSET ? 0 : (duration / keyCountIncrement)) : keyTimeIncrement; + } + + /** + * Incrementally scrubs the position by {@code positionChange}. + * + * @param positionChange The change in the scrubber position, in milliseconds. May be negative. + * @return Returns whether the scrubber position changed. + */ + private boolean scrubIncrementally(long positionChange) { + if (duration <= 0) { + return false; + } + long scrubberPosition = getScrubberPosition(); + scrubPosition = Util.constrainValue(scrubberPosition + positionChange, 0, duration); + if (scrubPosition == scrubberPosition) { + return false; + } + if (!scrubbing) { + startScrubbing(); + } + if (listener != null) { + listener.onScrubMove(this, scrubPosition); + } + update(); + return true; + } + + private static int dpToPx(DisplayMetrics displayMetrics, int dps) { + return (int) (dps * displayMetrics.density + 0.5f); + } + +} diff --git a/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlaybackControlView.java b/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlaybackControlView.java index 757252297d..a5c667635a 100644 --- a/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlaybackControlView.java +++ b/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlaybackControlView.java @@ -15,16 +15,17 @@ */ package com.google.android.exoplayer2.ui; +import android.annotation.SuppressLint; import android.annotation.TargetApi; import android.content.Context; import android.content.res.TypedArray; import android.os.SystemClock; import android.util.AttributeSet; +import android.util.Log; import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.View; import android.widget.FrameLayout; -import android.widget.SeekBar; import android.widget.TextView; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.ExoPlaybackException; @@ -34,6 +35,7 @@ import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.source.TrackGroupArray; import com.google.android.exoplayer2.trackselection.TrackSelectionArray; import com.google.android.exoplayer2.util.Util; +import java.util.Arrays; import java.util.Formatter; import java.util.Locale; @@ -125,9 +127,9 @@ import java.util.Locale; *

  • Type: {@link TextView}
  • *
* - *
  • {@code exo_progress} - Seek bar that's updated during playback and allows seeking. + *
  • {@code exo_progress} - Time bar that's updated during playback and allows seeking. *
      - *
    • Type: {@link SeekBar}
    • + *
    • Type: {@link TimeBar}
    • *
    *
  • * @@ -144,6 +146,8 @@ import java.util.Locale; */ public class PlaybackControlView extends FrameLayout { + private static final String TAG = "PlaybackControlView"; + /** * Listener to be notified about changes of the visibility of the UI control. */ @@ -191,7 +195,11 @@ public class PlaybackControlView extends FrameLayout { public static final int DEFAULT_REWIND_MS = 5000; public static final int DEFAULT_SHOW_TIMEOUT_MS = 5000; - private static final int PROGRESS_BAR_MAX = 1000; + /** + * The maximum number of windows that can be shown in a multi-window time bar. + */ + public static final int MAX_WINDOWS_FOR_MULTI_WINDOW_TIME_BAR = 100; + private static final long MAX_POSITION_FOR_SEEK_TO_PREVIOUS = 3000; private final ComponentListener componentListener; @@ -203,21 +211,25 @@ public class PlaybackControlView extends FrameLayout { private final View rewindButton; private final TextView durationView; private final TextView positionView; - private final SeekBar progressBar; + private final TimeBar timeBar; private final StringBuilder formatBuilder; private final Formatter formatter; - private final Timeline.Window currentWindow; + private final Timeline.Period period; + private final Timeline.Window window; private ExoPlayer player; private SeekDispatcher seekDispatcher; private VisibilityListener visibilityListener; private boolean isAttachedToWindow; - private boolean dragging; + private boolean showMultiWindowTimeBar; + private boolean multiWindowTimeBar; + private boolean scrubbing; private int rewindMs; private int fastForwardMs; private int showTimeoutMs; private long hideAtMs; + private long[] adBreakTimesMs; private final Runnable updateProgressAction = new Runnable() { @Override @@ -262,9 +274,11 @@ public class PlaybackControlView extends FrameLayout { a.recycle(); } } - currentWindow = new Timeline.Window(); + period = new Timeline.Period(); + window = new Timeline.Window(); formatBuilder = new StringBuilder(); formatter = new Formatter(formatBuilder, Locale.getDefault()); + adBreakTimesMs = new long[0]; componentListener = new ComponentListener(); seekDispatcher = DEFAULT_SEEK_DISPATCHER; @@ -273,10 +287,9 @@ public class PlaybackControlView extends FrameLayout { durationView = (TextView) findViewById(R.id.exo_duration); positionView = (TextView) findViewById(R.id.exo_position); - progressBar = (SeekBar) findViewById(R.id.exo_progress); - if (progressBar != null) { - progressBar.setOnSeekBarChangeListener(componentListener); - progressBar.setMax(PROGRESS_BAR_MAX); + timeBar = (TimeBar) findViewById(R.id.exo_progress); + if (timeBar != null) { + timeBar.setListener(componentListener); } playButton = findViewById(R.id.exo_play); if (playButton != null) { @@ -314,7 +327,7 @@ public class PlaybackControlView extends FrameLayout { /** * Sets the {@link ExoPlayer} to control. * - * @param player the {@code ExoPlayer} to control. + * @param player The {@code ExoPlayer} to control. */ public void setPlayer(ExoPlayer player) { if (this.player == player) { @@ -330,6 +343,18 @@ public class PlaybackControlView extends FrameLayout { updateAll(); } + /** + * Sets whether the time bar should show all windows, as opposed to just the current one. If the + * timeline has more than {@link #MAX_WINDOWS_FOR_MULTI_WINDOW_TIME_BAR} windows the time bar will + * fall back to showing a single window. + * + * @param showMultiWindowTimeBar Whether the time bar should show all windows. + */ + public void setShowMultiWindowTimeBar(boolean showMultiWindowTimeBar) { + this.showMultiWindowTimeBar = showMultiWindowTimeBar; + updateTimeBarMode(); + } + /** * Sets the {@link VisibilityListener}. * @@ -473,51 +498,122 @@ public class PlaybackControlView extends FrameLayout { if (!isVisible() || !isAttachedToWindow) { return; } - Timeline currentTimeline = player != null ? player.getCurrentTimeline() : null; - boolean haveNonEmptyTimeline = currentTimeline != null && !currentTimeline.isEmpty(); + Timeline timeline = player != null ? player.getCurrentTimeline() : null; + boolean haveNonEmptyTimeline = timeline != null && !timeline.isEmpty(); boolean isSeekable = false; boolean enablePrevious = false; boolean enableNext = false; if (haveNonEmptyTimeline) { - int currentWindowIndex = player.getCurrentWindowIndex(); - currentTimeline.getWindow(currentWindowIndex, currentWindow); - isSeekable = currentWindow.isSeekable; - enablePrevious = currentWindowIndex > 0 || isSeekable || !currentWindow.isDynamic; - enableNext = (currentWindowIndex < currentTimeline.getWindowCount() - 1) - || currentWindow.isDynamic; + int windowIndex = player.getCurrentWindowIndex(); + timeline.getWindow(windowIndex, window); + isSeekable = window.isSeekable; + enablePrevious = windowIndex > 0 || isSeekable || !window.isDynamic; + enableNext = (windowIndex < timeline.getWindowCount() - 1) || window.isDynamic; } - setButtonEnabled(enablePrevious , previousButton); + setButtonEnabled(enablePrevious, previousButton); setButtonEnabled(enableNext, nextButton); setButtonEnabled(fastForwardMs > 0 && isSeekable, fastForwardButton); setButtonEnabled(rewindMs > 0 && isSeekable, rewindButton); - if (progressBar != null) { - progressBar.setEnabled(isSeekable); + if (timeBar != null) { + timeBar.setEnabled(isSeekable); } } + private void updateTimeBarMode() { + if (player == null) { + return; + } + if (showMultiWindowTimeBar) { + if (player.getCurrentTimeline().getWindowCount() <= MAX_WINDOWS_FOR_MULTI_WINDOW_TIME_BAR) { + multiWindowTimeBar = true; + return; + } + Log.w(TAG, "Too many windows for multi-window time bar. Falling back to showing one window."); + } + multiWindowTimeBar = false; + } + private void updateProgress() { if (!isVisible() || !isAttachedToWindow) { return; } - long duration = player == null ? 0 : player.getDuration(); - long position = player == null ? 0 : player.getCurrentPosition(); - if (durationView != null) { - durationView.setText(stringForTime(duration)); + + long position = 0; + long bufferedPosition = 0; + long duration = 0; + if (player != null) { + if (multiWindowTimeBar) { + Timeline timeline = player.getCurrentTimeline(); + int windowCount = timeline.getWindowCount(); + int periodIndex = player.getCurrentPeriodIndex(); + long positionUs = 0; + long bufferedPositionUs = 0; + long durationUs = 0; + boolean isInAdBreak = false; + boolean isPlayingAd = false; + int adBreakCount = 0; + for (int i = 0; i < windowCount; i++) { + timeline.getWindow(i, window); + for (int j = window.firstPeriodIndex; j <= window.lastPeriodIndex; j++) { + if (timeline.getPeriod(j, period).isAd) { + isPlayingAd |= j == periodIndex; + if (!isInAdBreak) { + isInAdBreak = true; + if (adBreakCount == adBreakTimesMs.length) { + adBreakTimesMs = Arrays.copyOf(adBreakTimesMs, + adBreakTimesMs.length == 0 ? 1 : adBreakTimesMs.length * 2); + } + adBreakTimesMs[adBreakCount++] = C.usToMs(durationUs); + } + } else { + isInAdBreak = false; + long periodDurationUs = period.getDurationUs(); + if (periodDurationUs == C.TIME_UNSET) { + durationUs = C.TIME_UNSET; + break; + } + long periodDurationInWindowUs = periodDurationUs; + if (j == window.firstPeriodIndex) { + periodDurationInWindowUs -= window.positionInFirstPeriodUs; + } + if (i < periodIndex) { + positionUs += periodDurationInWindowUs; + bufferedPositionUs += periodDurationInWindowUs; + } + durationUs += periodDurationInWindowUs; + } + } + } + position = C.usToMs(positionUs); + bufferedPosition = C.usToMs(bufferedPositionUs); + duration = C.usToMs(durationUs); + if (!isPlayingAd) { + position += player.getCurrentPosition(); + bufferedPosition += player.getBufferedPosition(); + } + if (timeBar != null) { + timeBar.setAdBreakTimesMs(adBreakTimesMs, adBreakCount); + } + } else { + position = player.getCurrentPosition(); + bufferedPosition = player.getBufferedPosition(); + duration = player.getDuration(); + } } - if (positionView != null && !dragging) { - positionView.setText(stringForTime(position)); + if (durationView != null) { + durationView.setText(Util.getStringForTime(formatBuilder, formatter, duration)); + } + if (positionView != null && !scrubbing) { + positionView.setText(Util.getStringForTime(formatBuilder, formatter, position)); + } + if (timeBar != null) { + timeBar.setPosition(position); + timeBar.setBufferedPosition(bufferedPosition); + timeBar.setDuration(duration); } - if (progressBar != null) { - if (!dragging) { - progressBar.setProgress(progressBarValue(position)); - } - long bufferedPosition = player == null ? 0 : player.getBufferedPosition(); - progressBar.setSecondaryProgress(progressBarValue(bufferedPosition)); - // Remove scheduled updates. - } + // Cancel any pending updates and schedule a new one if necessary. removeCallbacks(updateProgressAction); - // Schedule an update if necessary. int playbackState = player == null ? ExoPlayer.STATE_IDLE : player.getPlaybackState(); if (playbackState != ExoPlayer.STATE_IDLE && playbackState != ExoPlayer.STATE_ENDED) { long delayMs; @@ -560,55 +656,31 @@ public class PlaybackControlView extends FrameLayout { view.setAlpha(alpha); } - private String stringForTime(long timeMs) { - if (timeMs == C.TIME_UNSET) { - timeMs = 0; - } - long totalSeconds = (timeMs + 500) / 1000; - long seconds = totalSeconds % 60; - long minutes = (totalSeconds / 60) % 60; - long hours = totalSeconds / 3600; - formatBuilder.setLength(0); - return hours > 0 ? formatter.format("%d:%02d:%02d", hours, minutes, seconds).toString() - : formatter.format("%02d:%02d", minutes, seconds).toString(); - } - - private int progressBarValue(long position) { - long duration = player == null ? C.TIME_UNSET : player.getDuration(); - return duration == C.TIME_UNSET || duration == 0 ? 0 - : (int) ((position * PROGRESS_BAR_MAX) / duration); - } - - private long positionValue(int progress) { - long duration = player == null ? C.TIME_UNSET : player.getDuration(); - return duration == C.TIME_UNSET ? 0 : ((duration * progress) / PROGRESS_BAR_MAX); - } - private void previous() { - Timeline currentTimeline = player.getCurrentTimeline(); - if (currentTimeline.isEmpty()) { + Timeline timeline = player.getCurrentTimeline(); + if (timeline.isEmpty()) { return; } - int currentWindowIndex = player.getCurrentWindowIndex(); - currentTimeline.getWindow(currentWindowIndex, currentWindow); - if (currentWindowIndex > 0 && (player.getCurrentPosition() <= MAX_POSITION_FOR_SEEK_TO_PREVIOUS - || (currentWindow.isDynamic && !currentWindow.isSeekable))) { - seekTo(currentWindowIndex - 1, C.TIME_UNSET); + int windowIndex = player.getCurrentWindowIndex(); + timeline.getWindow(windowIndex, window); + if (windowIndex > 0 && (player.getCurrentPosition() <= MAX_POSITION_FOR_SEEK_TO_PREVIOUS + || (window.isDynamic && !window.isSeekable))) { + seekTo(windowIndex - 1, C.TIME_UNSET); } else { seekTo(0); } } private void next() { - Timeline currentTimeline = player.getCurrentTimeline(); - if (currentTimeline.isEmpty()) { + Timeline timeline = player.getCurrentTimeline(); + if (timeline.isEmpty()) { return; } - int currentWindowIndex = player.getCurrentWindowIndex(); - if (currentWindowIndex < currentTimeline.getWindowCount() - 1) { - seekTo(currentWindowIndex + 1, C.TIME_UNSET); - } else if (currentTimeline.getWindow(currentWindowIndex, currentWindow, false).isDynamic) { - seekTo(currentWindowIndex, C.TIME_UNSET); + int windowIndex = player.getCurrentWindowIndex(); + if (windowIndex < timeline.getWindowCount() - 1) { + seekTo(windowIndex + 1, C.TIME_UNSET); + } else if (timeline.getWindow(windowIndex, window, false).isDynamic) { + seekTo(windowIndex, C.TIME_UNSET); } } @@ -714,6 +786,7 @@ public class PlaybackControlView extends FrameLayout { return true; } + @SuppressLint("InlinedApi") private static boolean isHandledMediaKey(int keyCode) { return keyCode == KeyEvent.KEYCODE_MEDIA_FAST_FORWARD || keyCode == KeyEvent.KEYCODE_MEDIA_REWIND @@ -724,33 +797,52 @@ public class PlaybackControlView extends FrameLayout { || keyCode == KeyEvent.KEYCODE_MEDIA_PREVIOUS; } - private final class ComponentListener implements ExoPlayer.EventListener, - SeekBar.OnSeekBarChangeListener, OnClickListener { + private final class ComponentListener implements ExoPlayer.EventListener, TimeBar.OnScrubListener, + OnClickListener { @Override - public void onStartTrackingTouch(SeekBar seekBar) { + public void onScrubStart(TimeBar timeBar) { removeCallbacks(hideAction); - dragging = true; + scrubbing = true; } @Override - public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { - if (fromUser) { - long position = positionValue(progress); - if (positionView != null) { - positionView.setText(stringForTime(position)); - } - if (player != null && !dragging) { - seekTo(position); - } + public void onScrubMove(TimeBar timeBar, long position) { + if (positionView != null) { + positionView.setText(Util.getStringForTime(formatBuilder, formatter, position)); } } @Override - public void onStopTrackingTouch(SeekBar seekBar) { - dragging = false; - if (player != null) { - seekTo(positionValue(seekBar.getProgress())); + public void onScrubStop(TimeBar timeBar, long position, boolean canceled) { + scrubbing = false; + if (!canceled && player != null) { + if (showMultiWindowTimeBar) { + Timeline timeline = player.getCurrentTimeline(); + int windowCount = timeline.getWindowCount(); + long remainingMs = position; + for (int i = 0; i < windowCount; i++) { + timeline.getWindow(i, window); + if (!timeline.getPeriod(window.firstPeriodIndex, period).isAd) { + long windowDurationMs = window.getDurationMs(); + if (windowDurationMs == C.TIME_UNSET) { + break; + } + if (i == windowCount - 1 && remainingMs >= windowDurationMs) { + // Seeking past the end of the last window should seek to the end of the timeline. + seekTo(i, windowDurationMs); + break; + } + if (remainingMs < windowDurationMs) { + seekTo(i, remainingMs); + break; + } + remainingMs -= windowDurationMs; + } + } + } else { + seekTo(position); + } } hideAfterTimeout(); } @@ -775,6 +867,7 @@ public class PlaybackControlView extends FrameLayout { @Override public void onTimelineChanged(Timeline timeline, Object manifest) { updateNavigation(); + updateTimeBarMode(); updateProgress(); } diff --git a/library/ui/src/main/java/com/google/android/exoplayer2/ui/SimpleExoPlayerView.java b/library/ui/src/main/java/com/google/android/exoplayer2/ui/SimpleExoPlayerView.java index 9d221be60a..c2ad8ddeb5 100644 --- a/library/ui/src/main/java/com/google/android/exoplayer2/ui/SimpleExoPlayerView.java +++ b/library/ui/src/main/java/com/google/android/exoplayer2/ui/SimpleExoPlayerView.java @@ -527,6 +527,16 @@ public final class SimpleExoPlayerView extends FrameLayout { controller.setFastForwardIncrementMs(fastForwardMs); } + /** + * Sets whether the time bar should show all windows, as opposed to just the current one. + * + * @param showMultiWindowTimeBar Whether to show all windows. + */ + public void setShowMultiWindowTimeBar(boolean showMultiWindowTimeBar) { + Assertions.checkState(controller != null); + controller.setShowMultiWindowTimeBar(showMultiWindowTimeBar); + } + /** * Gets the view onto which video is rendered. This is either a {@link SurfaceView} (default) * or a {@link TextureView} if the {@code use_texture_view} view attribute has been set to true. diff --git a/library/ui/src/main/java/com/google/android/exoplayer2/ui/TimeBar.java b/library/ui/src/main/java/com/google/android/exoplayer2/ui/TimeBar.java new file mode 100644 index 0000000000..aeb8e0255e --- /dev/null +++ b/library/ui/src/main/java/com/google/android/exoplayer2/ui/TimeBar.java @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.exoplayer2.ui; + +import android.support.annotation.Nullable; +import android.view.View; + +/** + * Interface for time bar views that can display a playback position, buffered position, duration + * and ad markers, and that have a listener for scrubbing (seeking) events. + */ +public interface TimeBar { + + /** + * @see View#isEnabled() + */ + void setEnabled(boolean enabled); + + /** + * Sets the listener for the scrubbing events. + * + * @param listener The listener for scrubbing events. + */ + void setListener(OnScrubListener listener); + + /** + * Sets the position increment for key presses and accessibility actions, in milliseconds. + *

    + * Clears any increment specified in a preceding call to {@link #setKeyCountIncrement(int)}. + * + * @param time The time increment, in milliseconds. + */ + void setKeyTimeIncrement(long time); + + /** + * Sets the position increment for key presses and accessibility actions, as a number of + * increments that divide the duration of the media. For example, passing 20 will cause key + * presses to increment/decrement the position by 1/20th of the duration (if known). + *

    + * Clears any increment specified in a preceding call to {@link #setKeyTimeIncrement(long)}. + * + * @param count The number of increments that divide the duration of the media. + */ + void setKeyCountIncrement(int count); + + /** + * Sets the current position. + * + * @param position The current position to show, in milliseconds. + */ + void setPosition(long position); + + /** + * Sets the buffered position. + * + * @param bufferedPosition The current buffered position to show, in milliseconds. + */ + void setBufferedPosition(long bufferedPosition); + + /** + * Sets the duration. + * + * @param duration The duration to show, in milliseconds. + */ + void setDuration(long duration); + + /** + * Sets the times of ad breaks. + * + * @param adBreakTimesMs An array where the first {@code adBreakCount} elements are the times of + * ad breaks in milliseconds. May be {@code null} if there are no ad breaks. + * @param adBreakCount The number of ad breaks. + */ + void setAdBreakTimesMs(@Nullable long[] adBreakTimesMs, int adBreakCount); + + /** + * Listener for scrubbing events. + */ + interface OnScrubListener { + + /** + * Called when the user starts moving the scrubber. + * + * @param timeBar The time bar. + */ + void onScrubStart(TimeBar timeBar); + + /** + * Called when the user moves the scrubber. + * + * @param timeBar The time bar. + * @param position The position of the scrubber, in milliseconds. + */ + void onScrubMove(TimeBar timeBar, long position); + + /** + * Called when the user stops moving the scrubber. + * + * @param timeBar The time bar. + * @param position The position of the scrubber, in milliseconds. + * @param canceled Whether scrubbing was canceled. + */ + void onScrubStop(TimeBar timeBar, long position, boolean canceled); + + } + +} diff --git a/library/ui/src/main/res/layout/exo_playback_control_view.xml b/library/ui/src/main/res/layout/exo_playback_control_view.xml index f8ef5a6fdd..665852936b 100644 --- a/library/ui/src/main/res/layout/exo_playback_control_view.xml +++ b/library/ui/src/main/res/layout/exo_playback_control_view.xml @@ -65,12 +65,11 @@ android:includeFontPadding="false" android:textColor="#FFBEBEBE"/> - + android:layout_height="24dp"/> + + + + + + + + + + + + From abcd177e8a940f5fe89288cd720abc7744e9416a Mon Sep 17 00:00:00 2001 From: andrewlewis Date: Mon, 27 Mar 2017 04:39:54 -0700 Subject: [PATCH 040/119] Remove some unused Sonic functionality. Also move it closer to the ExoPlayer code style. Note: This change is intended to be purely cosmetic. Issue: #26 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=151307575 --- .../android/exoplayer2/audio/Sonic.java | 874 ++++++------------ 1 file changed, 306 insertions(+), 568 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/audio/Sonic.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/Sonic.java index 40c52f13c2..47acccd967 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/audio/Sonic.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/audio/Sonic.java @@ -16,329 +16,129 @@ */ package com.google.android.exoplayer2.audio; +import com.google.android.exoplayer2.util.Assertions; +import java.util.Arrays; + /** - * Sonic audio time/pitch stretching library. Based on https://github.com/waywardgeek/sonic. + * Sonic audio stream processor for time/pitch stretching. + *

    + * Based on https://github.com/waywardgeek/sonic. */ /* package */ final class Sonic { - private static final int SONIC_MIN_PITCH = 65; - private static final int SONIC_MAX_PITCH = 400; - /* This is used to down-sample some inputs to improve speed */ - private static final int SONIC_AMDF_FREQ = 4000; + private static final boolean USE_CHORD_PITCH = false; + private static final int MINIMUM_PITCH = 65; + private static final int MAXIMUM_PITCH = 400; + private static final int AMDF_FREQUENCY = 4000; + private final int sampleRate; + private final int numChannels; + private final int minPeriod; + private final int maxPeriod; + private final int maxRequired; + private final short[] downSampleBuffer; + + private int inputBufferSize; private short[] inputBuffer; + private int outputBufferSize; private short[] outputBuffer; + private int pitchBufferSize; private short[] pitchBuffer; - private short[] downSampleBuffer; - private float speed; - private float volume; - private float pitch; - private float rate; private int oldRatePosition; private int newRatePosition; - private boolean useChordPitch; - private int quality; - private int numChannels; - private int inputBufferSize; - private int pitchBufferSize; - private int outputBufferSize; + private float speed; + private float pitch; private int numInputSamples; private int numOutputSamples; private int numPitchSamples; - private int minPeriod; - private int maxPeriod; - private int maxRequired; private int remainingInputToCopy; - private int sampleRate; private int prevPeriod; private int prevMinDiff; private int minDiff; private int maxDiff; - // Resize the array. - private short[] resize(short[] oldArray, int newLength) { - newLength *= numChannels; - short[] newArray = new short[newLength]; - int length = Math.min(oldArray.length, newLength); - - System.arraycopy(oldArray, 0, newArray, 0, length); - return newArray; - } - - // Move samples from one array to another. May move samples down within an array, but not up. - private void move(short[] dest, int destPos, short[] source, int sourcePos, int numSamples) { - System.arraycopy( - source, sourcePos * numChannels, dest, destPos * numChannels, numSamples * numChannels); - } - - // Scale the samples by the factor. - private void scaleSamples(short[] samples, int position, int numSamples, float volume) { - int fixedPointVolume = (int) (volume * 4096.0f); - int start = position * numChannels; - int stop = start + numSamples * numChannels; - - for (int xSample = start; xSample < stop; xSample++) { - int value = (samples[xSample] * fixedPointVolume) >> 12; - if (value > 32767) { - value = 32767; - } else if (value < -32767) { - value = -32767; - } - samples[xSample] = (short) value; - } - } - - // Get the speed of the stream. - public float getSpeed() { - return speed; - } - - // Set the speed of the stream. - public void setSpeed(float speed) { - this.speed = speed; - } - - // Get the pitch of the stream. - public float getPitch() { - return pitch; - } - - // Set the pitch of the stream. - public void setPitch(float pitch) { - this.pitch = pitch; - } - - // Get the rate of the stream. - public float getRate() { - return rate; - } - - // Set the playback rate of the stream. This scales pitch and speed at the same time. - public void setRate(float rate) { - this.rate = rate; - this.oldRatePosition = 0; - this.newRatePosition = 0; - } - - // Get the vocal chord pitch setting. - public boolean getChordPitch() { - return useChordPitch; - } - - // Set the vocal chord mode for pitch computation. Default is off. - public void setChordPitch(boolean useChordPitch) { - this.useChordPitch = useChordPitch; - } - - // Get the quality setting. - public int getQuality() { - return quality; - } - - // Set the "quality". Default 0 is virtually as good as 1, but very much faster. - public void setQuality(int quality) { - this.quality = quality; - } - - // Get the scaling factor of the stream. - public float getVolume() { - return volume; - } - - // Set the scaling factor of the stream. - public void setVolume(float volume) { - this.volume = volume; - } - - // Allocate stream buffers. - private void allocateStreamBuffers(int sampleRate, int numChannels) { - minPeriod = sampleRate / SONIC_MAX_PITCH; - maxPeriod = sampleRate / SONIC_MIN_PITCH; + /** + * Creates a new Sonic audio stream processor. + * + * @param sampleRate The sample rate of input audio. + * @param numChannels The number of channels in the input audio. + */ + public Sonic(int sampleRate, int numChannels) { + this.sampleRate = sampleRate; + this.numChannels = numChannels; + minPeriod = sampleRate / MAXIMUM_PITCH; + maxPeriod = sampleRate / MINIMUM_PITCH; maxRequired = 2 * maxPeriod; + downSampleBuffer = new short[maxRequired]; inputBufferSize = maxRequired; inputBuffer = new short[maxRequired * numChannels]; outputBufferSize = maxRequired; outputBuffer = new short[maxRequired * numChannels]; pitchBufferSize = maxRequired; pitchBuffer = new short[maxRequired * numChannels]; - downSampleBuffer = new short[maxRequired]; - this.sampleRate = sampleRate; - this.numChannels = numChannels; oldRatePosition = 0; newRatePosition = 0; prevPeriod = 0; - } - - // Create a sonic stream. - public Sonic(int sampleRate, int numChannels) { - allocateStreamBuffers(sampleRate, numChannels); speed = 1.0f; pitch = 1.0f; - volume = 1.0f; - rate = 1.0f; - oldRatePosition = 0; - newRatePosition = 0; - useChordPitch = false; - quality = 0; } - // Get the sample rate of the stream. - public int getSampleRate() { - return sampleRate; + /** + * Sets the output speed. + */ + public void setSpeed(float speed) { + this.speed = speed; } - // Set the sample rate of the stream. This will cause samples buffered in the stream to be lost. - public void setSampleRate(int sampleRate) { - allocateStreamBuffers(sampleRate, numChannels); + /** + * Gets the output speed. + */ + public float getSpeed() { + return speed; } - // Get the number of channels. - public int getNumChannels() { - return numChannels; + /** + * Sets the output pitch. + */ + public void setPitch(float pitch) { + this.pitch = pitch; } - // Set the num channels of the stream. This will cause samples buffered in the stream to be lost. - public void setNumChannels(int numChannels) { - allocateStreamBuffers(sampleRate, numChannels); + /** + * Gets the output pitch. + */ + public float getPitch() { + return pitch; } - // Enlarge the output buffer if needed. - private void enlargeOutputBufferIfNeeded(int numSamples) { - if (numOutputSamples + numSamples > outputBufferSize) { - outputBufferSize += (outputBufferSize >> 1) + numSamples; - outputBuffer = resize(outputBuffer, outputBufferSize); - } - } - - // Enlarge the input buffer if needed. - private void enlargeInputBufferIfNeeded(int numSamples) { - if (numInputSamples + numSamples > inputBufferSize) { - inputBufferSize += (inputBufferSize >> 1) + numSamples; - inputBuffer = resize(inputBuffer, inputBufferSize); - } - } - - // Add the input samples to the input buffer. - private void addFloatSamplesToInputBuffer(float[] samples, int numSamples) { - if (numSamples == 0) { - return; - } - enlargeInputBufferIfNeeded(numSamples); - int xBuffer = numInputSamples * numChannels; - for (int xSample = 0; xSample < numSamples * numChannels; xSample++) { - inputBuffer[xBuffer++] = (short) (samples[xSample] * 32767.0f); - } - numInputSamples += numSamples; - } - - // Add the input samples to the input buffer. - private void addShortSamplesToInputBuffer(short[] samples, int numSamples) { - if (numSamples == 0) { - return; - } - enlargeInputBufferIfNeeded(numSamples); - move(inputBuffer, numInputSamples, samples, 0, numSamples); - numInputSamples += numSamples; - } - - // Add the input samples to the input buffer. - private void addUnsignedByteSamplesToInputBuffer(byte[] samples, int numSamples) { - short sample; - - enlargeInputBufferIfNeeded(numSamples); - int xBuffer = numInputSamples * numChannels; - for (int xSample = 0; xSample < numSamples * numChannels; xSample++) { - sample = (short) ((samples[xSample] & 0xff) - 128); // Convert from unsigned to signed - inputBuffer[xBuffer++] = (short) (sample << 8); - } - numInputSamples += numSamples; - } - - // Add the input samples to the input buffer. They must be 16-bit little-endian encoded in a byte - // array. - private void addBytesToInputBuffer(byte[] inBuffer, int numBytes) { + /** + * Writes {@code numBytes} from {@code buffer} as input. + * + * @param buffer A buffer containing input data. + * @param numBytes The number of bytes of input data to read from {@code buffer}. + */ + public void writeBytesToStream(byte[] buffer, int numBytes) { int numSamples = numBytes / (2 * numChannels); short sample; enlargeInputBufferIfNeeded(numSamples); int xBuffer = numInputSamples * numChannels; for (int xByte = 0; xByte + 1 < numBytes; xByte += 2) { - sample = (short) ((inBuffer[xByte] & 0xff) | (inBuffer[xByte + 1] << 8)); + sample = (short) ((buffer[xByte] & 0xff) | (buffer[xByte + 1] << 8)); inputBuffer[xBuffer++] = sample; } numInputSamples += numSamples; + processStreamInput(); } - // Remove input samples that we have already processed. - private void removeInputSamples(int position) { - int remainingSamples = numInputSamples - position; - - move(inputBuffer, 0, inputBuffer, position, remainingSamples); - numInputSamples = remainingSamples; - } - - // Just copy from the array to the output buffer - private void copyToOutput(short[] samples, int position, int numSamples) { - enlargeOutputBufferIfNeeded(numSamples); - move(outputBuffer, numOutputSamples, samples, position, numSamples); - numOutputSamples += numSamples; - } - - // Just copy from the input buffer to the output buffer. Return num samples copied. - private int copyInputToOutput(int position) { - int numSamples = remainingInputToCopy; - - if (numSamples > maxRequired) { - numSamples = maxRequired; - } - copyToOutput(inputBuffer, position, numSamples); - remainingInputToCopy -= numSamples; - return numSamples; - } - - // Read data out of the stream. Sometimes no data will be available, and zero - // is returned, which is not an error condition. - public int readFloatFromStream(float[] samples, int maxSamples) { - int numSamples = numOutputSamples; - int remainingSamples = 0; - - if (numSamples == 0) { - return 0; - } - if (numSamples > maxSamples) { - remainingSamples = numSamples - maxSamples; - numSamples = maxSamples; - } - for (int xSample = 0; xSample < numSamples * numChannels; xSample++) { - samples[xSample++] = (outputBuffer[xSample]) / 32767.0f; - } - move(outputBuffer, 0, outputBuffer, numSamples, remainingSamples); - numOutputSamples = remainingSamples; - return numSamples; - } - - // Read short data out of the stream. Sometimes no data will be available, and zero - // is returned, which is not an error condition. - public int readShortFromStream(short[] samples, int maxSamples) { - int numSamples = numOutputSamples; - int remainingSamples = 0; - - if (numSamples == 0) { - return 0; - } - if (numSamples > maxSamples) { - remainingSamples = numSamples - maxSamples; - numSamples = maxSamples; - } - move(samples, 0, outputBuffer, 0, numSamples); - move(outputBuffer, 0, outputBuffer, numSamples, remainingSamples); - numOutputSamples = remainingSamples; - return numSamples; - } - - // Read unsigned byte data out of the stream. Sometimes no data will be available, and zero - // is returned, which is not an error condition. - public int readBytesFromStream(byte[] outBuffer, int maxBytes) { + /** + * Reads up to {@code maxBytes} of output into {@code buffer}. + * + * @param buffer The buffer into which output will be written. + * @param maxBytes The maximum number of bytes to write. + * @return The number of bytes read from the stream. + */ + public int readBytesFromStream(byte[] buffer, int maxBytes) { int maxSamples = maxBytes / (2 * numChannels); int numSamples = numOutputSamples; int remainingSamples = 0; @@ -352,23 +152,24 @@ package com.google.android.exoplayer2.audio; } for (int xSample = 0; xSample < numSamples * numChannels; xSample++) { short sample = outputBuffer[xSample]; - outBuffer[xSample << 1] = (byte) (sample & 0xff); - outBuffer[(xSample << 1) + 1] = (byte) (sample >> 8); + buffer[xSample << 1] = (byte) (sample & 0xff); + buffer[(xSample << 1) + 1] = (byte) (sample >> 8); } - move(outputBuffer, 0, outputBuffer, numSamples, remainingSamples); + System.arraycopy(outputBuffer, numSamples * numChannels, outputBuffer, 0, + remainingSamples * numChannels); numOutputSamples = remainingSamples; return 2 * numSamples * numChannels; } - // Force the sonic stream to generate output using whatever data it currently - // has. No extra delay will be added to the output, but flushing in the middle of - // words could introduce distortion. + /** + * Forces generating output using whatever data has been queued already. No extra delay will be + * added to the output, but flushing in the middle of words could introduce distortion. + */ public void flushStream() { int remainingSamples = numInputSamples; float s = speed / pitch; - float r = rate * pitch; int expectedOutputSamples = - numOutputSamples + (int) ((remainingSamples / s + numPitchSamples) / r + 0.5f); + numOutputSamples + (int) ((remainingSamples / s + numPitchSamples) / pitch + 0.5f); // Add enough silence to flush both input and pitch buffers. enlargeInputBufferIfNeeded(remainingSamples + 2 * maxRequired); @@ -376,7 +177,7 @@ package com.google.android.exoplayer2.audio; inputBuffer[remainingSamples * numChannels + xSample] = 0; } numInputSamples += 2 * maxRequired; - writeShortToStream(null, 0); + processStreamInput(); // Throw away any extra samples we generated due to the silence we added. if (numOutputSamples > expectedOutputSamples) { numOutputSamples = expectedOutputSamples; @@ -387,22 +188,59 @@ package com.google.android.exoplayer2.audio; numPitchSamples = 0; } - // Return the number of samples in the output buffer + /** + * Returns the number of output samples that can be read with + * {@link #readBytesFromStream(byte[], int)}. + */ public int samplesAvailable() { return numOutputSamples; } - // If skip is greater than one, average skip samples together and write them to - // the down-sample buffer. If numChannels is greater than one, mix the channels - // together as we down sample. + // Internal methods. + + private void enlargeOutputBufferIfNeeded(int numSamples) { + if (numOutputSamples + numSamples > outputBufferSize) { + outputBufferSize += (outputBufferSize / 2) + numSamples; + outputBuffer = Arrays.copyOf(outputBuffer, outputBufferSize * numChannels); + } + } + + private void enlargeInputBufferIfNeeded(int numSamples) { + if (numInputSamples + numSamples > inputBufferSize) { + inputBufferSize += (inputBufferSize / 2) + numSamples; + inputBuffer = Arrays.copyOf(inputBuffer, inputBufferSize * numChannels); + } + } + + private void removeProcessedInputSamples(int position) { + int remainingSamples = numInputSamples - position; + System.arraycopy(inputBuffer, position * numChannels, inputBuffer, 0, + remainingSamples * numChannels); + numInputSamples = remainingSamples; + } + + private void copyToOutput(short[] samples, int position, int numSamples) { + enlargeOutputBufferIfNeeded(numSamples); + System.arraycopy(samples, position * numChannels, outputBuffer, numOutputSamples * numChannels, + numSamples * numChannels); + numOutputSamples += numSamples; + } + + private int copyInputToOutput(int position) { + int numSamples = Math.min(maxRequired, remainingInputToCopy); + copyToOutput(inputBuffer, position, numSamples); + remainingInputToCopy -= numSamples; + return numSamples; + } + private void downSampleInput(short[] samples, int position, int skip) { + // If skip is greater than one, average skip samples together and write them to the down-sample + // buffer. If numChannels is greater than one, mix the channels together as we down sample. int numSamples = maxRequired / skip; int samplesPerValue = numChannels * skip; - int value; - position *= numChannels; for (int i = 0; i < numSamples; i++) { - value = 0; + int value = 0; for (int j = 0; j < samplesPerValue; j++) { value += samples[position + i * samplesPerValue + j]; } @@ -411,14 +249,13 @@ package com.google.android.exoplayer2.audio; } } - // Find the best frequency match in the range, and given a sample skip multiple. - // For now, just find the pitch of the first channel. private int findPitchPeriodInRange(short[] samples, int position, int minPeriod, int maxPeriod) { + // Find the best frequency match in the range, and given a sample skip multiple. For now, just + // find the pitch of the first channel. int bestPeriod = 0; int worstPeriod = 255; int minDiff = 1; int maxDiff = 0; - position *= numChannels; for (int period = minPeriod; period <= maxPeriod; period++) { int diff = 0; @@ -428,7 +265,7 @@ package com.google.android.exoplayer2.audio; diff += sVal >= pVal ? sVal - pVal : pVal - sVal; } // Note that the highest number of samples we add into diff will be less than 256, since we - // skip samples. Thus, diff is a 24 bit number, and we can safely multiply by numSamples + // skip samples. Thus, diff is a 24 bit number, and we can safely multiply by numSamples // without overflow. if (diff * bestPeriod < minDiff * period) { minDiff = diff; @@ -441,13 +278,14 @@ package com.google.android.exoplayer2.audio; } this.minDiff = minDiff / bestPeriod; this.maxDiff = maxDiff / worstPeriod; - return bestPeriod; } - // At abrupt ends of voiced words, we can have pitch periods that are better - // approximated by the previous pitch period estimate. Try to detect this case. - private boolean prevPeriodBetter(int minDiff, int maxDiff, boolean preferNewPeriod) { + /** + * Returns whether the previous pitch period estimate is a better approximation, which can occur + * at the abrupt end of voiced words. + */ + private boolean previousPeriodBetter(int minDiff, int maxDiff, boolean preferNewPeriod) { if (minDiff == 0 || prevPeriod == 0) { return false; } @@ -468,18 +306,14 @@ package com.google.android.exoplayer2.audio; return true; } - // Find the pitch period. This is a critical step, and we may have to try - // multiple ways to get a good answer. This version uses AMDF. To improve - // speed, we down sample by an integer factor get in the 11KHz range, and then - // do it again with a narrower frequency range without down sampling private int findPitchPeriod(short[] samples, int position, boolean preferNewPeriod) { + // Find the pitch period. This is a critical step, and we may have to try multiple ways to get a + // good answer. This version uses AMDF. To improve speed, we down sample by an integer factor + // get in the 11 kHz range, and then do it again with a narrower frequency range without down + // sampling. int period; int retPeriod; - int skip = 1; - - if (sampleRate > SONIC_AMDF_FREQ && quality == 0) { - skip = sampleRate / SONIC_AMDF_FREQ; - } + int skip = sampleRate > AMDF_FREQUENCY ? sampleRate / AMDF_FREQUENCY : 1; if (numChannels == 1 && skip == 1) { period = findPitchPeriodInRange(samples, position, minPeriod, maxPeriod); } else { @@ -487,8 +321,8 @@ package com.google.android.exoplayer2.audio; period = findPitchPeriodInRange(downSampleBuffer, 0, minPeriod / skip, maxPeriod / skip); if (skip != 1) { period *= skip; - int minP = period - (skip << 2); - int maxP = period + (skip << 2); + int minP = period - (skip * 4); + int maxP = period + (skip * 4); if (minP < minPeriod) { minP = minPeriod; } @@ -503,7 +337,7 @@ package com.google.android.exoplayer2.audio; } } } - if (prevPeriodBetter(minDiff, maxDiff, preferNewPeriod)) { + if (previousPeriodBetter(minDiff, maxDiff, preferNewPeriod)) { retPeriod = prevPeriod; } else { retPeriod = period; @@ -513,8 +347,171 @@ package com.google.android.exoplayer2.audio; return retPeriod; } - // Overlap two sound segments, ramp the volume of one down, while ramping the - // other one from zero up, and add them, storing the result at the output. + private void moveNewSamplesToPitchBuffer(int originalNumOutputSamples) { + int numSamples = numOutputSamples - originalNumOutputSamples; + if (numPitchSamples + numSamples > pitchBufferSize) { + pitchBufferSize += (pitchBufferSize / 2) + numSamples; + pitchBuffer = Arrays.copyOf(pitchBuffer, pitchBufferSize * numChannels); + } + System.arraycopy(outputBuffer, originalNumOutputSamples * numChannels, pitchBuffer, + numPitchSamples * numChannels, numSamples * numChannels); + numOutputSamples = originalNumOutputSamples; + numPitchSamples += numSamples; + } + + private void removePitchSamples(int numSamples) { + if (numSamples == 0) { + return; + } + System.arraycopy(pitchBuffer, numSamples * numChannels, pitchBuffer, 0, + (numPitchSamples - numSamples) * numChannels); + numPitchSamples -= numSamples; + } + + private void adjustPitch(int originalNumOutputSamples) { + // Latency due to pitch changes could be reduced by looking at past samples to determine pitch, + // rather than future. + if (numOutputSamples == originalNumOutputSamples) { + return; + } + moveNewSamplesToPitchBuffer(originalNumOutputSamples); + int position = 0; + while (numPitchSamples - position >= maxRequired) { + int period = findPitchPeriod(pitchBuffer, position, false); + int newPeriod = (int) (period / pitch); + enlargeOutputBufferIfNeeded(newPeriod); + if (pitch >= 1.0f) { + overlapAdd(newPeriod, numChannels, outputBuffer, numOutputSamples, pitchBuffer, position, + pitchBuffer, position + period - newPeriod); + } else { + int separation = newPeriod - period; + overlapAddWithSeparation(period, numChannels, separation, outputBuffer, numOutputSamples, + pitchBuffer, position, pitchBuffer, position); + } + numOutputSamples += newPeriod; + position += period; + } + removePitchSamples(position); + } + + private short interpolate(short[] in, int inPos, int oldSampleRate, int newSampleRate) { + short left = in[inPos * numChannels]; + short right = in[inPos * numChannels + numChannels]; + int position = newRatePosition * oldSampleRate; + int leftPosition = oldRatePosition * newSampleRate; + int rightPosition = (oldRatePosition + 1) * newSampleRate; + int ratio = rightPosition - position; + int width = rightPosition - leftPosition; + return (short) ((ratio * left + (width - ratio) * right) / width); + } + + private void adjustRate(float rate, int originalNumOutputSamples) { + if (numOutputSamples == originalNumOutputSamples) { + return; + } + int newSampleRate = (int) (sampleRate / rate); + int oldSampleRate = sampleRate; + // Set these values to help with the integer math. + while (newSampleRate > (1 << 14) || oldSampleRate > (1 << 14)) { + newSampleRate /= 2; + oldSampleRate /= 2; + } + moveNewSamplesToPitchBuffer(originalNumOutputSamples); + // Leave at least one pitch sample in the buffer. + for (int position = 0; position < numPitchSamples - 1; position++) { + while ((oldRatePosition + 1) * newSampleRate > newRatePosition * oldSampleRate) { + enlargeOutputBufferIfNeeded(1); + for (int i = 0; i < numChannels; i++) { + outputBuffer[numOutputSamples * numChannels + i] = + interpolate(pitchBuffer, position + i, oldSampleRate, newSampleRate); + } + newRatePosition++; + numOutputSamples++; + } + oldRatePosition++; + if (oldRatePosition == oldSampleRate) { + oldRatePosition = 0; + Assertions.checkState(newRatePosition == newSampleRate); + newRatePosition = 0; + } + } + removePitchSamples(numPitchSamples - 1); + } + + private int skipPitchPeriod(short[] samples, int position, float speed, int period) { + // Skip over a pitch period, and copy period/speed samples to the output. + int newSamples; + if (speed >= 2.0f) { + newSamples = (int) (period / (speed - 1.0f)); + } else { + newSamples = period; + remainingInputToCopy = (int) (period * (2.0f - speed) / (speed - 1.0f)); + } + enlargeOutputBufferIfNeeded(newSamples); + overlapAdd(newSamples, numChannels, outputBuffer, numOutputSamples, samples, position, samples, + position + period); + numOutputSamples += newSamples; + return newSamples; + } + + private int insertPitchPeriod(short[] samples, int position, float speed, int period) { + // Insert a pitch period, and determine how much input to copy directly. + int newSamples; + if (speed < 0.5f) { + newSamples = (int) (period * speed / (1.0f - speed)); + } else { + newSamples = period; + remainingInputToCopy = (int) (period * (2.0f * speed - 1.0f) / (1.0f - speed)); + } + enlargeOutputBufferIfNeeded(period + newSamples); + System.arraycopy(samples, position * numChannels, outputBuffer, numOutputSamples * numChannels, + period * numChannels); + overlapAdd(newSamples, numChannels, outputBuffer, numOutputSamples + period, samples, + position + period, samples, position); + numOutputSamples += period + newSamples; + return newSamples; + } + + private void changeSpeed(float speed) { + if (numInputSamples < maxRequired) { + return; + } + int numSamples = numInputSamples; + int position = 0; + do { + if (remainingInputToCopy > 0) { + position += copyInputToOutput(position); + } else { + int period = findPitchPeriod(inputBuffer, position, true); + if (speed > 1.0) { + position += period + skipPitchPeriod(inputBuffer, position, speed, period); + } else { + position += insertPitchPeriod(inputBuffer, position, speed, period); + } + } + } while (position + maxRequired <= numSamples); + removeProcessedInputSamples(position); + } + + private void processStreamInput() { + // Resample as many pitch periods as we have buffered on the input. + int originalNumOutputSamples = numOutputSamples; + float s = speed / pitch; + if (s > 1.00001 || s < 0.99999) { + changeSpeed(s); + } else { + copyToOutput(inputBuffer, 0, numInputSamples); + numInputSamples = 0; + } + if (USE_CHORD_PITCH) { + if (pitch != 1.0f) { + adjustPitch(originalNumOutputSamples); + } + } else if (!USE_CHORD_PITCH && pitch != 1.0f) { + adjustRate(pitch, originalNumOutputSamples); + } + } + private static void overlapAdd(int numSamples, int numChannels, short[] out, int outPos, short[] rampDown, int rampDownPos, short[] rampUp, int rampUpPos) { for (int i = 0; i < numChannels; i++) { @@ -530,8 +527,6 @@ package com.google.android.exoplayer2.audio; } } - // Overlap two sound segments, ramp the volume of one down, while ramping the - // other one from zero up, and add them, storing the result at the output. private static void overlapAddWithSeparation(int numSamples, int numChannels, int separation, short[] out, int outPos, short[] rampDown, int rampDownPos, short[] rampUp, int rampUpPos) { for (int i = 0; i < numChannels; i++) { @@ -557,261 +552,4 @@ package com.google.android.exoplayer2.audio; } } - // Just move the new samples in the output buffer to the pitch buffer - private void moveNewSamplesToPitchBuffer(int originalNumOutputSamples) { - int numSamples = numOutputSamples - originalNumOutputSamples; - - if (numPitchSamples + numSamples > pitchBufferSize) { - pitchBufferSize += (pitchBufferSize >> 1) + numSamples; - pitchBuffer = resize(pitchBuffer, pitchBufferSize); - } - move(pitchBuffer, numPitchSamples, outputBuffer, originalNumOutputSamples, numSamples); - numOutputSamples = originalNumOutputSamples; - numPitchSamples += numSamples; - } - - // Remove processed samples from the pitch buffer. - private void removePitchSamples(int numSamples) { - if (numSamples == 0) { - return; - } - move(pitchBuffer, 0, pitchBuffer, numSamples, numPitchSamples - numSamples); - numPitchSamples -= numSamples; - } - - // Change the pitch. The latency this introduces could be reduced by looking at - // past samples to determine pitch, rather than future. - private void adjustPitch(int originalNumOutputSamples) { - int period; - int newPeriod; - int separation; - int position = 0; - - if (numOutputSamples == originalNumOutputSamples) { - return; - } - moveNewSamplesToPitchBuffer(originalNumOutputSamples); - while (numPitchSamples - position >= maxRequired) { - period = findPitchPeriod(pitchBuffer, position, false); - newPeriod = (int) (period / pitch); - enlargeOutputBufferIfNeeded(newPeriod); - if (pitch >= 1.0f) { - overlapAdd(newPeriod, numChannels, outputBuffer, numOutputSamples, pitchBuffer, position, - pitchBuffer, position + period - newPeriod); - } else { - separation = newPeriod - period; - overlapAddWithSeparation(period, numChannels, separation, outputBuffer, numOutputSamples, - pitchBuffer, position, pitchBuffer, position); - } - numOutputSamples += newPeriod; - position += period; - } - removePitchSamples(position); - } - - // Interpolate the new output sample. - private short interpolate(short[] in, int inPos, int oldSampleRate, int newSampleRate) { - short left = in[inPos * numChannels]; - short right = in[inPos * numChannels + numChannels]; - int position = newRatePosition * oldSampleRate; - int leftPosition = oldRatePosition * newSampleRate; - int rightPosition = (oldRatePosition + 1) * newSampleRate; - int ratio = rightPosition - position; - int width = rightPosition - leftPosition; - - return (short) ((ratio * left + (width - ratio) * right) / width); - } - - // Change the rate. - private void adjustRate(float rate, int originalNumOutputSamples) { - int newSampleRate = (int) (sampleRate / rate); - int oldSampleRate = sampleRate; - int position; - - // Set these values to help with the integer math - while (newSampleRate > (1 << 14) || oldSampleRate > (1 << 14)) { - newSampleRate >>= 1; - oldSampleRate >>= 1; - } - if (numOutputSamples == originalNumOutputSamples) { - return; - } - moveNewSamplesToPitchBuffer(originalNumOutputSamples); - // Leave at least one pitch sample in the buffer - for (position = 0; position < numPitchSamples - 1; position++) { - while ((oldRatePosition + 1) * newSampleRate > newRatePosition * oldSampleRate) { - enlargeOutputBufferIfNeeded(1); - for (int i = 0; i < numChannels; i++) { - outputBuffer[numOutputSamples * numChannels + i] = - interpolate(pitchBuffer, position + i, oldSampleRate, newSampleRate); - } - newRatePosition++; - numOutputSamples++; - } - oldRatePosition++; - if (oldRatePosition == oldSampleRate) { - oldRatePosition = 0; - if (newRatePosition != newSampleRate) { - System.out.printf("Assertion failed: newRatePosition != newSampleRate\n"); - assert false; - } - newRatePosition = 0; - } - } - removePitchSamples(position); - } - - // Skip over a pitch period, and copy period/speed samples to the output - private int skipPitchPeriod(short[] samples, int position, float speed, int period) { - int newSamples; - - if (speed >= 2.0f) { - newSamples = (int) (period / (speed - 1.0f)); - } else { - newSamples = period; - remainingInputToCopy = (int) (period * (2.0f - speed) / (speed - 1.0f)); - } - enlargeOutputBufferIfNeeded(newSamples); - overlapAdd(newSamples, numChannels, outputBuffer, numOutputSamples, samples, position, samples, - position + period); - numOutputSamples += newSamples; - return newSamples; - } - - // Insert a pitch period, and determine how much input to copy directly. - private int insertPitchPeriod(short[] samples, int position, float speed, int period) { - int newSamples; - - if (speed < 0.5f) { - newSamples = (int) (period * speed / (1.0f - speed)); - } else { - newSamples = period; - remainingInputToCopy = (int) (period * (2.0f * speed - 1.0f) / (1.0f - speed)); - } - enlargeOutputBufferIfNeeded(period + newSamples); - move(outputBuffer, numOutputSamples, samples, position, period); - overlapAdd(newSamples, numChannels, outputBuffer, numOutputSamples + period, samples, - position + period, samples, position); - numOutputSamples += period + newSamples; - return newSamples; - } - - // Resample as many pitch periods as we have buffered on the input. Return 0 if - // we fail to resize an input or output buffer. Also scale the output by the volume. - private void changeSpeed(float speed) { - int numSamples = numInputSamples; - int position = 0; - int period; - int newSamples; - - if (numInputSamples < maxRequired) { - return; - } - do { - if (remainingInputToCopy > 0) { - newSamples = copyInputToOutput(position); - position += newSamples; - } else { - period = findPitchPeriod(inputBuffer, position, true); - if (speed > 1.0) { - newSamples = skipPitchPeriod(inputBuffer, position, speed, period); - position += period + newSamples; - } else { - newSamples = insertPitchPeriod(inputBuffer, position, speed, period); - position += newSamples; - } - } - } while (position + maxRequired <= numSamples); - removeInputSamples(position); - } - - // Resample as many pitch periods as we have buffered on the input. Scale the output by the - // volume. - private void processStreamInput() { - int originalNumOutputSamples = numOutputSamples; - float s = speed / pitch; - float r = rate; - - if (!useChordPitch) { - r *= pitch; - } - if (s > 1.00001 || s < 0.99999) { - changeSpeed(s); - } else { - copyToOutput(inputBuffer, 0, numInputSamples); - numInputSamples = 0; - } - if (useChordPitch) { - if (pitch != 1.0f) { - adjustPitch(originalNumOutputSamples); - } - } else if (r != 1.0f) { - adjustRate(r, originalNumOutputSamples); - } - if (volume != 1.0f) { - // Adjust output volume. - scaleSamples(outputBuffer, originalNumOutputSamples, - numOutputSamples - originalNumOutputSamples, volume); - } - } - - // Write floating point data to the input buffer and process it. - public void writeFloatToStream(float[] samples, int numSamples) { - addFloatSamplesToInputBuffer(samples, numSamples); - processStreamInput(); - } - - // Write the data to the input stream, and process it. - public void writeShortToStream(short[] samples, int numSamples) { - addShortSamplesToInputBuffer(samples, numSamples); - processStreamInput(); - } - - // Simple wrapper around sonicWriteFloatToStream that does the unsigned byte to short - // conversion for you. - public void writeUnsignedByteToStream(byte[] samples, int numSamples) { - addUnsignedByteSamplesToInputBuffer(samples, numSamples); - processStreamInput(); - } - - // Simple wrapper around sonicWriteBytesToStream that does the byte to 16-bit LE conversion. - public void writeBytesToStream(byte[] inBuffer, int numBytes) { - addBytesToInputBuffer(inBuffer, numBytes); - processStreamInput(); - } - - // This is a non-stream oriented interface to just change the speed of a sound sample - public static int changeFloatSpeed(float[] samples, int numSamples, float speed, float pitch, - float rate, float volume, boolean useChordPitch, int sampleRate, int numChannels) { - Sonic stream = new Sonic(sampleRate, numChannels); - - stream.setSpeed(speed); - stream.setPitch(pitch); - stream.setRate(rate); - stream.setVolume(volume); - stream.setChordPitch(useChordPitch); - stream.writeFloatToStream(samples, numSamples); - stream.flushStream(); - numSamples = stream.samplesAvailable(); - stream.readFloatFromStream(samples, numSamples); - return numSamples; - } - - /* This is a non-stream oriented interface to just change the speed of a sound sample */ - public int sonicChangeShortSpeed(short[] samples, int numSamples, float speed, float pitch, - float rate, float volume, boolean useChordPitch, int sampleRate, int numChannels) { - Sonic stream = new Sonic(sampleRate, numChannels); - - stream.setSpeed(speed); - stream.setPitch(pitch); - stream.setRate(rate); - stream.setVolume(volume); - stream.setChordPitch(useChordPitch); - stream.writeShortToStream(samples, numSamples); - stream.flushStream(); - numSamples = stream.samplesAvailable(); - stream.readShortFromStream(samples, numSamples); - return numSamples; - } - } From 5ebb3d44812322ee9788feec5ebf5a49b51cccd5 Mon Sep 17 00:00:00 2001 From: olly Date: Mon, 27 Mar 2017 06:52:34 -0700 Subject: [PATCH 041/119] Correct allowTimeBeyondBuffer values for skipToKeyframeBefore calls The only case where we should pass false is if we want to know whether the passed time is within the buffered media. This is relevant within seekTo implementations only. This is related to (but does not fix) issue #2582 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=151315676 --- .../exoplayer2/extractor/DefaultTrackOutput.java | 14 -------------- .../exoplayer2/source/ExtractorMediaPeriod.java | 8 ++++++-- .../exoplayer2/source/chunk/ChunkSampleStream.java | 6 +++--- .../source/hls/HlsSampleStreamWrapper.java | 2 +- 4 files changed, 10 insertions(+), 20 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/DefaultTrackOutput.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/DefaultTrackOutput.java index 8aff8858a1..d57b55bfd2 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/DefaultTrackOutput.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/DefaultTrackOutput.java @@ -227,20 +227,6 @@ public final class DefaultTrackOutput implements TrackOutput { return infoQueue.getLargestQueuedTimestampUs(); } - /** - * Attempts to skip to the keyframe before or at the specified time. Succeeds only if the buffer - * contains a keyframe with a timestamp of {@code timeUs} or earlier, and if {@code timeUs} falls - * within the currently buffered media. - *

    - * This method is equivalent to {@code skipToKeyframeBefore(timeUs, false)}. - * - * @param timeUs The seek time. - * @return Whether the skip was successful. - */ - public boolean skipToKeyframeBefore(long timeUs) { - return skipToKeyframeBefore(timeUs, false); - } - /** * Attempts to skip to the keyframe before or at the specified time. Succeeds only if the buffer * contains a keyframe with a timestamp of {@code timeUs} or earlier. If diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/ExtractorMediaPeriod.java b/library/core/src/main/java/com/google/android/exoplayer2/source/ExtractorMediaPeriod.java index d843943710..d54e881183 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/ExtractorMediaPeriod.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/ExtractorMediaPeriod.java @@ -301,7 +301,7 @@ import java.io.IOException; boolean seekInsideBuffer = !isPendingReset(); for (int i = 0; seekInsideBuffer && i < trackCount; i++) { if (trackEnabledStates[i]) { - seekInsideBuffer = sampleQueues.valueAt(i).skipToKeyframeBefore(positionUs); + seekInsideBuffer = sampleQueues.valueAt(i).skipToKeyframeBefore(positionUs, false); } } // If we failed to seek within the sample queues, we need to restart. @@ -340,6 +340,10 @@ import java.io.IOException; loadingFinished, lastSeekPositionUs); } + /* package */ void skipToKeyframeBefore(int track, long timeUs) { + sampleQueues.valueAt(track).skipToKeyframeBefore(timeUs, true); + } + // Loader.Callback implementation. @Override @@ -566,7 +570,7 @@ import java.io.IOException; @Override public void skipToKeyframeBefore(long timeUs) { - sampleQueues.valueAt(track).skipToKeyframeBefore(timeUs); + ExtractorMediaPeriod.this.skipToKeyframeBefore(track, timeUs); } } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/ChunkSampleStream.java b/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/ChunkSampleStream.java index 93d86a8de1..6a62afe2fb 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/ChunkSampleStream.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/ChunkSampleStream.java @@ -193,7 +193,7 @@ public class ChunkSampleStream implements SampleStream, S // TODO: For this to work correctly, the embedded streams must not discard anything from their // sample queues beyond the current read position of the primary stream. for (DefaultTrackOutput embeddedSampleQueue : embeddedSampleQueues) { - embeddedSampleQueue.skipToKeyframeBefore(positionUs); + embeddedSampleQueue.skipToKeyframeBefore(positionUs, true); } } else { // We failed, and need to restart. @@ -252,7 +252,7 @@ public class ChunkSampleStream implements SampleStream, S @Override public void skipToKeyframeBefore(long timeUs) { - primarySampleQueue.skipToKeyframeBefore(timeUs); + primarySampleQueue.skipToKeyframeBefore(timeUs, true); } // Loader.Callback implementation. @@ -449,7 +449,7 @@ public class ChunkSampleStream implements SampleStream, S @Override public void skipToKeyframeBefore(long timeUs) { - sampleQueue.skipToKeyframeBefore(timeUs); + sampleQueue.skipToKeyframeBefore(timeUs, true); } @Override diff --git a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsSampleStreamWrapper.java b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsSampleStreamWrapper.java index 8bd966f177..f2cf7d2ddb 100644 --- a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsSampleStreamWrapper.java +++ b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsSampleStreamWrapper.java @@ -313,7 +313,7 @@ import java.util.LinkedList; } /* package */ void skipToKeyframeBefore(int group, long timeUs) { - sampleQueues.valueAt(group).skipToKeyframeBefore(timeUs); + sampleQueues.valueAt(group).skipToKeyframeBefore(timeUs, true); } private boolean finishedReadingChunk(HlsMediaChunk chunk) { From 1888bd291b21778b389e90ab701bc1e5f048cd2b Mon Sep 17 00:00:00 2001 From: eguven Date: Mon, 27 Mar 2017 09:42:37 -0700 Subject: [PATCH 042/119] Make FakeDataSource FakeDataSet and FakeData structures accessible publicly for comparision in tests ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=151331819 --- .../exoplayer2/testutil/FakeDataSource.java | 37 ++++++++++++++++--- 1 file changed, 31 insertions(+), 6 deletions(-) diff --git a/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeDataSource.java b/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeDataSource.java index a7b849f233..3e4d6b0440 100644 --- a/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeDataSource.java +++ b/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeDataSource.java @@ -21,6 +21,7 @@ import com.google.android.exoplayer2.upstream.DataSource; import com.google.android.exoplayer2.upstream.DataSourceException; import com.google.android.exoplayer2.upstream.DataSpec; import com.google.android.exoplayer2.util.Assertions; +import java.io.ByteArrayOutputStream; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; @@ -236,11 +237,14 @@ public final class FakeDataSource implements DataSource { /** Container of fake data to be served by a {@link FakeDataSource}. */ public static final class FakeData { + /** Uri of the data or null if this is the default FakeData. */ + public final String uri; private final ArrayList segments; private final FakeDataSet dataSet; private boolean simulateUnknownLength; - public FakeData(FakeDataSet dataSet) { + private FakeData(FakeDataSet dataSet, String uri) { + this.uri = uri; this.segments = new ArrayList<>(); this.dataSet = dataSet; } @@ -277,32 +281,46 @@ public final class FakeDataSource implements DataSource { segments.add(new Segment(null, exception)); return this; } + + /** Returns the whole data added by {@link #appendReadData(byte[])}. */ + public byte[] getData() { + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + for (Segment segment : segments) { + if (segment.data != null) { + try { + outputStream.write(segment.data); + } catch (IOException e) { + throw new IllegalStateException(e); + } + } + } + return outputStream.toByteArray(); + } } /** A set of {@link FakeData} instances. */ public static final class FakeDataSet { - private FakeData defaultData; private final HashMap dataMap; + private FakeData defaultData; public FakeDataSet() { dataMap = new HashMap<>(); } public FakeData newDefaultData() { - defaultData = new FakeData(this); + defaultData = new FakeData(this, null); return defaultData; } public FakeData newData(String uri) { - FakeData data = new FakeData(this); + FakeData data = new FakeData(this, uri); dataMap.put(uri, data); return data; } public FakeDataSet setData(String uri, byte[] data) { - newData(uri).appendReadData(data); - return this; + return newData(uri).appendReadData(data).endData(); } public FakeData getData(String uri) { @@ -310,6 +328,13 @@ public final class FakeDataSource implements DataSource { return data != null ? data : defaultData; } + public ArrayList getAllData() { + ArrayList fakeDatas = new ArrayList<>(dataMap.values()); + if (defaultData != null) { + fakeDatas.add(defaultData); + } + return fakeDatas; + } } } From 21eecb679d396370ec41c5acf1d47d07c6a1a459 Mon Sep 17 00:00:00 2001 From: olly Date: Mon, 27 Mar 2017 11:44:32 -0700 Subject: [PATCH 043/119] Make ID3 parsing more robust - Validate frames for majorVersion 2 and 3 as well as 4 - Don't throw if top bit of frameSize is non-zero. Issue: #2604 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=151348836 --- .../exoplayer2/metadata/id3/Id3Decoder.java | 84 +++++++++++++------ 1 file changed, 57 insertions(+), 27 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/metadata/id3/Id3Decoder.java b/library/core/src/main/java/com/google/android/exoplayer2/metadata/id3/Id3Decoder.java index cbe6c65030..4faebd510a 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/metadata/id3/Id3Decoder.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/metadata/id3/Id3Decoder.java @@ -64,6 +64,15 @@ public final class Id3Decoder implements MetadataDecoder { */ public static final int ID3_HEADER_LENGTH = 10; + private static final int FRAME_FLAG_V3_IS_COMPRESSED = 0x0080; + private static final int FRAME_FLAG_V3_IS_ENCRYPTED = 0x0040; + private static final int FRAME_FLAG_V3_HAS_GROUP_IDENTIFIER = 0x0020; + private static final int FRAME_FLAG_V4_IS_COMPRESSED = 0x0008; + private static final int FRAME_FLAG_V4_IS_ENCRYPTED = 0x0004; + private static final int FRAME_FLAG_V4_HAS_GROUP_IDENTIFIER = 0x0040; + private static final int FRAME_FLAG_V4_IS_UNSYNCHRONIZED = 0x0002; + private static final int FRAME_FLAG_V4_HAS_DATA_LENGTH = 0x0001; + private static final int ID3_TEXT_ENCODING_ISO_8859_1 = 0; private static final int ID3_TEXT_ENCODING_UTF_16 = 1; private static final int ID3_TEXT_ENCODING_UTF_16BE = 2; @@ -105,6 +114,7 @@ public final class Id3Decoder implements MetadataDecoder { } int startPosition = id3Data.getPosition(); + int frameHeaderSize = id3Header.majorVersion == 2 ? 6 : 10; int framesSize = id3Header.framesSize; if (id3Header.isUnsynchronized) { framesSize = removeUnsynchronization(id3Data, id3Header.framesSize); @@ -112,18 +122,15 @@ public final class Id3Decoder implements MetadataDecoder { id3Data.setLimit(startPosition + framesSize); boolean unsignedIntFrameSizeHack = false; - if (id3Header.majorVersion == 4) { - if (!validateV4Frames(id3Data, false)) { - if (validateV4Frames(id3Data, true)) { - unsignedIntFrameSizeHack = true; - } else { - Log.w(TAG, "Failed to validate V4 ID3 tag"); - return null; - } + if (!validateFrames(id3Data, id3Header.majorVersion, frameHeaderSize, false)) { + if (id3Header.majorVersion == 4 && validateFrames(id3Data, 4, frameHeaderSize, true)) { + unsignedIntFrameSizeHack = true; + } else { + Log.w(TAG, "Failed to validate ID3 tag with majorVersion=" + id3Header.majorVersion); + return null; } } - int frameHeaderSize = id3Header.majorVersion == 2 ? 6 : 10; while (id3Data.bytesLeft() >= frameHeaderSize) { Id3Frame frame = decodeFrame(id3Header.majorVersion, id3Data, unsignedIntFrameSizeHack, frameHeaderSize, framePredicate); @@ -190,18 +197,30 @@ public final class Id3Decoder implements MetadataDecoder { return new Id3Header(majorVersion, isUnsynchronized, framesSize); } - private static boolean validateV4Frames(ParsableByteArray id3Data, - boolean unsignedIntFrameSizeHack) { + private static boolean validateFrames(ParsableByteArray id3Data, int majorVersion, + int frameHeaderSize, boolean unsignedIntFrameSizeHack) { int startPosition = id3Data.getPosition(); try { - while (id3Data.bytesLeft() >= 10) { - int id = id3Data.readInt(); - int frameSize = id3Data.readUnsignedIntToInt(); - int flags = id3Data.readUnsignedShort(); + while (id3Data.bytesLeft() >= frameHeaderSize) { + // Read the next frame header. + int id; + long frameSize; + int flags; + if (majorVersion >= 3) { + id = id3Data.readInt(); + frameSize = id3Data.readUnsignedInt(); + flags = id3Data.readUnsignedShort(); + } else { + id = id3Data.readUnsignedInt24(); + frameSize = id3Data.readUnsignedInt24(); + flags = 0; + } + // Validate the frame header and skip to the next one. if (id == 0 && frameSize == 0 && flags == 0) { + // We've reached zero padding after the end of the final frame. return true; } else { - if (!unsignedIntFrameSizeHack) { + if (majorVersion == 4 && !unsignedIntFrameSizeHack) { // Parse the data size as a synchsafe integer, as per the spec. if ((frameSize & 0x808080L) != 0) { return false; @@ -209,11 +228,21 @@ public final class Id3Decoder implements MetadataDecoder { frameSize = (frameSize & 0xFF) | (((frameSize >> 8) & 0xFF) << 7) | (((frameSize >> 16) & 0xFF) << 14) | (((frameSize >> 24) & 0xFF) << 21); } + boolean hasGroupIdentifier = false; + boolean hasDataLength = false; + if (majorVersion == 4) { + hasGroupIdentifier = (flags & FRAME_FLAG_V4_HAS_GROUP_IDENTIFIER) != 0; + hasDataLength = (flags & FRAME_FLAG_V4_HAS_DATA_LENGTH) != 0; + } else if (majorVersion == 3) { + hasGroupIdentifier = (flags & FRAME_FLAG_V3_HAS_GROUP_IDENTIFIER) != 0; + // A V3 frame has data length if and only if it's compressed. + hasDataLength = (flags & FRAME_FLAG_V3_IS_COMPRESSED) != 0; + } int minimumFrameSize = 0; - if ((flags & 0x0040) != 0 /* hasGroupIdentifier */) { + if (hasGroupIdentifier) { minimumFrameSize++; } - if ((flags & 0x0001) != 0 /* hasDataLength */) { + if (hasDataLength) { minimumFrameSize += 4; } if (frameSize < minimumFrameSize) { @@ -222,7 +251,7 @@ public final class Id3Decoder implements MetadataDecoder { if (id3Data.bytesLeft() < frameSize) { return false; } - id3Data.skipBytes(frameSize); // flags + id3Data.skipBytes((int) frameSize); // flags } } return true; @@ -280,16 +309,17 @@ public final class Id3Decoder implements MetadataDecoder { boolean hasDataLength = false; boolean hasGroupIdentifier = false; if (majorVersion == 3) { - isCompressed = (flags & 0x0080) != 0; - isEncrypted = (flags & 0x0040) != 0; - hasGroupIdentifier = (flags & 0x0020) != 0; + isCompressed = (flags & FRAME_FLAG_V3_IS_COMPRESSED) != 0; + isEncrypted = (flags & FRAME_FLAG_V3_IS_ENCRYPTED) != 0; + hasGroupIdentifier = (flags & FRAME_FLAG_V3_HAS_GROUP_IDENTIFIER) != 0; + // A V3 frame has data length if and only if it's compressed. hasDataLength = isCompressed; } else if (majorVersion == 4) { - hasGroupIdentifier = (flags & 0x0040) != 0; - isCompressed = (flags & 0x0008) != 0; - isEncrypted = (flags & 0x0004) != 0; - isUnsynchronized = (flags & 0x0002) != 0; - hasDataLength = (flags & 0x0001) != 0; + hasGroupIdentifier = (flags & FRAME_FLAG_V4_HAS_GROUP_IDENTIFIER) != 0; + isCompressed = (flags & FRAME_FLAG_V4_IS_COMPRESSED) != 0; + isEncrypted = (flags & FRAME_FLAG_V4_IS_ENCRYPTED) != 0; + isUnsynchronized = (flags & FRAME_FLAG_V4_IS_UNSYNCHRONIZED) != 0; + hasDataLength = (flags & FRAME_FLAG_V4_HAS_DATA_LENGTH) != 0; } if (isCompressed || isEncrypted) { From 422c2d0ab7f5edb90aa2dba29df5a2d3fc71dd6f Mon Sep 17 00:00:00 2001 From: andrewlewis Date: Tue, 28 Mar 2017 00:09:10 -0700 Subject: [PATCH 044/119] Support escaped AAC AOTs. Issue: #2514 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=151418949 --- .../util/CodecSpecificDataUtil.java | 56 +++++++++++++------ 1 file changed, 39 insertions(+), 17 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/util/CodecSpecificDataUtil.java b/library/core/src/main/java/com/google/android/exoplayer2/util/CodecSpecificDataUtil.java index f6f3e2a400..0093c3b826 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/util/CodecSpecificDataUtil.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/util/CodecSpecificDataUtil.java @@ -75,6 +75,8 @@ public final class CodecSpecificDataUtil { private static final int AUDIO_OBJECT_TYPE_ER_BSAC = 22; // Parametric Stereo. private static final int AUDIO_OBJECT_TYPE_PS = 29; + // Escape code for extended audio object types. + private static final int AUDIO_OBJECT_TYPE_ESCAPE = 31; private CodecSpecificDataUtil() {} @@ -86,15 +88,8 @@ public final class CodecSpecificDataUtil { */ public static Pair parseAacAudioSpecificConfig(byte[] audioSpecificConfig) { ParsableBitArray bitArray = new ParsableBitArray(audioSpecificConfig); - int audioObjectType = bitArray.readBits(5); - int frequencyIndex = bitArray.readBits(4); - int sampleRate; - if (frequencyIndex == AUDIO_SPECIFIC_CONFIG_FREQUENCY_INDEX_ARBITRARY) { - sampleRate = bitArray.readBits(24); - } else { - Assertions.checkArgument(frequencyIndex < 13); - sampleRate = AUDIO_SPECIFIC_CONFIG_SAMPLING_RATE_TABLE[frequencyIndex]; - } + int audioObjectType = getAacAudioObjectType(bitArray); + int sampleRate = getAacSamplingFrequency(bitArray); int channelConfiguration = bitArray.readBits(4); if (audioObjectType == AUDIO_OBJECT_TYPE_SBR || audioObjectType == AUDIO_OBJECT_TYPE_PS) { // For an AAC bitstream using spectral band replication (SBR) or parametric stereo (PS) with @@ -102,14 +97,8 @@ public final class CodecSpecificDataUtil { // content; this is identical to the sample rate of the decoded output but may differ from // the sample rate set above. // Use the extensionSamplingFrequencyIndex. - frequencyIndex = bitArray.readBits(4); - if (frequencyIndex == AUDIO_SPECIFIC_CONFIG_FREQUENCY_INDEX_ARBITRARY) { - sampleRate = bitArray.readBits(24); - } else { - Assertions.checkArgument(frequencyIndex < 13); - sampleRate = AUDIO_SPECIFIC_CONFIG_SAMPLING_RATE_TABLE[frequencyIndex]; - } - audioObjectType = bitArray.readBits(5); + sampleRate = getAacSamplingFrequency(bitArray); + audioObjectType = getAacAudioObjectType(bitArray); if (audioObjectType == AUDIO_OBJECT_TYPE_ER_BSAC) { // Use the extensionChannelConfiguration. channelConfiguration = bitArray.readBits(4); @@ -247,4 +236,37 @@ public final class CodecSpecificDataUtil { return true; } + /** + * Returns the AAC audio object type as specified in 14496-3 (2005) Table 1.14. + * + * @param bitArray The bit array containing the audio specific configuration. + * @return The audio object type. + */ + private static int getAacAudioObjectType(ParsableBitArray bitArray) { + int audioObjectType = bitArray.readBits(5); + if (audioObjectType == AUDIO_OBJECT_TYPE_ESCAPE) { + audioObjectType = 32 + bitArray.readBits(6); + } + return audioObjectType; + } + + /** + * Returns the AAC sampling frequency (or extension sampling frequency) as specified in 14496-3 + * (2005) Table 1.13. + * + * @param bitArray The bit array containing the audio specific configuration. + * @return The sampling frequency. + */ + private static int getAacSamplingFrequency(ParsableBitArray bitArray) { + int samplingFrequency; + int frequencyIndex = bitArray.readBits(4); + if (frequencyIndex == AUDIO_SPECIFIC_CONFIG_FREQUENCY_INDEX_ARBITRARY) { + samplingFrequency = bitArray.readBits(24); + } else { + Assertions.checkArgument(frequencyIndex < 13); + samplingFrequency = AUDIO_SPECIFIC_CONFIG_SAMPLING_RATE_TABLE[frequencyIndex]; + } + return samplingFrequency; + } + } From 1dcfae452a51ba84d50649d9717d0bb4d75039b4 Mon Sep 17 00:00:00 2001 From: andrewlewis Date: Tue, 28 Mar 2017 03:27:32 -0700 Subject: [PATCH 045/119] Avoid input/output copies in SonicAudioProcessor. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=151431376 --- .../android/exoplayer2/audio/Sonic.java | 65 +++++++------------ .../exoplayer2/audio/SonicAudioProcessor.java | 54 ++++++++------- 2 files changed, 48 insertions(+), 71 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/audio/Sonic.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/Sonic.java index 47acccd967..5d6f01b6e0 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/audio/Sonic.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/audio/Sonic.java @@ -17,6 +17,7 @@ package com.google.android.exoplayer2.audio; import com.google.android.exoplayer2.util.Assertions; +import java.nio.ShortBuffer; import java.util.Arrays; /** @@ -112,60 +113,39 @@ import java.util.Arrays; } /** - * Writes {@code numBytes} from {@code buffer} as input. + * Queues remaining data from {@code buffer}, and advances its position by the number of bytes + * consumed. * - * @param buffer A buffer containing input data. - * @param numBytes The number of bytes of input data to read from {@code buffer}. + * @param buffer A {@link ShortBuffer} containing input data between its position and limit. */ - public void writeBytesToStream(byte[] buffer, int numBytes) { - int numSamples = numBytes / (2 * numChannels); - short sample; - - enlargeInputBufferIfNeeded(numSamples); - int xBuffer = numInputSamples * numChannels; - for (int xByte = 0; xByte + 1 < numBytes; xByte += 2) { - sample = (short) ((buffer[xByte] & 0xff) | (buffer[xByte + 1] << 8)); - inputBuffer[xBuffer++] = sample; - } - numInputSamples += numSamples; + public void queueInput(ShortBuffer buffer) { + int samplesToWrite = buffer.remaining() / numChannels; + int bytesToWrite = samplesToWrite * numChannels * 2; + enlargeInputBufferIfNeeded(samplesToWrite); + buffer.get(inputBuffer, numInputSamples * numChannels, bytesToWrite / 2); + numInputSamples += samplesToWrite; processStreamInput(); } /** - * Reads up to {@code maxBytes} of output into {@code buffer}. + * Gets available output, outputting to the start of {@code buffer}. The buffer's position will be + * advanced by the number of bytes written. * - * @param buffer The buffer into which output will be written. - * @param maxBytes The maximum number of bytes to write. - * @return The number of bytes read from the stream. + * @param buffer A {@link ShortBuffer} into which output will be written. */ - public int readBytesFromStream(byte[] buffer, int maxBytes) { - int maxSamples = maxBytes / (2 * numChannels); - int numSamples = numOutputSamples; - int remainingSamples = 0; - - if (numSamples == 0 || maxSamples == 0) { - return 0; - } - if (numSamples > maxSamples) { - remainingSamples = numSamples - maxSamples; - numSamples = maxSamples; - } - for (int xSample = 0; xSample < numSamples * numChannels; xSample++) { - short sample = outputBuffer[xSample]; - buffer[xSample << 1] = (byte) (sample & 0xff); - buffer[(xSample << 1) + 1] = (byte) (sample >> 8); - } - System.arraycopy(outputBuffer, numSamples * numChannels, outputBuffer, 0, - remainingSamples * numChannels); - numOutputSamples = remainingSamples; - return 2 * numSamples * numChannels; + public void getOutput(ShortBuffer buffer) { + int samplesToRead = Math.min(buffer.remaining() / numChannels, numOutputSamples); + buffer.put(outputBuffer, 0, samplesToRead * numChannels); + numOutputSamples -= samplesToRead; + System.arraycopy(outputBuffer, samplesToRead * numChannels, outputBuffer, 0, + numOutputSamples * numChannels); } /** * Forces generating output using whatever data has been queued already. No extra delay will be * added to the output, but flushing in the middle of words could introduce distortion. */ - public void flushStream() { + public void queueEndOfStream() { int remainingSamples = numInputSamples; float s = speed / pitch; int expectedOutputSamples = @@ -189,10 +169,9 @@ import java.util.Arrays; } /** - * Returns the number of output samples that can be read with - * {@link #readBytesFromStream(byte[], int)}. + * Returns the number of output samples that can be read with {@link #getOutput(ShortBuffer)}. */ - public int samplesAvailable() { + public int getSamplesAvailable() { return numOutputSamples; } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/audio/SonicAudioProcessor.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/SonicAudioProcessor.java index ae1fae40c2..3f45afd53e 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/audio/SonicAudioProcessor.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/audio/SonicAudioProcessor.java @@ -21,6 +21,7 @@ import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.util.Util; import java.nio.ByteBuffer; import java.nio.ByteOrder; +import java.nio.ShortBuffer; /** * An {@link AudioProcessor} that uses the Sonic library to modify the speed/pitch of audio. @@ -50,8 +51,6 @@ import java.nio.ByteOrder; */ private static final float CLOSE_THRESHOLD = 0.01f; - private static final byte[] EMPTY_ARRAY = new byte[0]; - private int channelCount; private int sampleRateHz; @@ -59,9 +58,9 @@ import java.nio.ByteOrder; private float speed; private float pitch; - private byte[] inputArray; + private ShortBuffer shortBuffer; + private ByteBuffer buffer; - private byte[] bufferArray; private ByteBuffer outputBuffer; private long inputBytes; private long outputBytes; @@ -76,9 +75,8 @@ import java.nio.ByteOrder; channelCount = Format.NO_VALUE; sampleRateHz = Format.NO_VALUE; buffer = EMPTY_BUFFER; + shortBuffer = buffer.asShortBuffer(); outputBuffer = EMPTY_BUFFER; - inputArray = EMPTY_ARRAY; - bufferArray = EMPTY_ARRAY; } /** @@ -142,31 +140,32 @@ import java.nio.ByteOrder; @Override public void queueInput(ByteBuffer inputBuffer) { - // TODO: Remove this extra copy. - int inputBytesToRead = inputBuffer.remaining(); - if (inputArray == null || inputArray.length < inputBytesToRead) { - inputArray = new byte[inputBytesToRead]; + if (inputBuffer.hasRemaining()) { + ShortBuffer shortBuffer = inputBuffer.asShortBuffer(); + int inputSize = inputBuffer.remaining(); + inputBytes += inputSize; + sonic.queueInput(shortBuffer); + inputBuffer.position(inputBuffer.position() + inputSize); } - inputBuffer.get(inputArray, 0, inputBytesToRead); - sonic.writeBytesToStream(inputArray, inputBytesToRead); - int outputSize = sonic.samplesAvailable() * channelCount * 2; - if (buffer.capacity() < outputSize) { - buffer = ByteBuffer.allocateDirect(outputSize).order(ByteOrder.nativeOrder()); - bufferArray = new byte[outputSize]; - } else { - buffer.clear(); + int outputSize = sonic.getSamplesAvailable() * channelCount * 2; + if (outputSize > 0) { + if (buffer.capacity() < outputSize) { + buffer = ByteBuffer.allocateDirect(outputSize).order(ByteOrder.nativeOrder()); + shortBuffer = buffer.asShortBuffer(); + } else { + buffer.clear(); + shortBuffer.clear(); + } + sonic.getOutput(shortBuffer); + outputBytes += outputSize; + buffer.limit(outputSize); + outputBuffer = buffer; } - inputBytes += inputBytesToRead; - int outputBytesRead = sonic.readBytesFromStream(bufferArray, outputSize); - buffer.put(bufferArray, 0, outputBytesRead); - buffer.flip(); - outputBytes += outputSize; - outputBuffer = buffer; } @Override public void queueEndOfStream() { - sonic.flushStream(); + sonic.queueEndOfStream(); inputEnded = true; } @@ -179,7 +178,7 @@ import java.nio.ByteOrder; @Override public boolean isEnded() { - return inputEnded && (sonic == null || sonic.samplesAvailable() == 0); + return inputEnded && (sonic == null || sonic.getSamplesAvailable() == 0); } @Override @@ -198,8 +197,7 @@ import java.nio.ByteOrder; sonic = null; buffer = EMPTY_BUFFER; outputBuffer = EMPTY_BUFFER; - inputArray = EMPTY_ARRAY; - bufferArray = EMPTY_ARRAY; + shortBuffer = null; } } From e7a4c28ddeb6649745823a385cd0ee7cb208f19c Mon Sep 17 00:00:00 2001 From: olly Date: Tue, 28 Mar 2017 05:55:12 -0700 Subject: [PATCH 046/119] Fix missing NonNull annotations for overriding methods If the super method has the annotation on an argument, then the overriding method should have it too. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=151440313 --- .../com/google/android/exoplayer2/demo/PlayerActivity.java | 5 +++-- .../com/google/android/exoplayer2/drm/FrameworkMediaDrm.java | 4 +++- .../google/android/exoplayer2/text/SubtitleInputBuffer.java | 3 ++- .../com/google/android/exoplayer2/text/cea/Cea708Cue.java | 3 ++- .../android/exoplayer2/text/webvtt/WebvttCueParser.java | 3 ++- .../android/exoplayer2/upstream/DataSourceInputStream.java | 5 +++-- .../google/android/exoplayer2/upstream/cache/CacheSpan.java | 3 ++- .../exoplayer2/upstream/cache/CachedRegionTracker.java | 3 ++- .../java/com/google/android/exoplayer2/util/AtomicFile.java | 5 +++-- .../main/java/com/google/android/exoplayer2/util/Util.java | 3 ++- .../android/exoplayer2/video/MediaCodecVideoRenderer.java | 3 ++- .../exoplayer2/source/dash/manifest/RepresentationKey.java | 3 ++- .../exoplayer2/source/hls/playlist/HlsMediaPlaylist.java | 3 ++- 13 files changed, 30 insertions(+), 16 deletions(-) diff --git a/demo/src/main/java/com/google/android/exoplayer2/demo/PlayerActivity.java b/demo/src/main/java/com/google/android/exoplayer2/demo/PlayerActivity.java index 15433af92d..e79e557827 100644 --- a/demo/src/main/java/com/google/android/exoplayer2/demo/PlayerActivity.java +++ b/demo/src/main/java/com/google/android/exoplayer2/demo/PlayerActivity.java @@ -21,6 +21,7 @@ import android.content.pm.PackageManager; import android.net.Uri; import android.os.Bundle; import android.os.Handler; +import android.support.annotation.NonNull; import android.text.TextUtils; import android.view.KeyEvent; import android.view.View; @@ -184,8 +185,8 @@ public class PlayerActivity extends Activity implements OnClickListener, ExoPlay } @Override - public void onRequestPermissionsResult(int requestCode, String[] permissions, - int[] grantResults) { + public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, + @NonNull int[] grantResults) { if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { initializePlayer(); } else { diff --git a/library/core/src/main/java/com/google/android/exoplayer2/drm/FrameworkMediaDrm.java b/library/core/src/main/java/com/google/android/exoplayer2/drm/FrameworkMediaDrm.java index 1339b90852..e6887af6da 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/drm/FrameworkMediaDrm.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/drm/FrameworkMediaDrm.java @@ -23,6 +23,7 @@ import android.media.MediaDrm; import android.media.NotProvisionedException; import android.media.ResourceBusyException; import android.media.UnsupportedSchemeException; +import android.support.annotation.NonNull; import com.google.android.exoplayer2.util.Assertions; import java.util.HashMap; import java.util.Map; @@ -62,7 +63,8 @@ public final class FrameworkMediaDrm implements ExoMediaDrm listener) { mediaDrm.setOnEventListener(listener == null ? null : new MediaDrm.OnEventListener() { @Override - public void onEvent(MediaDrm md, byte[] sessionId, int event, int extra, byte[] data) { + public void onEvent(@NonNull MediaDrm md, byte[] sessionId, int event, int extra, + byte[] data) { listener.onEvent(FrameworkMediaDrm.this, sessionId, event, extra, data); } }); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/text/SubtitleInputBuffer.java b/library/core/src/main/java/com/google/android/exoplayer2/text/SubtitleInputBuffer.java index 8bb9e790eb..28e67e8623 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/text/SubtitleInputBuffer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/text/SubtitleInputBuffer.java @@ -15,6 +15,7 @@ */ package com.google.android.exoplayer2.text; +import android.support.annotation.NonNull; import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.decoder.DecoderInputBuffer; @@ -35,7 +36,7 @@ public final class SubtitleInputBuffer extends DecoderInputBuffer } @Override - public int compareTo(SubtitleInputBuffer other) { + public int compareTo(@NonNull SubtitleInputBuffer other) { long delta = timeUs - other.timeUs; if (delta == 0) { return 0; diff --git a/library/core/src/main/java/com/google/android/exoplayer2/text/cea/Cea708Cue.java b/library/core/src/main/java/com/google/android/exoplayer2/text/cea/Cea708Cue.java index e63d1d4118..0a3f36fa87 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/text/cea/Cea708Cue.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/text/cea/Cea708Cue.java @@ -15,6 +15,7 @@ */ package com.google.android.exoplayer2.text.cea; +import android.support.annotation.NonNull; import android.text.Layout.Alignment; import com.google.android.exoplayer2.text.Cue; @@ -55,7 +56,7 @@ import com.google.android.exoplayer2.text.Cue; } @Override - public int compareTo(Cea708Cue other) { + public int compareTo(@NonNull Cea708Cue other) { if (other.priority < priority) { return -1; } else if (other.priority > priority) { diff --git a/library/core/src/main/java/com/google/android/exoplayer2/text/webvtt/WebvttCueParser.java b/library/core/src/main/java/com/google/android/exoplayer2/text/webvtt/WebvttCueParser.java index 932d4a6bed..30c9c8737e 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/text/webvtt/WebvttCueParser.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/text/webvtt/WebvttCueParser.java @@ -16,6 +16,7 @@ package com.google.android.exoplayer2.text.webvtt; import android.graphics.Typeface; +import android.support.annotation.NonNull; import android.text.Layout.Alignment; import android.text.Spannable; import android.text.SpannableStringBuilder; @@ -476,7 +477,7 @@ import java.util.regex.Pattern; } @Override - public int compareTo(StyleMatch another) { + public int compareTo(@NonNull StyleMatch another) { return this.score - another.score; } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/upstream/DataSourceInputStream.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/DataSourceInputStream.java index c4296bd6f6..2f2075f354 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/upstream/DataSourceInputStream.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/upstream/DataSourceInputStream.java @@ -15,6 +15,7 @@ */ package com.google.android.exoplayer2.upstream; +import android.support.annotation.NonNull; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.util.Assertions; import java.io.IOException; @@ -71,12 +72,12 @@ public final class DataSourceInputStream extends InputStream { } @Override - public int read(byte[] buffer) throws IOException { + public int read(@NonNull byte[] buffer) throws IOException { return read(buffer, 0, buffer.length); } @Override - public int read(byte[] buffer, int offset, int length) throws IOException { + public int read(@NonNull byte[] buffer, int offset, int length) throws IOException { Assertions.checkState(!closed); checkOpened(); int bytesRead = dataSource.read(buffer, offset, length); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheSpan.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheSpan.java index fb96c0fb0e..97d55c5fe2 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheSpan.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheSpan.java @@ -15,6 +15,7 @@ */ package com.google.android.exoplayer2.upstream.cache; +import android.support.annotation.NonNull; import com.google.android.exoplayer2.C; import java.io.File; @@ -95,7 +96,7 @@ public class CacheSpan implements Comparable { } @Override - public int compareTo(CacheSpan another) { + public int compareTo(@NonNull CacheSpan another) { if (!key.equals(another.key)) { return key.compareTo(another.key); } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CachedRegionTracker.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CachedRegionTracker.java index 0f08ca40f2..9559054f6d 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CachedRegionTracker.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CachedRegionTracker.java @@ -15,6 +15,7 @@ */ package com.google.android.exoplayer2.upstream.cache; +import android.support.annotation.NonNull; import android.util.Log; import com.google.android.exoplayer2.extractor.ChunkIndex; import java.util.Arrays; @@ -195,7 +196,7 @@ public final class CachedRegionTracker implements Cache.Listener { } @Override - public int compareTo(Region another) { + public int compareTo(@NonNull Region another) { return startOffset < another.startOffset ? -1 : startOffset == another.startOffset ? 0 : 1; } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/util/AtomicFile.java b/library/core/src/main/java/com/google/android/exoplayer2/util/AtomicFile.java index c383c01453..f2e30d981b 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/util/AtomicFile.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/util/AtomicFile.java @@ -16,6 +16,7 @@ package com.google.android.exoplayer2.util; +import android.support.annotation.NonNull; import android.util.Log; import java.io.File; import java.io.FileInputStream; @@ -185,12 +186,12 @@ public final class AtomicFile { } @Override - public void write(byte[] b) throws IOException { + public void write(@NonNull byte[] b) throws IOException { fileOutputStream.write(b); } @Override - public void write(byte[] b, int off, int len) throws IOException { + public void write(@NonNull byte[] b, int off, int len) throws IOException { fileOutputStream.write(b, off, len); } } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/util/Util.java b/library/core/src/main/java/com/google/android/exoplayer2/util/Util.java index 8fa89dea28..73ea18e2ca 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/util/Util.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/util/Util.java @@ -25,6 +25,7 @@ import android.content.pm.PackageManager.NameNotFoundException; import android.graphics.Point; import android.net.Uri; import android.os.Build; +import android.support.annotation.NonNull; import android.text.TextUtils; import android.util.Log; import android.view.Display; @@ -199,7 +200,7 @@ public final class Util { public static ExecutorService newSingleThreadExecutor(final String threadName) { return Executors.newSingleThreadExecutor(new ThreadFactory() { @Override - public Thread newThread(Runnable r) { + public Thread newThread(@NonNull Runnable r) { return new Thread(r, threadName); } }); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java b/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java index 059628e0c8..9c7f436bdc 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java @@ -25,6 +25,7 @@ import android.media.MediaCrypto; import android.media.MediaFormat; import android.os.Handler; import android.os.SystemClock; +import android.support.annotation.NonNull; import android.util.Log; import android.view.Surface; import com.google.android.exoplayer2.C; @@ -823,7 +824,7 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { } @Override - public void onFrameRendered(MediaCodec codec, long presentationTimeUs, long nanoTime) { + public void onFrameRendered(@NonNull MediaCodec codec, long presentationTimeUs, long nanoTime) { if (this != tunnelingOnFrameRenderedListener) { // Stale event. return; diff --git a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/RepresentationKey.java b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/RepresentationKey.java index 51451a83c2..cf17a081d7 100644 --- a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/RepresentationKey.java +++ b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/RepresentationKey.java @@ -17,6 +17,7 @@ package com.google.android.exoplayer2.source.dash.manifest; import android.os.Parcel; import android.os.Parcelable; +import android.support.annotation.NonNull; /** * Uniquely identifies a {@link Representation} in a {@link DashManifest}. @@ -68,7 +69,7 @@ public final class RepresentationKey implements Parcelable, Comparable relativeStartTimeUs ? 1 : (this.relativeStartTimeUs < relativeStartTimeUs ? -1 : 0); } From 2f9e082fe13226af043330b22fa2f9ae1fc3ac63 Mon Sep 17 00:00:00 2001 From: eguven Date: Tue, 28 Mar 2017 07:47:17 -0700 Subject: [PATCH 047/119] Enable gradle test code coverage for the library modules that aren't affected by 'stuck in endless loop' issue 'library-core' is still affected by https://code.google.com/p/android/issues/detail?id=226070 Code coverage report can be generated for the rest of the modules by: ./gradlew :[module name]:createDebugCoverageReport Report is generated under: [module folder]/buildout/reports/coverage/debug ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=151448536 --- library/core/build.gradle | 12 ++++++------ library/dash/build.gradle | 6 ++++++ library/hls/build.gradle | 6 ++++++ library/smoothstreaming/build.gradle | 6 ++++++ library/ui/build.gradle | 6 ++++++ 5 files changed, 30 insertions(+), 6 deletions(-) diff --git a/library/core/build.gradle b/library/core/build.gradle index d2ce499235..046d80af0e 100644 --- a/library/core/build.gradle +++ b/library/core/build.gradle @@ -23,6 +23,12 @@ android { consumerProguardFiles 'proguard-rules.txt' } + sourceSets { + androidTest { + java.srcDirs += "../../testutils/src/main/java/" + } + } + buildTypes { // Re-enable test coverage when the following issue is fixed: // https://code.google.com/p/android/issues/detail?id=226070 @@ -30,12 +36,6 @@ android { // testCoverageEnabled = true // } } - - sourceSets { - androidTest { - java.srcDirs += "../../testutils/src/main/java/" - } - } } dependencies { diff --git a/library/dash/build.gradle b/library/dash/build.gradle index 93f75216db..a9a5550219 100644 --- a/library/dash/build.gradle +++ b/library/dash/build.gradle @@ -27,6 +27,12 @@ android { java.srcDirs += "../../testutils/src/main/java/" } } + + buildTypes { + debug { + testCoverageEnabled = true + } + } } dependencies { diff --git a/library/hls/build.gradle b/library/hls/build.gradle index a35142870c..e7375fbc8e 100644 --- a/library/hls/build.gradle +++ b/library/hls/build.gradle @@ -21,6 +21,12 @@ android { minSdkVersion project.ext.minSdkVersion targetSdkVersion project.ext.targetSdkVersion } + + buildTypes { + debug { + testCoverageEnabled = true + } + } } dependencies { diff --git a/library/smoothstreaming/build.gradle b/library/smoothstreaming/build.gradle index c9196c1792..1f76f1043d 100644 --- a/library/smoothstreaming/build.gradle +++ b/library/smoothstreaming/build.gradle @@ -27,6 +27,12 @@ android { java.srcDirs += "../../testutils/src/main/java/" } } + + buildTypes { + debug { + testCoverageEnabled = true + } + } } dependencies { diff --git a/library/ui/build.gradle b/library/ui/build.gradle index 6defb42078..9e1e0ba85a 100644 --- a/library/ui/build.gradle +++ b/library/ui/build.gradle @@ -21,6 +21,12 @@ android { minSdkVersion project.ext.minSdkVersion targetSdkVersion project.ext.targetSdkVersion } + + buildTypes { + debug { + testCoverageEnabled = true + } + } } dependencies { From 103c3b631b1e8cba0b6d9d5fc959c73c5f66de22 Mon Sep 17 00:00:00 2001 From: eguven Date: Tue, 28 Mar 2017 09:06:21 -0700 Subject: [PATCH 048/119] Add DashDownloader helper class to download dash streams ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=151456161 --- .../java/com/google/android/exoplayer2/C.java | 9 + .../upstream/cache/CacheDataSource.java | 11 +- .../exoplayer2/util/PriorityTaskManager.java | 2 +- .../dash/offline/DashDownloaderTest.java | 420 ++++++++++++++++++ .../exoplayer2/source/dash/DashUtil.java | 6 +- .../source/dash/DashWrappingSegmentIndex.java | 2 +- .../{DashTest.java => DashStreamingTest.java} | 6 +- .../playbacktests/gts/DashTestData.java | 2 +- 8 files changed, 448 insertions(+), 10 deletions(-) create mode 100644 library/dash/src/androidTest/java/com/google/android/exoplayer2/source/dash/offline/DashDownloaderTest.java rename playbacktests/src/androidTest/java/com/google/android/exoplayer2/playbacktests/gts/{DashTest.java => DashStreamingTest.java} (99%) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/C.java b/library/core/src/main/java/com/google/android/exoplayer2/C.java index 29f8220037..02e5939b86 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/C.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/C.java @@ -541,9 +541,18 @@ public final class C { /** * Priority for media playback. + * + *

    Larger values indicate higher priorities. */ public static final int PRIORITY_PLAYBACK = 0; + /** + * Priority for media downloading. + * + *

    Larger values indicate higher priorities. + */ + public static final int PRIORITY_DOWNLOAD = PRIORITY_PLAYBACK - 1000; + /** * Converts a time in microseconds to the corresponding time in milliseconds, preserving * {@link #TIME_UNSET} values. diff --git a/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheDataSource.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheDataSource.java index a11f1956ea..26c77b5dd1 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheDataSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheDataSource.java @@ -108,6 +108,15 @@ public final class CacheDataSource implements DataSource { private boolean currentRequestIgnoresCache; private long totalCachedBytesRead; + /** + * Generates a cache key out of the given {@link Uri}. + * + * @param uri Uri of a content which the requested key is for. + */ + public static String generateKey(Uri uri) { + return uri.toString(); + } + /** * Constructs an instance with default {@link DataSource} and {@link DataSink} instances for * reading and writing the cache and with {@link #DEFAULT_MAX_CACHE_FILE_SIZE}. @@ -171,7 +180,7 @@ public final class CacheDataSource implements DataSource { try { uri = dataSpec.uri; flags = dataSpec.flags; - key = dataSpec.key != null ? dataSpec.key : uri.toString(); + key = dataSpec.key != null ? dataSpec.key : generateKey(dataSpec.uri); readPosition = dataSpec.position; currentRequestIgnoresCache = (ignoreCacheOnError && seenCacheError) || (dataSpec.length == C.LENGTH_UNSET && ignoreCacheForUnsetLengthRequests); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/util/PriorityTaskManager.java b/library/core/src/main/java/com/google/android/exoplayer2/util/PriorityTaskManager.java index fb61d3ba4a..2516b538c6 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/util/PriorityTaskManager.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/util/PriorityTaskManager.java @@ -54,7 +54,7 @@ public final class PriorityTaskManager { /** * Register a new task. The task must call {@link #remove(int)} when done. * - * @param priority The priority of the task. + * @param priority The priority of the task. Larger values indicate higher priorities. */ public void add(int priority) { synchronized (lock) { diff --git a/library/dash/src/androidTest/java/com/google/android/exoplayer2/source/dash/offline/DashDownloaderTest.java b/library/dash/src/androidTest/java/com/google/android/exoplayer2/source/dash/offline/DashDownloaderTest.java new file mode 100644 index 0000000000..c2578c196f --- /dev/null +++ b/library/dash/src/androidTest/java/com/google/android/exoplayer2/source/dash/offline/DashDownloaderTest.java @@ -0,0 +1,420 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.exoplayer2.source.dash.offline; + +import android.net.Uri; +import android.test.InstrumentationTestCase; +import android.test.MoreAsserts; +import com.google.android.exoplayer2.C; +import com.google.android.exoplayer2.source.dash.manifest.DashManifest; +import com.google.android.exoplayer2.source.dash.manifest.RepresentationKey; +import com.google.android.exoplayer2.source.dash.offline.DashDownloader.ProgressListener; +import com.google.android.exoplayer2.testutil.FakeDataSource; +import com.google.android.exoplayer2.testutil.FakeDataSource.FakeData; +import com.google.android.exoplayer2.testutil.FakeDataSource.FakeDataSet; +import com.google.android.exoplayer2.testutil.TestUtil; +import com.google.android.exoplayer2.upstream.DataSourceInputStream; +import com.google.android.exoplayer2.upstream.DataSpec; +import com.google.android.exoplayer2.upstream.DummyDataSource; +import com.google.android.exoplayer2.upstream.cache.CacheDataSource; +import com.google.android.exoplayer2.upstream.cache.NoOpCacheEvictor; +import com.google.android.exoplayer2.upstream.cache.SimpleCache; +import com.google.android.exoplayer2.util.ClosedSource; +import com.google.android.exoplayer2.util.Util; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.util.Arrays; + +/** + * Unit tests for {@link DashDownloader}. + */ +@ClosedSource(reason = "Not ready yet") +public class DashDownloaderTest extends InstrumentationTestCase { + + private static final byte[] TEST_MPD = + ("\n" + + "\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + // Bounded range data + + " \n" + // Unbounded range data + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + "").getBytes(); + + private static final byte[] TEST_MPD_NO_INDEX = + ("\n" + + "\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + "").getBytes(); + + private File tempFolder; + private SimpleCache cache; + + @Override + public void setUp() throws Exception { + super.setUp(); + tempFolder = Util.createTempDirectory(getInstrumentation().getContext(), "ExoPlayerTest"); + cache = new SimpleCache(tempFolder, new NoOpCacheEvictor()); + } + + @Override + public void tearDown() throws Exception { + Util.recursiveDelete(tempFolder); + } + + public void testDownloadManifest() throws Exception { + FakeDataSet fakeDataSet = new FakeDataSet() + .setData("test.mpd", TEST_MPD); + DashDownloader dashDownloader = + new DashDownloader("test.mpd", cache, new FakeDataSource(fakeDataSet)); + + DashManifest manifest = dashDownloader.downloadManifest(); + + assertNotNull(manifest); + assertCachedData(fakeDataSet); + } + + public void testDownloadManifestFailure() throws Exception { + byte[] testMpdFirstPart = Arrays.copyOf(TEST_MPD, 10); + byte[] testMpdSecondPart = Arrays.copyOfRange(TEST_MPD, 10, TEST_MPD.length); + FakeDataSet fakeDataSet = new FakeDataSet() + .newData("test.mpd") + .appendReadData(testMpdFirstPart) + .appendReadError(new IOException()) + .appendReadData(testMpdSecondPart) + .endData(); + DashDownloader dashDownloader = + new DashDownloader("test.mpd", cache, new FakeDataSource(fakeDataSet)); + + // downloadManifest fails on the first try + try { + dashDownloader.downloadManifest(); + fail(); + } catch (IOException e) { + // ignore + } + assertCachedData("test.mpd", testMpdFirstPart); + + // on the second try it downloads the rest of the data + DashManifest manifest = dashDownloader.downloadManifest(); + + assertNotNull(manifest); + assertCachedData(fakeDataSet); + } + + public void testDownloadRepresentation() throws Exception { + FakeDataSet fakeDataSet = new FakeDataSet() + .setData("test.mpd", TEST_MPD) + .setData("audio_init_data", TestUtil.buildTestData(10)) + .setData("audio_segment_1", TestUtil.buildTestData(4)) + .setData("audio_segment_2", TestUtil.buildTestData(5)) + .setData("audio_segment_3", TestUtil.buildTestData(6)); + DashDownloader dashDownloader = + new DashDownloader("test.mpd", cache, new FakeDataSource(fakeDataSet)); + dashDownloader.downloadManifest(); + + dashDownloader.selectRepresentations(new RepresentationKey(0, 0, 0)); + dashDownloader.downloadRepresentations(null); + + assertCachedData(fakeDataSet); + } + + public void testDownloadRepresentationInSmallParts() throws Exception { + FakeDataSet fakeDataSet = new FakeDataSet() + .setData("test.mpd", TEST_MPD) + .setData("audio_init_data", TestUtil.buildTestData(10)) + .newData("audio_segment_1") + .appendReadData(TestUtil.buildTestData(10)) + .appendReadData(TestUtil.buildTestData(10)) + .appendReadData(TestUtil.buildTestData(10)) + .endData() + .setData("audio_segment_2", TestUtil.buildTestData(5)) + .setData("audio_segment_3", TestUtil.buildTestData(6)); + DashDownloader dashDownloader = + new DashDownloader("test.mpd", cache, new FakeDataSource(fakeDataSet)); + dashDownloader.downloadManifest(); + + dashDownloader.selectRepresentations(new RepresentationKey(0, 0, 0)); + dashDownloader.downloadRepresentations(null); + + assertCachedData(fakeDataSet); + } + + public void testDownloadRepresentations() throws Exception { + FakeDataSet fakeDataSet = new FakeDataSet() + .setData("test.mpd", TEST_MPD) + .setData("audio_init_data", TestUtil.buildTestData(10)) + .setData("audio_segment_1", TestUtil.buildTestData(4)) + .setData("audio_segment_2", TestUtil.buildTestData(5)) + .setData("audio_segment_3", TestUtil.buildTestData(6)) + .setData("text_segment_1", TestUtil.buildTestData(1)) + .setData("text_segment_2", TestUtil.buildTestData(2)) + .setData("text_segment_3", TestUtil.buildTestData(3)); + DashDownloader dashDownloader = + new DashDownloader("test.mpd", cache, new FakeDataSource(fakeDataSet)); + dashDownloader.downloadManifest(); + + dashDownloader.selectRepresentations( + new RepresentationKey(0, 0, 0), + new RepresentationKey(0, 1, 0)); + dashDownloader.downloadRepresentations(null); + + assertCachedData(fakeDataSet); + } + + public void testDownloadRepresentationFailure() throws Exception { + FakeDataSet fakeDataSet = new FakeDataSet() + .setData("test.mpd", TEST_MPD) + .setData("audio_init_data", TestUtil.buildTestData(10)) + .setData("audio_segment_1", TestUtil.buildTestData(4)) + .newData("audio_segment_2") + .appendReadData(TestUtil.buildTestData(2)) + .appendReadError(new IOException()) + .appendReadData(TestUtil.buildTestData(3)) + .endData() + .setData("audio_segment_3", TestUtil.buildTestData(6)); + DashDownloader dashDownloader = + new DashDownloader("test.mpd", cache, new FakeDataSource(fakeDataSet)); + dashDownloader.downloadManifest(); + + dashDownloader.selectRepresentations(new RepresentationKey(0, 0, 0)); + // downloadRepresentations fails on the first try + try { + dashDownloader.downloadRepresentations(null); + fail(); + } catch (IOException e) { + // ignore + } + dashDownloader.downloadRepresentations(null); + + assertCachedData(fakeDataSet); + } + + public void testCounters() throws Exception { + FakeDataSet fakeDataSet = new FakeDataSet() + .setData("test.mpd", TEST_MPD) + .setData("audio_init_data", TestUtil.buildTestData(10)) + .setData("audio_segment_1", TestUtil.buildTestData(4)) + .newData("audio_segment_2") + .appendReadData(TestUtil.buildTestData(2)) + .appendReadError(new IOException()) + .appendReadData(TestUtil.buildTestData(3)) + .endData() + .setData("audio_segment_3", TestUtil.buildTestData(6)); + DashDownloader dashDownloader = + new DashDownloader("test.mpd", cache, new FakeDataSource(fakeDataSet)); + dashDownloader.downloadManifest(); + + assertCounters(dashDownloader, C.LENGTH_UNSET, C.LENGTH_UNSET, C.LENGTH_UNSET); + + dashDownloader.selectRepresentations(new RepresentationKey(0, 0, 0)); + dashDownloader.initStatus(); + assertCounters(dashDownloader, 3, 0, 0); + + // downloadRepresentations fails after downloading init data, segment 1 and 2 bytes in segment 2 + try { + dashDownloader.downloadRepresentations(null); + fail(); + } catch (IOException e) { + // ignore + } + dashDownloader.initStatus(); + assertCounters(dashDownloader, 3, 1, 10 + 4 + 2); + + dashDownloader.downloadRepresentations(null); + + assertCounters(dashDownloader, 3, 3, 10 + 4 + 5 + 6); + } + + public void testListener() throws Exception { + FakeDataSet fakeDataSet = new FakeDataSet() + .setData("test.mpd", TEST_MPD) + .setData("audio_init_data", TestUtil.buildTestData(10)) + .setData("audio_segment_1", TestUtil.buildTestData(4)) + .setData("audio_segment_2", TestUtil.buildTestData(5)) + .setData("audio_segment_3", TestUtil.buildTestData(6)); + DashDownloader dashDownloader = + new DashDownloader("test.mpd", cache, new FakeDataSource(fakeDataSet)); + dashDownloader.downloadManifest(); + + dashDownloader.selectRepresentations(new RepresentationKey(0, 0, 0)); + dashDownloader.downloadRepresentations(new ProgressListener() { + private int counter = 0; + @Override + public void onDownloadProgress(DashDownloader dashDownloader, int totalSegments, + int downloadedSegments, + long downloadedBytes) { + switch (counter++) { + case 0: + assertTrue(totalSegments == 3 && downloadedSegments == 0 && downloadedBytes == 10); + break; + case 1: + assertTrue(totalSegments == 3 && downloadedSegments == 1 && downloadedBytes == 14); + break; + case 2: + assertTrue(totalSegments == 3 && downloadedSegments == 2 && downloadedBytes == 19); + break; + case 3: + assertTrue(totalSegments == 3 && downloadedSegments == 3 && downloadedBytes == 25); + break; + default: + fail(); + } + } + }); + } + + public void testRemoveAll() throws Exception { + FakeDataSet fakeDataSet = new FakeDataSet() + .setData("test.mpd", TEST_MPD) + .setData("audio_init_data", TestUtil.buildTestData(10)) + .setData("audio_segment_1", TestUtil.buildTestData(4)) + .setData("audio_segment_2", TestUtil.buildTestData(5)) + .setData("audio_segment_3", TestUtil.buildTestData(6)) + .setData("text_segment_1", TestUtil.buildTestData(1)) + .setData("text_segment_2", TestUtil.buildTestData(2)) + .setData("text_segment_3", TestUtil.buildTestData(3)); + DashDownloader dashDownloader = + new DashDownloader("test.mpd", cache, new FakeDataSource(fakeDataSet)); + dashDownloader.downloadManifest(); + dashDownloader.selectRepresentations( + new RepresentationKey(0, 0, 0), + new RepresentationKey(0, 1, 0)); + dashDownloader.downloadRepresentations(null); + + dashDownloader.removeAll(); + + assertEquals(0, cache.getCacheSpace()); + } + + public void testRemoveRepresentations() throws Exception { + FakeDataSet fakeDataSet = new FakeDataSet() + .setData("test.mpd", TEST_MPD) + .setData("audio_init_data", TestUtil.buildTestData(10)) + .setData("audio_segment_1", TestUtil.buildTestData(4)) + .setData("audio_segment_2", TestUtil.buildTestData(5)) + .setData("audio_segment_3", TestUtil.buildTestData(6)) + .setData("text_segment_1", TestUtil.buildTestData(1)) + .setData("text_segment_2", TestUtil.buildTestData(2)) + .setData("text_segment_3", TestUtil.buildTestData(3)); + DashDownloader dashDownloader = + new DashDownloader("test.mpd", cache, new FakeDataSource(fakeDataSet)); + dashDownloader.downloadManifest(); + dashDownloader.selectRepresentations( + new RepresentationKey(0, 0, 0), + new RepresentationKey(0, 1, 0)); + dashDownloader.downloadRepresentations(null); + + dashDownloader.removeRepresentations(); + + assertEquals(TEST_MPD.length, cache.getCacheSpace()); + assertCachedData("test.mpd", TEST_MPD); + } + + public void testMpdNoIndex() throws Exception { + FakeDataSet fakeDataSet = new FakeDataSet() + .setData("test.mpd", TEST_MPD_NO_INDEX) + .setData("test_segment_1", TestUtil.buildTestData(4)); + DashDownloader dashDownloader = + new DashDownloader("test.mpd", cache, new FakeDataSource(fakeDataSet)); + dashDownloader.downloadManifest(); + + dashDownloader.selectRepresentations(new RepresentationKey(0, 0, 0)); + dashDownloader.initStatus(); + try { + dashDownloader.downloadRepresentations(null); + fail(); + } catch (DashDownloaderException e) { + // expected interrupt. + } + dashDownloader.removeAll(); + + assertEquals(0, cache.getCacheSpace()); + } + + private void assertCachedData(FakeDataSet fakeDataSet) throws IOException { + int totalLength = 0; + for (FakeData fakeData : fakeDataSet.getAllData()) { + byte[] data = fakeData.getData(); + assertCachedData(fakeData.uri, data); + totalLength += data.length; + } + assertEquals(totalLength, cache.getCacheSpace()); + } + + private void assertCachedData(String uriString, byte[] expected) throws IOException { + CacheDataSource dataSource = new CacheDataSource(cache, DummyDataSource.INSTANCE, 0); + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + DataSourceInputStream inputStream = new DataSourceInputStream(dataSource, + new DataSpec(Uri.parse(uriString), DataSpec.FLAG_ALLOW_CACHING_UNKNOWN_LENGTH)); + try { + inputStream.open(); + byte[] buffer = new byte[1024]; + int bytesRead; + while ((bytesRead = inputStream.read(buffer)) != -1) { + outputStream.write(buffer, 0, bytesRead); + } + } catch (IOException e) { + // Ignore + } finally { + inputStream.close(); + } + MoreAsserts.assertEquals(expected, outputStream.toByteArray()); + } + + private static void assertCounters(DashDownloader dashDownloader, int totalSegments, + int downloadedSegments, int downloadedBytes) { + assertEquals(totalSegments, dashDownloader.getTotalSegments()); + assertEquals(downloadedSegments, dashDownloader.getDownloadedSegments()); + assertEquals(downloadedBytes, dashDownloader.getDownloadedBytes()); + } + +} diff --git a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashUtil.java b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashUtil.java index 3ec44c2f69..2febeb8c81 100644 --- a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashUtil.java +++ b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashUtil.java @@ -48,14 +48,14 @@ public final class DashUtil { * Loads a DASH manifest. * * @param dataSource The {@link HttpDataSource} from which the manifest should be read. - * @param manifestUriString The URI of the manifest to be read. + * @param manifestUri The URI of the manifest to be read. * @return An instance of {@link DashManifest}. * @throws IOException Thrown when there is an error while loading. */ - public static DashManifest loadManifest(DataSource dataSource, String manifestUriString) + public static DashManifest loadManifest(DataSource dataSource, String manifestUri) throws IOException { DataSourceInputStream inputStream = new DataSourceInputStream(dataSource, - new DataSpec(Uri.parse(manifestUriString), DataSpec.FLAG_ALLOW_CACHING_UNKNOWN_LENGTH)); + new DataSpec(Uri.parse(manifestUri), DataSpec.FLAG_ALLOW_CACHING_UNKNOWN_LENGTH)); try { inputStream.open(); DashManifestParser parser = new DashManifestParser(); diff --git a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashWrappingSegmentIndex.java b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashWrappingSegmentIndex.java index 40f3448f6a..8cd5018dc7 100644 --- a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashWrappingSegmentIndex.java +++ b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashWrappingSegmentIndex.java @@ -22,7 +22,7 @@ import com.google.android.exoplayer2.source.dash.manifest.RangedUri; * An implementation of {@link DashSegmentIndex} that wraps a {@link ChunkIndex} parsed from a * media stream. */ -/* package */ final class DashWrappingSegmentIndex implements DashSegmentIndex { +public final class DashWrappingSegmentIndex implements DashSegmentIndex { private final ChunkIndex chunkIndex; diff --git a/playbacktests/src/androidTest/java/com/google/android/exoplayer2/playbacktests/gts/DashTest.java b/playbacktests/src/androidTest/java/com/google/android/exoplayer2/playbacktests/gts/DashStreamingTest.java similarity index 99% rename from playbacktests/src/androidTest/java/com/google/android/exoplayer2/playbacktests/gts/DashTest.java rename to playbacktests/src/androidTest/java/com/google/android/exoplayer2/playbacktests/gts/DashStreamingTest.java index fc0701da8d..e7441362cf 100644 --- a/playbacktests/src/androidTest/java/com/google/android/exoplayer2/playbacktests/gts/DashTest.java +++ b/playbacktests/src/androidTest/java/com/google/android/exoplayer2/playbacktests/gts/DashStreamingTest.java @@ -28,9 +28,9 @@ import com.google.android.exoplayer2.util.Util; /** * Tests DASH playbacks using {@link ExoPlayer}. */ -public final class DashTest extends ActivityInstrumentationTestCase2 { +public final class DashStreamingTest extends ActivityInstrumentationTestCase2 { - private static final String TAG = "DashTest"; + private static final String TAG = "DashStreamingTest"; private static final ActionSchedule SEEKING_SCHEDULE = new ActionSchedule.Builder(TAG) .delay(10000).seek(15000) @@ -72,7 +72,7 @@ public final class DashTest extends ActivityInstrumentationTestCase2 Date: Wed, 29 Mar 2017 02:41:19 -0700 Subject: [PATCH 049/119] Downgrade jacoco to 0.7.4.201502262128 as a workaround for 'stuck in endless loop' issue ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=151554130 --- build.gradle | 9 ++++++++- library/core/build.gradle | 8 +++----- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/build.gradle b/build.gradle index 6d5a069d3e..63a5f5eaaa 100644 --- a/build.gradle +++ b/build.gradle @@ -19,8 +19,15 @@ buildscript { classpath 'com.android.tools.build:gradle:2.3.0' classpath 'com.novoda:bintray-release:0.4.0' } + // Workaround for the following test coverage issue. Remove when fixed: + // https://code.google.com/p/android/issues/detail?id=226070 + configurations.all { + resolutionStrategy { + force 'org.jacoco:org.jacoco.report:0.7.4.201502262128' + force 'org.jacoco:org.jacoco.core:0.7.4.201502262128' + } + } } - allprojects { repositories { jcenter() diff --git a/library/core/build.gradle b/library/core/build.gradle index 046d80af0e..49ed791a78 100644 --- a/library/core/build.gradle +++ b/library/core/build.gradle @@ -30,11 +30,9 @@ android { } buildTypes { - // Re-enable test coverage when the following issue is fixed: - // https://code.google.com/p/android/issues/detail?id=226070 - // debug { - // testCoverageEnabled = true - // } + debug { + testCoverageEnabled = true + } } } From 9bc20d23a89c073c9d1052fd382b6bbb5a419c8f Mon Sep 17 00:00:00 2001 From: olly Date: Wed, 29 Mar 2017 05:08:02 -0700 Subject: [PATCH 050/119] Print when frame is rendered in EventLogger + tidying This is the boring part of a larger change that fixes how video renderers behave when surfaces are attached and detached whilst they're enabled or started. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=151563031 --- .../android/exoplayer2/demo/EventLogger.java | 2 +- .../ext/vp9/LibvpxVideoRenderer.java | 36 +++++++++---------- .../video/MediaCodecVideoRenderer.java | 32 ++++++++--------- 3 files changed, 35 insertions(+), 35 deletions(-) diff --git a/demo/src/main/java/com/google/android/exoplayer2/demo/EventLogger.java b/demo/src/main/java/com/google/android/exoplayer2/demo/EventLogger.java index 79e0bcd693..953021fe6f 100644 --- a/demo/src/main/java/com/google/android/exoplayer2/demo/EventLogger.java +++ b/demo/src/main/java/com/google/android/exoplayer2/demo/EventLogger.java @@ -281,7 +281,7 @@ import java.util.Locale; @Override public void onRenderedFirstFrame(Surface surface) { - // Do nothing. + Log.d(TAG, "renderedFirstFrame [" + surface + "]"); } // DefaultDrmSessionManager.EventListener diff --git a/extensions/vp9/src/main/java/com/google/android/exoplayer2/ext/vp9/LibvpxVideoRenderer.java b/extensions/vp9/src/main/java/com/google/android/exoplayer2/ext/vp9/LibvpxVideoRenderer.java index d0417bc37e..d00b9a19a7 100644 --- a/extensions/vp9/src/main/java/com/google/android/exoplayer2/ext/vp9/LibvpxVideoRenderer.java +++ b/extensions/vp9/src/main/java/com/google/android/exoplayer2/ext/vp9/LibvpxVideoRenderer.java @@ -87,8 +87,8 @@ public final class LibvpxVideoRenderer extends BaseRenderer { private boolean inputStreamEnded; private boolean outputStreamEnded; - private int lastReportedWidth; - private int lastReportedHeight; + private int reportedWidth; + private int reportedHeight; private long droppedFrameAccumulationStartTimeMs; private int droppedFrames; @@ -147,8 +147,8 @@ public final class LibvpxVideoRenderer extends BaseRenderer { this.maxDroppedFramesToNotify = maxDroppedFramesToNotify; this.drmSessionManager = drmSessionManager; this.playClearSamplesWithoutKeys = playClearSamplesWithoutKeys; - joiningDeadlineMs = -1; - clearLastReportedVideoSize(); + joiningDeadlineMs = C.TIME_UNSET; + clearReportedVideoSize(); formatHolder = new FormatHolder(); flagsOnlyBuffer = new DecoderInputBuffer(DecoderInputBuffer.BUFFER_REPLACEMENT_MODE_DISABLED); eventDispatcher = new EventDispatcher(eventHandler, eventListener); @@ -259,7 +259,7 @@ public final class LibvpxVideoRenderer extends BaseRenderer { // Drop the frame if we're joining and are more than 30ms late, or if we have the next frame // and that's also late. Else we'll render what we have. - if ((joiningDeadlineMs != -1 && outputBuffer.timeUs < positionUs - 30000) + if ((joiningDeadlineMs != C.TIME_UNSET && outputBuffer.timeUs < positionUs - 30000) || (nextOutputBuffer != null && !nextOutputBuffer.isEndOfStream() && nextOutputBuffer.timeUs < positionUs)) { decoderCounters.droppedOutputBufferCount++; @@ -408,9 +408,9 @@ public final class LibvpxVideoRenderer extends BaseRenderer { if (format != null && (isSourceReady() || outputBuffer != null) && (renderedFirstFrame || !isRendererAvailable())) { // Ready. If we were joining then we've now joined, so clear the joining deadline. - joiningDeadlineMs = -1; + joiningDeadlineMs = C.TIME_UNSET; return true; - } else if (joiningDeadlineMs == -1) { + } else if (joiningDeadlineMs == C.TIME_UNSET) { // Not joining. return false; } else if (SystemClock.elapsedRealtime() < joiningDeadlineMs) { @@ -418,7 +418,7 @@ public final class LibvpxVideoRenderer extends BaseRenderer { return true; } else { // The joining deadline has been exceeded. Give up and clear the deadline. - joiningDeadlineMs = -1; + joiningDeadlineMs = C.TIME_UNSET; return false; } } @@ -439,18 +439,18 @@ public final class LibvpxVideoRenderer extends BaseRenderer { flushDecoder(); } joiningDeadlineMs = joining && allowedJoiningTimeMs > 0 - ? (SystemClock.elapsedRealtime() + allowedJoiningTimeMs) : -1; + ? (SystemClock.elapsedRealtime() + allowedJoiningTimeMs) : C.TIME_UNSET; } @Override protected void onStarted() { droppedFrames = 0; droppedFrameAccumulationStartTimeMs = SystemClock.elapsedRealtime(); + joiningDeadlineMs = C.TIME_UNSET; } @Override protected void onStopped() { - joiningDeadlineMs = -1; maybeNotifyDroppedFrames(); } @@ -460,7 +460,7 @@ public final class LibvpxVideoRenderer extends BaseRenderer { outputBuffer = null; format = null; waitingForKeys = false; - clearLastReportedVideoSize(); + clearReportedVideoSize(); try { releaseDecoder(); } finally { @@ -540,7 +540,7 @@ public final class LibvpxVideoRenderer extends BaseRenderer { // Clear state so that we always call the event listener with the video size and when a frame // is rendered, even if the output hasn't changed. renderedFirstFrame = false; - clearLastReportedVideoSize(); + clearReportedVideoSize(); // We only need to update the decoder if the output has changed. if (this.surface != surface || this.outputBufferRenderer != outputBufferRenderer) { this.surface = surface; @@ -565,15 +565,15 @@ public final class LibvpxVideoRenderer extends BaseRenderer { return surface != null || outputBufferRenderer != null; } - private void clearLastReportedVideoSize() { - lastReportedWidth = Format.NO_VALUE; - lastReportedHeight = Format.NO_VALUE; + private void clearReportedVideoSize() { + reportedWidth = Format.NO_VALUE; + reportedHeight = Format.NO_VALUE; } private void maybeNotifyVideoSizeChanged(int width, int height) { - if (lastReportedWidth != width || lastReportedHeight != height) { - lastReportedWidth = width; - lastReportedHeight = height; + if (reportedWidth != width || reportedHeight != height) { + reportedWidth = width; + reportedHeight = height; eventDispatcher.videoSizeChanged(width, height, 0, 1); } } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java b/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java index 9c7f436bdc..a4985f066b 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java @@ -86,10 +86,10 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { private int currentHeight; private int currentUnappliedRotationDegrees; private float currentPixelWidthHeightRatio; - private int lastReportedWidth; - private int lastReportedHeight; - private int lastReportedUnappliedRotationDegrees; - private float lastReportedPixelWidthHeightRatio; + private int reportedWidth; + private int reportedHeight; + private int reportedUnappliedRotationDegrees; + private float reportedPixelWidthHeightRatio; private boolean tunneling; private int tunnelingAudioSessionId; @@ -257,11 +257,11 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { super.onStarted(); droppedFrames = 0; droppedFrameAccumulationStartTimeMs = SystemClock.elapsedRealtime(); + joiningDeadlineMs = C.TIME_UNSET; } @Override protected void onStopped() { - joiningDeadlineMs = C.TIME_UNSET; maybeNotifyDroppedFrames(); super.onStopped(); } @@ -544,22 +544,22 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { } private void clearLastReportedVideoSize() { - lastReportedWidth = Format.NO_VALUE; - lastReportedHeight = Format.NO_VALUE; - lastReportedPixelWidthHeightRatio = Format.NO_VALUE; - lastReportedUnappliedRotationDegrees = Format.NO_VALUE; + reportedWidth = Format.NO_VALUE; + reportedHeight = Format.NO_VALUE; + reportedPixelWidthHeightRatio = Format.NO_VALUE; + reportedUnappliedRotationDegrees = Format.NO_VALUE; } private void maybeNotifyVideoSizeChanged() { - if (lastReportedWidth != currentWidth || lastReportedHeight != currentHeight - || lastReportedUnappliedRotationDegrees != currentUnappliedRotationDegrees - || lastReportedPixelWidthHeightRatio != currentPixelWidthHeightRatio) { + if (reportedWidth != currentWidth || reportedHeight != currentHeight + || reportedUnappliedRotationDegrees != currentUnappliedRotationDegrees + || reportedPixelWidthHeightRatio != currentPixelWidthHeightRatio) { eventDispatcher.videoSizeChanged(currentWidth, currentHeight, currentUnappliedRotationDegrees, currentPixelWidthHeightRatio); - lastReportedWidth = currentWidth; - lastReportedHeight = currentHeight; - lastReportedUnappliedRotationDegrees = currentUnappliedRotationDegrees; - lastReportedPixelWidthHeightRatio = currentPixelWidthHeightRatio; + reportedWidth = currentWidth; + reportedHeight = currentHeight; + reportedUnappliedRotationDegrees = currentUnappliedRotationDegrees; + reportedPixelWidthHeightRatio = currentPixelWidthHeightRatio; } } From 035fab225da7570f320d0fb7f93b46532c072485 Mon Sep 17 00:00:00 2001 From: olly Date: Wed, 29 Mar 2017 12:11:57 -0700 Subject: [PATCH 051/119] Rename skipToKeyframeBefore -> skipData and allow skipping to EOS This change also ensures that format changes are read whilst the renderer is enabled but without a codec. This is necessary to ensure the drm session is updated (or replaced). Updating the format is also needed so that the up-to-date format is used in the case that the codec is initialized later due to the surface being set. Previously, if an ABR change occurred between the format being read and the surface being attached, we would instantiate the codec and then immediately have to reconfigure it when as a result of reading the up-to-date format. For a non-adaptive codec this resulted in the codec being immediately released and instantiated again! Issue: #2582 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=151608096 --- .../ext/vp9/LibvpxVideoRenderer.java | 18 ++++++- .../android/exoplayer2/ExoPlayerTest.java | 2 +- .../android/exoplayer2/BaseRenderer.java | 19 +++---- .../audio/SimpleDecoderAudioRenderer.java | 2 +- .../decoder/DecoderInputBuffer.java | 17 ++++++ .../extractor/DefaultTrackOutput.java | 52 +++++++++++++++---- .../mediacodec/MediaCodecRenderer.java | 25 +++++++-- .../source/ClippingMediaPeriod.java | 4 +- .../exoplayer2/source/EmptySampleStream.java | 2 +- .../source/ExtractorMediaPeriod.java | 13 +++-- .../exoplayer2/source/SampleStream.java | 7 +-- .../source/SingleSampleMediaPeriod.java | 6 ++- .../source/chunk/ChunkSampleStream.java | 16 ++++-- .../source/hls/HlsSampleStream.java | 4 +- .../source/hls/HlsSampleStreamWrapper.java | 9 +++- 15 files changed, 149 insertions(+), 47 deletions(-) diff --git a/extensions/vp9/src/main/java/com/google/android/exoplayer2/ext/vp9/LibvpxVideoRenderer.java b/extensions/vp9/src/main/java/com/google/android/exoplayer2/ext/vp9/LibvpxVideoRenderer.java index d00b9a19a7..bf9f1f9ced 100644 --- a/extensions/vp9/src/main/java/com/google/android/exoplayer2/ext/vp9/LibvpxVideoRenderer.java +++ b/extensions/vp9/src/main/java/com/google/android/exoplayer2/ext/vp9/LibvpxVideoRenderer.java @@ -150,7 +150,7 @@ public final class LibvpxVideoRenderer extends BaseRenderer { joiningDeadlineMs = C.TIME_UNSET; clearReportedVideoSize(); formatHolder = new FormatHolder(); - flagsOnlyBuffer = new DecoderInputBuffer(DecoderInputBuffer.BUFFER_REPLACEMENT_MODE_DISABLED); + flagsOnlyBuffer = DecoderInputBuffer.newFlagsOnlyInstance(); eventDispatcher = new EventDispatcher(eventHandler, eventListener); outputMode = VpxDecoder.OUTPUT_MODE_NONE; } @@ -185,6 +185,7 @@ public final class LibvpxVideoRenderer extends BaseRenderer { } } + // We have a format. if (isRendererAvailable()) { drmSession = pendingDrmSession; ExoMediaCrypto mediaCrypto = null; @@ -222,7 +223,20 @@ public final class LibvpxVideoRenderer extends BaseRenderer { throw ExoPlaybackException.createForRenderer(e, getIndex()); } } else { - skipToKeyframeBefore(positionUs); + skipSource(positionUs); + // We need to read any format changes despite not having a codec so that drmSession can be + // updated, and so that we have the most recent format should the codec be initialized. We may + // also reach the end of the stream. Note that readSource will not read a sample into a + // flags-only buffer. + flagsOnlyBuffer.clear(); + int result = readSource(formatHolder, flagsOnlyBuffer, false); + if (result == C.RESULT_FORMAT_READ) { + onInputFormatChanged(formatHolder.format); + } else if (result == C.RESULT_BUFFER_READ) { + Assertions.checkState(flagsOnlyBuffer.isEndOfStream()); + inputStreamEnded = true; + outputStreamEnded = true; + } } decoderCounters.ensureUpdated(); } diff --git a/library/core/src/androidTest/java/com/google/android/exoplayer2/ExoPlayerTest.java b/library/core/src/androidTest/java/com/google/android/exoplayer2/ExoPlayerTest.java index 9fc55d5c77..434a0249df 100644 --- a/library/core/src/androidTest/java/com/google/android/exoplayer2/ExoPlayerTest.java +++ b/library/core/src/androidTest/java/com/google/android/exoplayer2/ExoPlayerTest.java @@ -551,7 +551,7 @@ public final class ExoPlayerTest extends TestCase { } @Override - public void skipToKeyframeBefore(long timeUs) { + public void skipData(long positionUs) { // Do nothing. } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/BaseRenderer.java b/library/core/src/main/java/com/google/android/exoplayer2/BaseRenderer.java index f6aae200dd..8324b184ca 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/BaseRenderer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/BaseRenderer.java @@ -296,6 +296,16 @@ public abstract class BaseRenderer implements Renderer, RendererCapabilities { return result; } + /** + * Attempts to skip to the keyframe before the specified position, or to the end of the stream if + * {@code positionUs} is beyond it. + * + * @param positionUs The position in microseconds. + */ + protected void skipSource(long positionUs) { + stream.skipData(positionUs - streamOffsetUs); + } + /** * Returns whether the upstream source is ready. * @@ -305,13 +315,4 @@ public abstract class BaseRenderer implements Renderer, RendererCapabilities { return readEndOfStream ? streamIsFinal : stream.isReady(); } - /** - * Attempts to skip to the keyframe before the specified time. - * - * @param timeUs The specified time. - */ - protected void skipToKeyframeBefore(long timeUs) { - stream.skipToKeyframeBefore(timeUs - streamOffsetUs); - } - } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/audio/SimpleDecoderAudioRenderer.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/SimpleDecoderAudioRenderer.java index 3a50a8244a..ddb870f6ff 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/audio/SimpleDecoderAudioRenderer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/audio/SimpleDecoderAudioRenderer.java @@ -146,7 +146,7 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements eventDispatcher = new EventDispatcher(eventHandler, eventListener); audioTrack = new AudioTrack(audioCapabilities, audioProcessors, new AudioTrackListener()); formatHolder = new FormatHolder(); - flagsOnlyBuffer = new DecoderInputBuffer(DecoderInputBuffer.BUFFER_REPLACEMENT_MODE_DISABLED); + flagsOnlyBuffer = DecoderInputBuffer.newFlagsOnlyInstance(); decoderReinitializationState = REINITIALIZATION_STATE_NONE; audioTrackNeedsConfigure = true; } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/decoder/DecoderInputBuffer.java b/library/core/src/main/java/com/google/android/exoplayer2/decoder/DecoderInputBuffer.java index 84c89de427..d22a45ce88 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/decoder/DecoderInputBuffer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/decoder/DecoderInputBuffer.java @@ -63,6 +63,15 @@ public class DecoderInputBuffer extends Buffer { @BufferReplacementMode private final int bufferReplacementMode; + /** + * Creates a new instance for which {@link #isFlagsOnly()} will return true. + * + * @return A new flags only input buffer. + */ + public static DecoderInputBuffer newFlagsOnlyInstance() { + return new DecoderInputBuffer(BUFFER_REPLACEMENT_MODE_DISABLED); + } + /** * @param bufferReplacementMode Determines the behavior of {@link #ensureSpaceForWrite(int)}. One * of {@link #BUFFER_REPLACEMENT_MODE_DISABLED}, {@link #BUFFER_REPLACEMENT_MODE_NORMAL} and @@ -109,6 +118,14 @@ public class DecoderInputBuffer extends Buffer { data = newData; } + /** + * Returns whether the buffer is only able to hold flags, meaning {@link #data} is null and + * its replacement mode is {@link #BUFFER_REPLACEMENT_MODE_DISABLED}. + */ + public final boolean isFlagsOnly() { + return data == null && bufferReplacementMode == BUFFER_REPLACEMENT_MODE_DISABLED; + } + /** * Returns whether the {@link C#BUFFER_FLAG_ENCRYPTED} flag is set. */ diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/DefaultTrackOutput.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/DefaultTrackOutput.java index d57b55bfd2..1c9a148226 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/DefaultTrackOutput.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/DefaultTrackOutput.java @@ -76,7 +76,6 @@ public final class DefaultTrackOutput implements TrackOutput { private long totalBytesWritten; private Allocation lastAllocation; private int lastAllocationOffset; - private boolean needKeyframe; private boolean pendingSplice; private UpstreamFormatChangedListener upstreamFormatChangeListener; @@ -92,7 +91,6 @@ public final class DefaultTrackOutput implements TrackOutput { scratch = new ParsableByteArray(INITIAL_SCRATCH_SIZE); state = new AtomicInteger(); lastAllocationOffset = allocationLength; - needKeyframe = true; } // Called by the consuming thread, but only when there is no loading thread. @@ -227,6 +225,16 @@ public final class DefaultTrackOutput implements TrackOutput { return infoQueue.getLargestQueuedTimestampUs(); } + /** + * Skips all samples currently in the buffer. + */ + public void skipAll() { + long nextOffset = infoQueue.skipAll(); + if (nextOffset != C.POSITION_UNSET) { + dropDownstreamTo(nextOffset); + } + } + /** * Attempts to skip to the keyframe before or at the specified time. Succeeds only if the buffer * contains a keyframe with a timestamp of {@code timeUs} or earlier. If @@ -523,12 +531,6 @@ public final class DefaultTrackOutput implements TrackOutput { } pendingSplice = false; } - if (needKeyframe) { - if ((flags & C.BUFFER_FLAG_KEY_FRAME) == 0) { - return; - } - needKeyframe = false; - } timeUs += sampleOffsetUs; long absoluteOffset = totalBytesWritten - size - offset; infoQueue.commitSample(timeUs, flags, absoluteOffset, size, encryptionKey); @@ -558,7 +560,6 @@ public final class DefaultTrackOutput implements TrackOutput { totalBytesWritten = 0; lastAllocation = null; lastAllocationOffset = allocationLength; - needKeyframe = true; } /** @@ -615,6 +616,7 @@ public final class DefaultTrackOutput implements TrackOutput { private long largestDequeuedTimestampUs; private long largestQueuedTimestampUs; + private boolean upstreamKeyframeRequired; private boolean upstreamFormatRequired; private Format upstreamFormat; private int upstreamSourceId; @@ -631,6 +633,7 @@ public final class DefaultTrackOutput implements TrackOutput { largestDequeuedTimestampUs = Long.MIN_VALUE; largestQueuedTimestampUs = Long.MIN_VALUE; upstreamFormatRequired = true; + upstreamKeyframeRequired = true; } public void clearSampleData() { @@ -638,6 +641,7 @@ public final class DefaultTrackOutput implements TrackOutput { relativeReadIndex = 0; relativeWriteIndex = 0; queueSize = 0; + upstreamKeyframeRequired = true; } // Called by the consuming thread, but only when there is no loading thread. @@ -780,6 +784,10 @@ public final class DefaultTrackOutput implements TrackOutput { return C.RESULT_FORMAT_READ; } + if (buffer.isFlagsOnly()) { + return C.RESULT_NOTHING_READ; + } + buffer.timeUs = timesUs[relativeReadIndex]; buffer.setFlags(flags[relativeReadIndex]); extrasHolder.size = sizes[relativeReadIndex]; @@ -800,6 +808,24 @@ public final class DefaultTrackOutput implements TrackOutput { return C.RESULT_BUFFER_READ; } + /** + * Skips all samples in the buffer. + * + * @return The offset up to which data should be dropped, or {@link C#POSITION_UNSET} if no + * dropping of data is required. + */ + public synchronized long skipAll() { + if (queueSize == 0) { + return C.POSITION_UNSET; + } + + int lastSampleIndex = (relativeReadIndex + queueSize - 1) % capacity; + relativeReadIndex = (relativeReadIndex + queueSize) % capacity; + absoluteReadIndex += queueSize; + queueSize = 0; + return offsets[lastSampleIndex] + sizes[lastSampleIndex]; + } + /** * Attempts to locate the keyframe before or at the specified time. If * {@code allowTimeBeyondBuffer} is {@code false} then it is also required that {@code timeUs} @@ -842,9 +868,9 @@ public final class DefaultTrackOutput implements TrackOutput { return C.POSITION_UNSET; } - queueSize -= sampleCountToKeyframe; relativeReadIndex = (relativeReadIndex + sampleCountToKeyframe) % capacity; absoluteReadIndex += sampleCountToKeyframe; + queueSize -= sampleCountToKeyframe; return offsets[relativeReadIndex]; } @@ -867,6 +893,12 @@ public final class DefaultTrackOutput implements TrackOutput { public synchronized void commitSample(long timeUs, @C.BufferFlags int sampleFlags, long offset, int size, byte[] encryptionKey) { + if (upstreamKeyframeRequired) { + if ((sampleFlags & C.BUFFER_FLAG_KEY_FRAME) == 0) { + return; + } + upstreamKeyframeRequired = false; + } Assertions.checkState(!upstreamFormatRequired); commitSampleTimestamp(timeUs); timesUs[relativeWriteIndex] = timeUs; 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 3fbbfac652..8af024f666 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 @@ -169,6 +169,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer { private final DrmSessionManager drmSessionManager; private final boolean playClearSamplesWithoutKeys; private final DecoderInputBuffer buffer; + private final DecoderInputBuffer flagsOnlyBuffer; private final FormatHolder formatHolder; private final List decodeOnlyPresentationTimestamps; private final MediaCodec.BufferInfo outputBufferInfo; @@ -227,6 +228,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer { this.drmSessionManager = drmSessionManager; this.playClearSamplesWithoutKeys = playClearSamplesWithoutKeys; buffer = new DecoderInputBuffer(DecoderInputBuffer.BUFFER_REPLACEMENT_MODE_DISABLED); + flagsOnlyBuffer = DecoderInputBuffer.newFlagsOnlyInstance(); formatHolder = new FormatHolder(); decodeOnlyPresentationTimestamps = new ArrayList<>(); outputBufferInfo = new MediaCodec.BufferInfo(); @@ -448,6 +450,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer { codecReconfigurationState = RECONFIGURATION_STATE_NONE; codecReinitializationState = REINITIALIZATION_STATE_NONE; decoderCounters.decoderReleaseCount++; + buffer.data = null; try { codec.stop(); } finally { @@ -486,12 +489,12 @@ public abstract class MediaCodecRenderer extends BaseRenderer { if (format == null) { // We don't have a format yet, so try and read one. buffer.clear(); - int result = readSource(formatHolder, buffer, true); + int result = readSource(formatHolder, flagsOnlyBuffer, true); if (result == C.RESULT_FORMAT_READ) { onInputFormatChanged(formatHolder.format); } else if (result == C.RESULT_BUFFER_READ) { // End of stream read having not read a format. - Assertions.checkState(buffer.isEndOfStream()); + Assertions.checkState(flagsOnlyBuffer.isEndOfStream()); inputStreamEnded = true; processEndOfStream(); return; @@ -500,14 +503,28 @@ public abstract class MediaCodecRenderer extends BaseRenderer { return; } } + // We have a format. maybeInitCodec(); if (codec != null) { TraceUtil.beginSection("drainAndFeed"); while (drainOutputBuffer(positionUs, elapsedRealtimeUs)) {} while (feedInputBuffer()) {} TraceUtil.endSection(); - } else if (format != null) { - skipToKeyframeBefore(positionUs); + } else { + skipSource(positionUs); + // We need to read any format changes despite not having a codec so that drmSession can be + // updated, and so that we have the most recent format should the codec be initialized. We may + // also reach the end of the stream. Note that readSource will not read a sample into a + // flags-only buffer. + flagsOnlyBuffer.clear(); + int result = readSource(formatHolder, flagsOnlyBuffer, false); + if (result == C.RESULT_FORMAT_READ) { + onInputFormatChanged(formatHolder.format); + } else if (result == C.RESULT_BUFFER_READ) { + Assertions.checkState(flagsOnlyBuffer.isEndOfStream()); + inputStreamEnded = true; + processEndOfStream(); + } } decoderCounters.ensureUpdated(); } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/ClippingMediaPeriod.java b/library/core/src/main/java/com/google/android/exoplayer2/source/ClippingMediaPeriod.java index 102a689742..e14930c7b8 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/ClippingMediaPeriod.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/ClippingMediaPeriod.java @@ -262,8 +262,8 @@ public final class ClippingMediaPeriod implements MediaPeriod, MediaPeriod.Callb } @Override - public void skipToKeyframeBefore(long timeUs) { - stream.skipToKeyframeBefore(startUs + timeUs); + public void skipData(long positionUs) { + stream.skipData(startUs + positionUs); } } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/EmptySampleStream.java b/library/core/src/main/java/com/google/android/exoplayer2/source/EmptySampleStream.java index eb94351f61..7aab22d8a0 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/EmptySampleStream.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/EmptySampleStream.java @@ -43,7 +43,7 @@ public final class EmptySampleStream implements SampleStream { } @Override - public void skipToKeyframeBefore(long timeUs) { + public void skipData(long positionUs) { // Do nothing. } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/ExtractorMediaPeriod.java b/library/core/src/main/java/com/google/android/exoplayer2/source/ExtractorMediaPeriod.java index d54e881183..f247d4dd37 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/ExtractorMediaPeriod.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/ExtractorMediaPeriod.java @@ -340,8 +340,13 @@ import java.io.IOException; loadingFinished, lastSeekPositionUs); } - /* package */ void skipToKeyframeBefore(int track, long timeUs) { - sampleQueues.valueAt(track).skipToKeyframeBefore(timeUs, true); + /* package */ void skipData(int track, long positionUs) { + DefaultTrackOutput sampleQueue = sampleQueues.valueAt(track); + if (loadingFinished && positionUs > sampleQueue.getLargestQueuedTimestampUs()) { + sampleQueue.skipAll(); + } else { + sampleQueue.skipToKeyframeBefore(positionUs, true); + } } // Loader.Callback implementation. @@ -569,8 +574,8 @@ import java.io.IOException; } @Override - public void skipToKeyframeBefore(long timeUs) { - ExtractorMediaPeriod.this.skipToKeyframeBefore(track, timeUs); + public void skipData(long positionUs) { + ExtractorMediaPeriod.this.skipData(track, positionUs); } } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/SampleStream.java b/library/core/src/main/java/com/google/android/exoplayer2/source/SampleStream.java index e3039878f8..dc58c29c22 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/SampleStream.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/SampleStream.java @@ -66,10 +66,11 @@ public interface SampleStream { int readData(FormatHolder formatHolder, DecoderInputBuffer buffer, boolean formatRequired); /** - * Attempts to skip to the keyframe before the specified time. + * Attempts to skip to the keyframe before the specified position, or to the end of the stream if + * {@code positionUs} is beyond it. * - * @param timeUs The specified time. + * @param positionUs The specified time. */ - void skipToKeyframeBefore(long timeUs); + void skipData(long positionUs); } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/SingleSampleMediaPeriod.java b/library/core/src/main/java/com/google/android/exoplayer2/source/SingleSampleMediaPeriod.java index 5b717e51da..8e38588e89 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/SingleSampleMediaPeriod.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/SingleSampleMediaPeriod.java @@ -235,8 +235,10 @@ import java.util.Arrays; } @Override - public void skipToKeyframeBefore(long timeUs) { - // Do nothing. + public void skipData(long positionUs) { + if (positionUs > 0) { + streamState = STREAM_STATE_END_OF_STREAM; + } } } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/ChunkSampleStream.java b/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/ChunkSampleStream.java index 6a62afe2fb..c43f3d577a 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/ChunkSampleStream.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/ChunkSampleStream.java @@ -251,8 +251,12 @@ public class ChunkSampleStream implements SampleStream, S } @Override - public void skipToKeyframeBefore(long timeUs) { - primarySampleQueue.skipToKeyframeBefore(timeUs, true); + public void skipData(long positionUs) { + if (loadingFinished && positionUs > primarySampleQueue.getLargestQueuedTimestampUs()) { + primarySampleQueue.skipAll(); + } else { + primarySampleQueue.skipToKeyframeBefore(positionUs, true); + } } // Loader.Callback implementation. @@ -448,8 +452,12 @@ public class ChunkSampleStream implements SampleStream, S } @Override - public void skipToKeyframeBefore(long timeUs) { - sampleQueue.skipToKeyframeBefore(timeUs, true); + public void skipData(long positionUs) { + if (loadingFinished && positionUs > sampleQueue.getLargestQueuedTimestampUs()) { + sampleQueue.skipAll(); + } else { + sampleQueue.skipToKeyframeBefore(positionUs, true); + } } @Override diff --git a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsSampleStream.java b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsSampleStream.java index d8eb7e1ae8..450644f60f 100644 --- a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsSampleStream.java +++ b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsSampleStream.java @@ -50,8 +50,8 @@ import java.io.IOException; } @Override - public void skipToKeyframeBefore(long timeUs) { - sampleStreamWrapper.skipToKeyframeBefore(group, timeUs); + public void skipData(long positionUs) { + sampleStreamWrapper.skipData(group, positionUs); } } diff --git a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsSampleStreamWrapper.java b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsSampleStreamWrapper.java index f2cf7d2ddb..827a6e885d 100644 --- a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsSampleStreamWrapper.java +++ b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsSampleStreamWrapper.java @@ -312,8 +312,13 @@ import java.util.LinkedList; loadingFinished, lastSeekPositionUs); } - /* package */ void skipToKeyframeBefore(int group, long timeUs) { - sampleQueues.valueAt(group).skipToKeyframeBefore(timeUs, true); + /* package */ void skipData(int group, long positionUs) { + DefaultTrackOutput sampleQueue = sampleQueues.valueAt(group); + if (loadingFinished && positionUs > sampleQueue.getLargestQueuedTimestampUs()) { + sampleQueue.skipAll(); + } else { + sampleQueue.skipToKeyframeBefore(positionUs, true); + } } private boolean finishedReadingChunk(HlsMediaChunk chunk) { From cc1a8f40341703db077329b2d1803401a278108c Mon Sep 17 00:00:00 2001 From: olly Date: Thu, 30 Mar 2017 06:47:35 -0700 Subject: [PATCH 052/119] Make playlist samples a little more interesting ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=151696298 --- demo/src/main/assets/media.exolist.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/demo/src/main/assets/media.exolist.json b/demo/src/main/assets/media.exolist.json index dd88f206c1..814c89a45b 100644 --- a/demo/src/main/assets/media.exolist.json +++ b/demo/src/main/assets/media.exolist.json @@ -416,13 +416,16 @@ ] }, { - "name": "Audio -> Video", + "name": "Audio -> Video -> Audio", "playlist": [ { "uri": "https://storage.googleapis.com/exoplayer-test-media-1/gen-3/screens/dash-vod-single-segment/audio-141.mp4" }, { "uri": "http://storage.googleapis.com/exoplayer-test-media-1/mkv/android-screens-lavf-56.36.100-aac-avc-main-1280x720.mkv" + }, + { + "uri": "https://storage.googleapis.com/exoplayer-test-media-1/gen-3/screens/dash-vod-single-segment/audio-141.mp4" } ] }, From a473e7300c3fb678763f5ac91f1b1d9466f3e468 Mon Sep 17 00:00:00 2001 From: eguven Date: Thu, 30 Mar 2017 08:44:19 -0700 Subject: [PATCH 053/119] Move utility methods to DemoUtil from TrackSelectionHelper ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=151706338 --- .../android/exoplayer2/demo/DemoUtil.java | 86 +++++++++++++++++++ .../exoplayer2/demo/TrackSelectionHelper.java | 59 +------------ 2 files changed, 87 insertions(+), 58 deletions(-) create mode 100644 demo/src/main/java/com/google/android/exoplayer2/demo/DemoUtil.java diff --git a/demo/src/main/java/com/google/android/exoplayer2/demo/DemoUtil.java b/demo/src/main/java/com/google/android/exoplayer2/demo/DemoUtil.java new file mode 100644 index 0000000000..f9e9c34158 --- /dev/null +++ b/demo/src/main/java/com/google/android/exoplayer2/demo/DemoUtil.java @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.exoplayer2.demo; + +import android.text.TextUtils; +import com.google.android.exoplayer2.Format; +import com.google.android.exoplayer2.util.MimeTypes; +import java.util.Locale; + +/** + * Utility methods for demo application. + */ +/*package*/ final class DemoUtil { + + /** + * Builds a track name for display. + * + * @param format {@link Format} of the track. + * @return a generated name specific to the track. + */ + public static String buildTrackName(Format format) { + String trackName; + if (MimeTypes.isVideo(format.sampleMimeType)) { + trackName = joinWithSeparator(joinWithSeparator(joinWithSeparator( + buildResolutionString(format), buildBitrateString(format)), buildTrackIdString(format)), + buildSampleMimeTypeString(format)); + } else if (MimeTypes.isAudio(format.sampleMimeType)) { + trackName = joinWithSeparator(joinWithSeparator(joinWithSeparator(joinWithSeparator( + buildLanguageString(format), buildAudioPropertyString(format)), + buildBitrateString(format)), buildTrackIdString(format)), + buildSampleMimeTypeString(format)); + } else { + trackName = joinWithSeparator(joinWithSeparator(joinWithSeparator(buildLanguageString(format), + buildBitrateString(format)), buildTrackIdString(format)), + buildSampleMimeTypeString(format)); + } + return trackName.length() == 0 ? "unknown" : trackName; + } + + private static String buildResolutionString(Format format) { + return format.width == Format.NO_VALUE || format.height == Format.NO_VALUE + ? "" : format.width + "x" + format.height; + } + + private static String buildAudioPropertyString(Format format) { + return format.channelCount == Format.NO_VALUE || format.sampleRate == Format.NO_VALUE + ? "" : format.channelCount + "ch, " + format.sampleRate + "Hz"; + } + + private static String buildLanguageString(Format format) { + return TextUtils.isEmpty(format.language) || "und".equals(format.language) ? "" + : format.language; + } + + private static String buildBitrateString(Format format) { + return format.bitrate == Format.NO_VALUE ? "" + : String.format(Locale.US, "%.2fMbit", format.bitrate / 1000000f); + } + + private static String joinWithSeparator(String first, String second) { + return first.length() == 0 ? second : (second.length() == 0 ? first : first + ", " + second); + } + + private static String buildTrackIdString(Format format) { + return format.id == null ? "" : ("id:" + format.id); + } + + private static String buildSampleMimeTypeString(Format format) { + return format.sampleMimeType == null ? "" : format.sampleMimeType; + } + + private DemoUtil() {} +} diff --git a/demo/src/main/java/com/google/android/exoplayer2/demo/TrackSelectionHelper.java b/demo/src/main/java/com/google/android/exoplayer2/demo/TrackSelectionHelper.java index 576eb23c9d..033b515767 100644 --- a/demo/src/main/java/com/google/android/exoplayer2/demo/TrackSelectionHelper.java +++ b/demo/src/main/java/com/google/android/exoplayer2/demo/TrackSelectionHelper.java @@ -21,13 +21,11 @@ import android.app.AlertDialog; import android.content.Context; import android.content.DialogInterface; import android.content.res.TypedArray; -import android.text.TextUtils; import android.util.Pair; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.CheckedTextView; -import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.RendererCapabilities; import com.google.android.exoplayer2.source.TrackGroup; import com.google.android.exoplayer2.source.TrackGroupArray; @@ -37,9 +35,7 @@ import com.google.android.exoplayer2.trackselection.MappingTrackSelector.MappedT import com.google.android.exoplayer2.trackselection.MappingTrackSelector.SelectionOverride; import com.google.android.exoplayer2.trackselection.RandomTrackSelection; import com.google.android.exoplayer2.trackselection.TrackSelection; -import com.google.android.exoplayer2.util.MimeTypes; import java.util.Arrays; -import java.util.Locale; /** * Helper class for displaying track selection dialogs. @@ -157,7 +153,7 @@ import java.util.Locale; CheckedTextView trackView = (CheckedTextView) inflater.inflate( trackViewLayoutId, root, false); trackView.setBackgroundResource(selectableItemBackgroundResourceId); - trackView.setText(buildTrackName(group.getFormat(trackIndex))); + trackView.setText(DemoUtil.buildTrackName(group.getFormat(trackIndex))); if (trackInfo.getTrackFormatSupport(rendererIndex, groupIndex, trackIndex) == RendererCapabilities.FORMAT_HANDLED) { trackView.setFocusable(true); @@ -296,57 +292,4 @@ import java.util.Locale; return tracks; } - // Track name construction. - - private static String buildTrackName(Format format) { - String trackName; - if (MimeTypes.isVideo(format.sampleMimeType)) { - trackName = joinWithSeparator(joinWithSeparator(joinWithSeparator( - buildResolutionString(format), buildBitrateString(format)), buildTrackIdString(format)), - buildSampleMimeTypeString(format)); - } else if (MimeTypes.isAudio(format.sampleMimeType)) { - trackName = joinWithSeparator(joinWithSeparator(joinWithSeparator(joinWithSeparator( - buildLanguageString(format), buildAudioPropertyString(format)), - buildBitrateString(format)), buildTrackIdString(format)), - buildSampleMimeTypeString(format)); - } else { - trackName = joinWithSeparator(joinWithSeparator(joinWithSeparator(buildLanguageString(format), - buildBitrateString(format)), buildTrackIdString(format)), - buildSampleMimeTypeString(format)); - } - return trackName.length() == 0 ? "unknown" : trackName; - } - - private static String buildResolutionString(Format format) { - return format.width == Format.NO_VALUE || format.height == Format.NO_VALUE - ? "" : format.width + "x" + format.height; - } - - private static String buildAudioPropertyString(Format format) { - return format.channelCount == Format.NO_VALUE || format.sampleRate == Format.NO_VALUE - ? "" : format.channelCount + "ch, " + format.sampleRate + "Hz"; - } - - private static String buildLanguageString(Format format) { - return TextUtils.isEmpty(format.language) || "und".equals(format.language) ? "" - : format.language; - } - - private static String buildBitrateString(Format format) { - return format.bitrate == Format.NO_VALUE ? "" - : String.format(Locale.US, "%.2fMbit", format.bitrate / 1000000f); - } - - private static String joinWithSeparator(String first, String second) { - return first.length() == 0 ? second : (second.length() == 0 ? first : first + ", " + second); - } - - private static String buildTrackIdString(Format format) { - return format.id == null ? "" : ("id:" + format.id); - } - - private static String buildSampleMimeTypeString(Format format) { - return format.sampleMimeType == null ? "" : format.sampleMimeType; - } - } From f7fff0d5835b42f6d6db7e46c7b1e546f84f545a Mon Sep 17 00:00:00 2001 From: andrewlewis Date: Fri, 31 Mar 2017 01:26:19 -0700 Subject: [PATCH 054/119] Apply parameter adjustments with resetting. Start draining the audio processors when the playback parameters change, and when draining completes use the written frame count and next input buffer presentation timestamp as an offset for applying the new playback speed. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=151800074 --- .../android/exoplayer2/audio/AudioTrack.java | 146 ++++++++++++++---- .../exoplayer2/audio/SonicAudioProcessor.java | 14 +- 2 files changed, 127 insertions(+), 33 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioTrack.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioTrack.java index d56f6a0d89..d376ffee14 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioTrack.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioTrack.java @@ -32,6 +32,7 @@ import java.lang.reflect.Method; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.util.ArrayList; +import java.util.LinkedList; /** * Plays audio data. The implementation delegates to an {@link android.media.AudioTrack} and handles @@ -170,7 +171,7 @@ public final class AudioTrack { } /** - * Returned by {@link #getCurrentPositionUs} when the position is not set. + * Returned by {@link #getCurrentPositionUs(boolean)} when the position is not set. */ public static final long CURRENT_POSITION_NOT_SET = Long.MIN_VALUE; @@ -251,6 +252,13 @@ public final class AudioTrack { private static final int MIN_PLAYHEAD_OFFSET_SAMPLE_INTERVAL_US = 30000; private static final int MIN_TIMESTAMP_SAMPLE_INTERVAL_US = 500000; + /** + * The minimum number of output bytes from {@link #sonicAudioProcessor} at which the speedup is + * calculated using the input/output byte counts from the processor, rather than using the + * current playback parameters speed. + */ + private static final int SONIC_MIN_BYTES_FOR_SPEEDUP = 1024; + /** * Whether to enable a workaround for an issue where an audio effect does not keep its session * active across releasing/initializing a new audio track, on platform builds where @@ -277,6 +285,7 @@ public final class AudioTrack { private final ConditionVariable releasingConditionVariable; private final long[] playheadOffsets; private final AudioTrackUtil audioTrackUtil; + private final LinkedList playbackParametersCheckpoints; /** * Used to keep the audio session active on pre-V21 builds (see {@link #initialize()}). @@ -295,7 +304,11 @@ public final class AudioTrack { private boolean passthrough; private int bufferSize; private long bufferSizeUs; + + private PlaybackParameters drainingPlaybackParameters; private PlaybackParameters playbackParameters; + private long playbackParametersOffsetUs; + private long playbackParametersPositionUs; private ByteBuffer avSyncHeader; private int bytesUntilNextAvSync; @@ -377,6 +390,7 @@ public final class AudioTrack { drainingAudioProcessorIndex = C.INDEX_UNSET; this.audioProcessors = new AudioProcessor[0]; outputBuffers = new ByteBuffer[0]; + playbackParametersCheckpoints = new LinkedList<>(); } /** @@ -432,7 +446,8 @@ public final class AudioTrack { positionUs -= latencyUs; } } - return startMediaTimeUs + scaleFrames(positionUs); + + return startMediaTimeUs + applySpeedup(positionUs); } /** @@ -747,6 +762,21 @@ public final class AudioTrack { framesPerEncodedSample = getFramesPerEncodedSample(outputEncoding, buffer); } + if (drainingPlaybackParameters != null) { + if (!drainAudioProcessorsToEndOfStream()) { + // Don't process any more input until draining completes. + return false; + } + // Store the position and corresponding media time from which the parameters will apply. + playbackParametersCheckpoints.add(new PlaybackParametersCheckpoint( + drainingPlaybackParameters, Math.max(0, presentationTimeUs), + framesToDurationUs(getWrittenFrames()))); + drainingPlaybackParameters = null; + // The audio processors have drained, so flush them. This will cause any active speed + // adjustment audio processor to start producing audio with the new parameters. + resetAudioProcessors(); + } + if (startMediaTimeState == START_NOT_SET) { startMediaTimeUs = Math.max(0, presentationTimeUs); startMediaTimeState = START_IN_SYNC; @@ -895,7 +925,15 @@ public final class AudioTrack { return; } - // Drain the audio processors. + if (drainAudioProcessorsToEndOfStream()) { + // The audio processors have drained, so drain the underlying audio track. + audioTrackUtil.handleEndOfStream(getWrittenFrames()); + bytesUntilNextAvSync = 0; + handledEndOfStream = true; + } + } + + private boolean drainAudioProcessorsToEndOfStream() throws WriteException { boolean audioProcessorNeedsEndOfStream = false; if (drainingAudioProcessorIndex == C.INDEX_UNSET) { drainingAudioProcessorIndex = passthrough ? audioProcessors.length : 0; @@ -908,7 +946,7 @@ public final class AudioTrack { } processBuffers(C.TIME_UNSET); if (!audioProcessor.isEnded()) { - return; + return false; } audioProcessorNeedsEndOfStream = true; drainingAudioProcessorIndex++; @@ -918,14 +956,11 @@ public final class AudioTrack { if (outputBuffer != null) { writeBuffer(outputBuffer, C.TIME_UNSET); if (outputBuffer != null) { - return; + return false; } } - - // Drain the track. - audioTrackUtil.handleEndOfStream(getWrittenFrames()); - bytesUntilNextAvSync = 0; - handledEndOfStream = true; + drainingAudioProcessorIndex = C.INDEX_UNSET; + return true; } /** @@ -949,21 +984,27 @@ public final class AudioTrack { * Attempts to set the playback parameters and returns the active playback parameters, which may * differ from those passed in. * + * @param playbackParameters The new playback parameters to attempt to set. * @return The active playback parameters. */ public PlaybackParameters setPlaybackParameters(PlaybackParameters playbackParameters) { if (passthrough) { + // The playback parameters are always the default in passthrough mode. this.playbackParameters = PlaybackParameters.DEFAULT; - } else { - this.playbackParameters = new PlaybackParameters( - sonicAudioProcessor.setSpeed(playbackParameters.speed), - sonicAudioProcessor.setPitch(playbackParameters.pitch)); - // TODO: Avoid resetting the track, so that speed/pitch changes are seamless. - // See [Internal: b/36542189]. - reset(); - // Setting the playback parameters never changes the output format, so it is not necessary to - // reconfigure the processors, though they may have become active/inactive. - resetAudioProcessors(); + return this.playbackParameters; + } + playbackParameters = new PlaybackParameters( + sonicAudioProcessor.setSpeed(playbackParameters.speed), + sonicAudioProcessor.setPitch(playbackParameters.pitch)); + PlaybackParameters lastSetPlaybackParameters = + drainingPlaybackParameters != null ? drainingPlaybackParameters + : !playbackParametersCheckpoints.isEmpty() + ? playbackParametersCheckpoints.getLast().playbackParameters + : this.playbackParameters; + if (!playbackParameters.equals(lastSetPlaybackParameters)) { + // We need to change the playback parameters. Drain the audio processors so we can determine + // the frame position at which the new parameters apply. + drainingPlaybackParameters = playbackParameters; } return this.playbackParameters; } @@ -975,15 +1016,6 @@ public final class AudioTrack { return playbackParameters; } - /** - * Returns the number of input frames corresponding to the specified number of output frames, - * taking into account any internal playback speed adjustment. - */ - private long scaleFrames(long outputFrameCount) { - return sonicAudioProcessor.isActive() ? sonicAudioProcessor.getInputFrames(outputFrameCount) - : outputFrameCount; - } - /** * Sets the stream type for audio track. If the stream type has changed and if the audio track * is not configured for use with tunneling, then the audio track is reset and the audio session @@ -1098,6 +1130,14 @@ public final class AudioTrack { writtenPcmBytes = 0; writtenEncodedFrames = 0; framesPerEncodedSample = 0; + if (drainingPlaybackParameters != null) { + playbackParameters = drainingPlaybackParameters; + } else if (!playbackParametersCheckpoints.isEmpty()) { + playbackParameters = playbackParametersCheckpoints.getLast().playbackParameters; + } + playbackParametersCheckpoints.clear(); + playbackParametersOffsetUs = 0; + playbackParametersPositionUs = 0; inputBuffer = null; outputBuffer = null; for (int i = 0; i < audioProcessors.length; i++) { @@ -1174,6 +1214,36 @@ public final class AudioTrack { return isInitialized() && startMediaTimeState != START_NOT_SET; } + /** + * Returns the underlying audio track {@code positionUs} with any applicable speedup applied. + */ + private long applySpeedup(long positionUs) { + while (!playbackParametersCheckpoints.isEmpty() + && positionUs >= playbackParametersCheckpoints.getFirst().positionUs) { + // We are playing (or about to play) media with the new playback parameters, so update them. + PlaybackParametersCheckpoint checkpoint = playbackParametersCheckpoints.remove(); + playbackParameters = checkpoint.playbackParameters; + playbackParametersPositionUs = checkpoint.positionUs; + playbackParametersOffsetUs = checkpoint.mediaTimeUs - startMediaTimeUs; + } + + if (playbackParameters.speed == 1f) { + return positionUs + playbackParametersOffsetUs - playbackParametersPositionUs; + } + + if (playbackParametersCheckpoints.isEmpty() + && sonicAudioProcessor.getOutputByteCount() >= SONIC_MIN_BYTES_FOR_SPEEDUP) { + return playbackParametersOffsetUs + + Util.scaleLargeTimestamp(positionUs - playbackParametersPositionUs, + sonicAudioProcessor.getInputByteCount(), sonicAudioProcessor.getOutputByteCount()); + } + + // We are playing drained data at a previous playback speed, or don't have enough bytes to + // calculate an accurate speedup, so fall back to multiplying by the speed. + return playbackParametersOffsetUs + + (long) ((double) playbackParameters.speed * (positionUs - playbackParametersPositionUs)); + } + /** * Updates the audio track latency and playback position parameters. */ @@ -1636,4 +1706,22 @@ public final class AudioTrack { } + /** + * Stores playback parameters with the position and media time at which they apply. + */ + private static final class PlaybackParametersCheckpoint { + + private final PlaybackParameters playbackParameters; + private final long mediaTimeUs; + private final long positionUs; + + private PlaybackParametersCheckpoint(PlaybackParameters playbackParameters, long mediaTimeUs, + long positionUs) { + this.playbackParameters = playbackParameters; + this.mediaTimeUs = mediaTimeUs; + this.positionUs = positionUs; + } + + } + } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/audio/SonicAudioProcessor.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/SonicAudioProcessor.java index 3f45afd53e..2dc14a094d 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/audio/SonicAudioProcessor.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/audio/SonicAudioProcessor.java @@ -102,11 +102,17 @@ import java.nio.ShortBuffer; } /** - * Returns the number of input frames corresponding to the specified number of output frames. + * Returns the number of bytes of input queued since the last call to {@link #flush()}. */ - public long getInputFrames(long outputFrames) { - // Sonic produces output data as soon as input is queued. - return outputBytes == 0 ? 0 : Util.scaleLargeTimestamp(outputFrames, inputBytes, outputBytes); + public long getInputByteCount() { + return inputBytes; + } + + /** + * Returns the number of bytes of output dequeued since the last call to {@link #flush()}. + */ + public long getOutputByteCount() { + return outputBytes; } @Override From 77e6d75bddfa29e812eba4905e7535b03146da0c Mon Sep 17 00:00:00 2001 From: olly Date: Fri, 31 Mar 2017 06:18:13 -0700 Subject: [PATCH 055/119] Fix FLV parser to skip metadata we're not interested in Issue: #2634 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=151816597 --- .../exoplayer2/extractor/flv/ScriptTagPayloadReader.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/flv/ScriptTagPayloadReader.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/flv/ScriptTagPayloadReader.java index 7229d96300..1a4f8f3e88 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/flv/ScriptTagPayloadReader.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/flv/ScriptTagPayloadReader.java @@ -80,8 +80,8 @@ import java.util.Map; } int type = readAmfType(data); if (type != AMF_TYPE_ECMA_ARRAY) { - // Should never happen. - throw new ParserException(); + // We're not interested in this metadata. + return; } // Set the duration to the value contained in the metadata, if present. Map metadata = readAmfEcmaArray(data); From 64188748f436283766369bf9dfaead5eefebc1e2 Mon Sep 17 00:00:00 2001 From: eguven Date: Fri, 31 Mar 2017 06:48:40 -0700 Subject: [PATCH 056/119] Move cache related generic methods from DashDownloader to CacheUtil ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=151818564 --- .../upstream/cache/CacheDataSource.java | 11 +- .../exoplayer2/upstream/cache/CacheUtil.java | 237 ++++++++++++++++++ 2 files changed, 238 insertions(+), 10 deletions(-) create mode 100644 library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheUtil.java diff --git a/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheDataSource.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheDataSource.java index 26c77b5dd1..86dc5cfedf 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheDataSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheDataSource.java @@ -108,15 +108,6 @@ public final class CacheDataSource implements DataSource { private boolean currentRequestIgnoresCache; private long totalCachedBytesRead; - /** - * Generates a cache key out of the given {@link Uri}. - * - * @param uri Uri of a content which the requested key is for. - */ - public static String generateKey(Uri uri) { - return uri.toString(); - } - /** * Constructs an instance with default {@link DataSource} and {@link DataSink} instances for * reading and writing the cache and with {@link #DEFAULT_MAX_CACHE_FILE_SIZE}. @@ -180,7 +171,7 @@ public final class CacheDataSource implements DataSource { try { uri = dataSpec.uri; flags = dataSpec.flags; - key = dataSpec.key != null ? dataSpec.key : generateKey(dataSpec.uri); + key = CacheUtil.getKey(dataSpec); readPosition = dataSpec.position; currentRequestIgnoresCache = (ignoreCacheOnError && seenCacheError) || (dataSpec.length == C.LENGTH_UNSET && ignoreCacheForUnsetLengthRequests); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheUtil.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheUtil.java new file mode 100644 index 0000000000..f6251dbbf1 --- /dev/null +++ b/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheUtil.java @@ -0,0 +1,237 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.exoplayer2.upstream.cache; + +import android.net.Uri; +import com.google.android.exoplayer2.C; +import com.google.android.exoplayer2.upstream.DataSource; +import com.google.android.exoplayer2.upstream.DataSpec; +import com.google.android.exoplayer2.util.Assertions; +import com.google.android.exoplayer2.util.PriorityTaskManager; +import com.google.android.exoplayer2.util.Util; +import java.io.IOException; +import java.util.NavigableSet; + +/** + * Caching related utility methods. + */ +public final class CacheUtil { + + /** Holds the counters used during caching. */ + public static class CachingCounters { + /** Total number of already cached bytes. */ + public long alreadyCachedBytes; + /** + * Total number of downloaded bytes. + * + *

    {@link #getCached(DataSpec, Cache, CachingCounters)} sets it to the count of the missing + * bytes or to {@link C#LENGTH_UNSET} if {@code dataSpec} is unbounded and content length isn't + * available in the {@code cache}. + */ + public long downloadedBytes; + } + + /** + * Generates a cache key out of the given {@link Uri}. + * + * @param uri Uri of a content which the requested key is for. + */ + public static String generateKey(Uri uri) { + return uri.toString(); + } + + /** + * Returns the {@code dataSpec.key} if not null, otherwise generates a cache key out of {@code + * dataSpec.uri} + * + * @param dataSpec Defines a content which the requested key is for. + */ + public static String getKey(DataSpec dataSpec) { + return dataSpec.key != null ? dataSpec.key : generateKey(dataSpec.uri); + } + + /** + * Returns already cached and missing bytes in the {@cache} for the data defined by {@code + * dataSpec}. + * + * @param dataSpec Defines the data to be checked. + * @param cache A {@link Cache} which has the data. + * @param counters The counters to be set. If null a new {@link CachingCounters} is created and + * used. + * @return The used {@link CachingCounters} instance. + */ + public static CachingCounters getCached(DataSpec dataSpec, Cache cache, + CachingCounters counters) { + try { + return internalCache(dataSpec, cache, null, null, null, 0, counters); + } catch (IOException | InterruptedException e) { + throw new IllegalStateException(e); + } + } + + /** + * Caches the data defined by {@code dataSpec} while skipping already cached data. + * + * @param dataSpec Defines the data to be cached. + * @param cache A {@link Cache} to store the data. + * @param dataSource A {@link CacheDataSource} that works on the {@code cache}. + * @param buffer The buffer to be used while caching. + * @param priorityTaskManager If not null it's used to check whether it is allowed to proceed with + * caching. + * @param priority The priority of this task. Used with {@code priorityTaskManager}. + * @param counters The counters to be set during caching. If not null its values reset to + * zero before using. If null a new {@link CachingCounters} is created and used. + * @return The used {@link CachingCounters} instance. + * @throws IOException If an error occurs reading from the source. + * @throws InterruptedException If the thread was interrupted. + */ + public static CachingCounters cache(DataSpec dataSpec, Cache cache, CacheDataSource dataSource, + byte[] buffer, PriorityTaskManager priorityTaskManager, int priority, + CachingCounters counters) throws IOException, InterruptedException { + Assertions.checkNotNull(dataSource); + Assertions.checkNotNull(buffer); + return internalCache(dataSpec, cache, dataSource, buffer, priorityTaskManager, priority, + counters); + } + + /** + * Caches the data defined by {@code dataSpec} while skipping already cached data. If {@code + * dataSource} or {@code buffer} is null performs a dry run. + * + * @param dataSpec Defines the data to be cached. + * @param cache A {@link Cache} to store the data. + * @param dataSource A {@link CacheDataSource} that works on the {@code cache}. If null a dry run + * is performed. + * @param buffer The buffer to be used while caching. If null a dry run is performed. + * @param priorityTaskManager If not null it's used to check whether it is allowed to proceed with + * caching. + * @param priority The priority of this task. Used with {@code priorityTaskManager}. + * @param counters The counters to be set during caching. If not null its values reset to + * zero before using. If null a new {@link CachingCounters} is created and used. + * @return The used {@link CachingCounters} instance. + * @throws IOException If not dry run and an error occurs reading from the source. + * @throws InterruptedException If not dry run and the thread was interrupted. + */ + private static CachingCounters internalCache(DataSpec dataSpec, Cache cache, + CacheDataSource dataSource, byte[] buffer, PriorityTaskManager priorityTaskManager, + int priority, CachingCounters counters) throws IOException, InterruptedException { + long start = dataSpec.position; + long left = dataSpec.length; + String key = getKey(dataSpec); + if (left == C.LENGTH_UNSET) { + left = cache.getContentLength(key); + if (left == C.LENGTH_UNSET) { + left = Long.MAX_VALUE; + } + } + if (counters == null) { + counters = new CachingCounters(); + } else { + counters.alreadyCachedBytes = 0; + counters.downloadedBytes = 0; + } + while (left > 0) { + long blockLength = cache.getCachedBytes(key, start, left); + // Skip already cached data + if (blockLength > 0) { + counters.alreadyCachedBytes += blockLength; + } else { + // There is a hole in the cache which is at least "-blockLength" long. + blockLength = -blockLength; + if (dataSource != null && buffer != null) { + DataSpec subDataSpec = new DataSpec(dataSpec.uri, start, + blockLength == Long.MAX_VALUE ? C.LENGTH_UNSET : blockLength, key); + long read = readAndDiscard(subDataSpec, dataSource, buffer, priorityTaskManager, + priority); + counters.downloadedBytes += read; + if (read < blockLength) { + // Reached end of data. + break; + } + } else if (blockLength == Long.MAX_VALUE) { + counters.downloadedBytes = C.LENGTH_UNSET; + break; + } else { + counters.downloadedBytes += blockLength; + } + } + start += blockLength; + if (left != Long.MAX_VALUE) { + left -= blockLength; + } + } + return counters; + } + + /** + * Reads and discards all data specified by the {@code dataSpec}. + * + * @param dataSpec Defines the data to be read. + * @param dataSource The {@link DataSource} to read the data from. + * @param buffer The buffer to be used while downloading. + * @param priorityTaskManager If not null it's used to check whether it is allowed to proceed with + * caching. + * @param priority The priority of this task. + * @return Number of read bytes, or 0 if no data is available because the end of the opened range + * has been reached. + */ + private static long readAndDiscard(DataSpec dataSpec, DataSource dataSource, byte[] buffer, + PriorityTaskManager priorityTaskManager, int priority) + throws IOException, InterruptedException { + while (true) { + if (priorityTaskManager != null) { + // Wait for any other thread with higher priority to finish its job. + priorityTaskManager.proceed(priority); + } + try { + dataSource.open(dataSpec); + long totalRead = 0; + while (true) { + if (Thread.interrupted()) { + throw new InterruptedException(); + } + int read = dataSource.read(buffer, 0, buffer.length); + if (read == C.RESULT_END_OF_INPUT) { + return totalRead; + } + totalRead += read; + } + } catch (PriorityTaskManager.PriorityTooLowException exception) { + // catch and try again + } finally { + Util.closeQuietly(dataSource); + } + } + } + + /** Removes all of the data in the {@code cache} pointed by the {@code key}. */ + public static void remove(Cache cache, String key) { + NavigableSet cachedSpans = cache.getCachedSpans(key); + if (cachedSpans == null) { + return; + } + for (CacheSpan cachedSpan : cachedSpans) { + try { + cache.removeSpan(cachedSpan); + } catch (Cache.CacheException e) { + // do nothing + } + } + } + + private CacheUtil() {} + +} From 8e7679082380897af3eade77242cffbafe39995c Mon Sep 17 00:00:00 2001 From: olly Date: Fri, 31 Mar 2017 10:49:09 -0700 Subject: [PATCH 057/119] Remove deprecated readSource method ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=151840862 --- .../java/com/google/android/exoplayer2/BaseRenderer.java | 8 -------- 1 file changed, 8 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/BaseRenderer.java b/library/core/src/main/java/com/google/android/exoplayer2/BaseRenderer.java index 8324b184ca..44fb6d68ae 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/BaseRenderer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/BaseRenderer.java @@ -254,14 +254,6 @@ public abstract class BaseRenderer implements Renderer, RendererCapabilities { return index; } - /** - * Use {@link #readSource(FormatHolder, DecoderInputBuffer, boolean)} instead. - */ - @Deprecated - protected final int readSource(FormatHolder formatHolder, DecoderInputBuffer buffer) { - return readSource(formatHolder, buffer, false); - } - /** * Reads from the enabled upstream source. If the upstream source has been read to the end then * {@link C#RESULT_BUFFER_READ} is only returned if {@link #setCurrentStreamFinal()} has been From 9a06b77b8a454db4950bac7a20b6fd12190b2721 Mon Sep 17 00:00:00 2001 From: Oliver Woodman Date: Fri, 31 Mar 2017 19:25:57 +0100 Subject: [PATCH 058/119] Delete stray test --- .../dash/offline/DashDownloaderTest.java | 420 ------------------ 1 file changed, 420 deletions(-) delete mode 100644 library/dash/src/androidTest/java/com/google/android/exoplayer2/source/dash/offline/DashDownloaderTest.java diff --git a/library/dash/src/androidTest/java/com/google/android/exoplayer2/source/dash/offline/DashDownloaderTest.java b/library/dash/src/androidTest/java/com/google/android/exoplayer2/source/dash/offline/DashDownloaderTest.java deleted file mode 100644 index c2578c196f..0000000000 --- a/library/dash/src/androidTest/java/com/google/android/exoplayer2/source/dash/offline/DashDownloaderTest.java +++ /dev/null @@ -1,420 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.google.android.exoplayer2.source.dash.offline; - -import android.net.Uri; -import android.test.InstrumentationTestCase; -import android.test.MoreAsserts; -import com.google.android.exoplayer2.C; -import com.google.android.exoplayer2.source.dash.manifest.DashManifest; -import com.google.android.exoplayer2.source.dash.manifest.RepresentationKey; -import com.google.android.exoplayer2.source.dash.offline.DashDownloader.ProgressListener; -import com.google.android.exoplayer2.testutil.FakeDataSource; -import com.google.android.exoplayer2.testutil.FakeDataSource.FakeData; -import com.google.android.exoplayer2.testutil.FakeDataSource.FakeDataSet; -import com.google.android.exoplayer2.testutil.TestUtil; -import com.google.android.exoplayer2.upstream.DataSourceInputStream; -import com.google.android.exoplayer2.upstream.DataSpec; -import com.google.android.exoplayer2.upstream.DummyDataSource; -import com.google.android.exoplayer2.upstream.cache.CacheDataSource; -import com.google.android.exoplayer2.upstream.cache.NoOpCacheEvictor; -import com.google.android.exoplayer2.upstream.cache.SimpleCache; -import com.google.android.exoplayer2.util.ClosedSource; -import com.google.android.exoplayer2.util.Util; -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.IOException; -import java.util.Arrays; - -/** - * Unit tests for {@link DashDownloader}. - */ -@ClosedSource(reason = "Not ready yet") -public class DashDownloaderTest extends InstrumentationTestCase { - - private static final byte[] TEST_MPD = - ("\n" - + "\n" - + " \n" - + " \n" - + " \n" - + " \n" - + " \n" - + " \n" - + " \n" - + " \n" - + " \n" - + " \n" - + " \n" - // Bounded range data - + " \n" - // Unbounded range data - + " \n" - + " \n" - + " \n" - + " \n" - + " \n" - + " \n" - + " \n" - + " \n" - + " \n" - + " \n" - + " \n" - + " \n" - + " \n" - + " \n" - + " \n" - + " \n" - + "").getBytes(); - - private static final byte[] TEST_MPD_NO_INDEX = - ("\n" - + "\n" - + " \n" - + " \n" - + " \n" - + " \n" - + " \n" - + " \n" - + " \n" - + "").getBytes(); - - private File tempFolder; - private SimpleCache cache; - - @Override - public void setUp() throws Exception { - super.setUp(); - tempFolder = Util.createTempDirectory(getInstrumentation().getContext(), "ExoPlayerTest"); - cache = new SimpleCache(tempFolder, new NoOpCacheEvictor()); - } - - @Override - public void tearDown() throws Exception { - Util.recursiveDelete(tempFolder); - } - - public void testDownloadManifest() throws Exception { - FakeDataSet fakeDataSet = new FakeDataSet() - .setData("test.mpd", TEST_MPD); - DashDownloader dashDownloader = - new DashDownloader("test.mpd", cache, new FakeDataSource(fakeDataSet)); - - DashManifest manifest = dashDownloader.downloadManifest(); - - assertNotNull(manifest); - assertCachedData(fakeDataSet); - } - - public void testDownloadManifestFailure() throws Exception { - byte[] testMpdFirstPart = Arrays.copyOf(TEST_MPD, 10); - byte[] testMpdSecondPart = Arrays.copyOfRange(TEST_MPD, 10, TEST_MPD.length); - FakeDataSet fakeDataSet = new FakeDataSet() - .newData("test.mpd") - .appendReadData(testMpdFirstPart) - .appendReadError(new IOException()) - .appendReadData(testMpdSecondPart) - .endData(); - DashDownloader dashDownloader = - new DashDownloader("test.mpd", cache, new FakeDataSource(fakeDataSet)); - - // downloadManifest fails on the first try - try { - dashDownloader.downloadManifest(); - fail(); - } catch (IOException e) { - // ignore - } - assertCachedData("test.mpd", testMpdFirstPart); - - // on the second try it downloads the rest of the data - DashManifest manifest = dashDownloader.downloadManifest(); - - assertNotNull(manifest); - assertCachedData(fakeDataSet); - } - - public void testDownloadRepresentation() throws Exception { - FakeDataSet fakeDataSet = new FakeDataSet() - .setData("test.mpd", TEST_MPD) - .setData("audio_init_data", TestUtil.buildTestData(10)) - .setData("audio_segment_1", TestUtil.buildTestData(4)) - .setData("audio_segment_2", TestUtil.buildTestData(5)) - .setData("audio_segment_3", TestUtil.buildTestData(6)); - DashDownloader dashDownloader = - new DashDownloader("test.mpd", cache, new FakeDataSource(fakeDataSet)); - dashDownloader.downloadManifest(); - - dashDownloader.selectRepresentations(new RepresentationKey(0, 0, 0)); - dashDownloader.downloadRepresentations(null); - - assertCachedData(fakeDataSet); - } - - public void testDownloadRepresentationInSmallParts() throws Exception { - FakeDataSet fakeDataSet = new FakeDataSet() - .setData("test.mpd", TEST_MPD) - .setData("audio_init_data", TestUtil.buildTestData(10)) - .newData("audio_segment_1") - .appendReadData(TestUtil.buildTestData(10)) - .appendReadData(TestUtil.buildTestData(10)) - .appendReadData(TestUtil.buildTestData(10)) - .endData() - .setData("audio_segment_2", TestUtil.buildTestData(5)) - .setData("audio_segment_3", TestUtil.buildTestData(6)); - DashDownloader dashDownloader = - new DashDownloader("test.mpd", cache, new FakeDataSource(fakeDataSet)); - dashDownloader.downloadManifest(); - - dashDownloader.selectRepresentations(new RepresentationKey(0, 0, 0)); - dashDownloader.downloadRepresentations(null); - - assertCachedData(fakeDataSet); - } - - public void testDownloadRepresentations() throws Exception { - FakeDataSet fakeDataSet = new FakeDataSet() - .setData("test.mpd", TEST_MPD) - .setData("audio_init_data", TestUtil.buildTestData(10)) - .setData("audio_segment_1", TestUtil.buildTestData(4)) - .setData("audio_segment_2", TestUtil.buildTestData(5)) - .setData("audio_segment_3", TestUtil.buildTestData(6)) - .setData("text_segment_1", TestUtil.buildTestData(1)) - .setData("text_segment_2", TestUtil.buildTestData(2)) - .setData("text_segment_3", TestUtil.buildTestData(3)); - DashDownloader dashDownloader = - new DashDownloader("test.mpd", cache, new FakeDataSource(fakeDataSet)); - dashDownloader.downloadManifest(); - - dashDownloader.selectRepresentations( - new RepresentationKey(0, 0, 0), - new RepresentationKey(0, 1, 0)); - dashDownloader.downloadRepresentations(null); - - assertCachedData(fakeDataSet); - } - - public void testDownloadRepresentationFailure() throws Exception { - FakeDataSet fakeDataSet = new FakeDataSet() - .setData("test.mpd", TEST_MPD) - .setData("audio_init_data", TestUtil.buildTestData(10)) - .setData("audio_segment_1", TestUtil.buildTestData(4)) - .newData("audio_segment_2") - .appendReadData(TestUtil.buildTestData(2)) - .appendReadError(new IOException()) - .appendReadData(TestUtil.buildTestData(3)) - .endData() - .setData("audio_segment_3", TestUtil.buildTestData(6)); - DashDownloader dashDownloader = - new DashDownloader("test.mpd", cache, new FakeDataSource(fakeDataSet)); - dashDownloader.downloadManifest(); - - dashDownloader.selectRepresentations(new RepresentationKey(0, 0, 0)); - // downloadRepresentations fails on the first try - try { - dashDownloader.downloadRepresentations(null); - fail(); - } catch (IOException e) { - // ignore - } - dashDownloader.downloadRepresentations(null); - - assertCachedData(fakeDataSet); - } - - public void testCounters() throws Exception { - FakeDataSet fakeDataSet = new FakeDataSet() - .setData("test.mpd", TEST_MPD) - .setData("audio_init_data", TestUtil.buildTestData(10)) - .setData("audio_segment_1", TestUtil.buildTestData(4)) - .newData("audio_segment_2") - .appendReadData(TestUtil.buildTestData(2)) - .appendReadError(new IOException()) - .appendReadData(TestUtil.buildTestData(3)) - .endData() - .setData("audio_segment_3", TestUtil.buildTestData(6)); - DashDownloader dashDownloader = - new DashDownloader("test.mpd", cache, new FakeDataSource(fakeDataSet)); - dashDownloader.downloadManifest(); - - assertCounters(dashDownloader, C.LENGTH_UNSET, C.LENGTH_UNSET, C.LENGTH_UNSET); - - dashDownloader.selectRepresentations(new RepresentationKey(0, 0, 0)); - dashDownloader.initStatus(); - assertCounters(dashDownloader, 3, 0, 0); - - // downloadRepresentations fails after downloading init data, segment 1 and 2 bytes in segment 2 - try { - dashDownloader.downloadRepresentations(null); - fail(); - } catch (IOException e) { - // ignore - } - dashDownloader.initStatus(); - assertCounters(dashDownloader, 3, 1, 10 + 4 + 2); - - dashDownloader.downloadRepresentations(null); - - assertCounters(dashDownloader, 3, 3, 10 + 4 + 5 + 6); - } - - public void testListener() throws Exception { - FakeDataSet fakeDataSet = new FakeDataSet() - .setData("test.mpd", TEST_MPD) - .setData("audio_init_data", TestUtil.buildTestData(10)) - .setData("audio_segment_1", TestUtil.buildTestData(4)) - .setData("audio_segment_2", TestUtil.buildTestData(5)) - .setData("audio_segment_3", TestUtil.buildTestData(6)); - DashDownloader dashDownloader = - new DashDownloader("test.mpd", cache, new FakeDataSource(fakeDataSet)); - dashDownloader.downloadManifest(); - - dashDownloader.selectRepresentations(new RepresentationKey(0, 0, 0)); - dashDownloader.downloadRepresentations(new ProgressListener() { - private int counter = 0; - @Override - public void onDownloadProgress(DashDownloader dashDownloader, int totalSegments, - int downloadedSegments, - long downloadedBytes) { - switch (counter++) { - case 0: - assertTrue(totalSegments == 3 && downloadedSegments == 0 && downloadedBytes == 10); - break; - case 1: - assertTrue(totalSegments == 3 && downloadedSegments == 1 && downloadedBytes == 14); - break; - case 2: - assertTrue(totalSegments == 3 && downloadedSegments == 2 && downloadedBytes == 19); - break; - case 3: - assertTrue(totalSegments == 3 && downloadedSegments == 3 && downloadedBytes == 25); - break; - default: - fail(); - } - } - }); - } - - public void testRemoveAll() throws Exception { - FakeDataSet fakeDataSet = new FakeDataSet() - .setData("test.mpd", TEST_MPD) - .setData("audio_init_data", TestUtil.buildTestData(10)) - .setData("audio_segment_1", TestUtil.buildTestData(4)) - .setData("audio_segment_2", TestUtil.buildTestData(5)) - .setData("audio_segment_3", TestUtil.buildTestData(6)) - .setData("text_segment_1", TestUtil.buildTestData(1)) - .setData("text_segment_2", TestUtil.buildTestData(2)) - .setData("text_segment_3", TestUtil.buildTestData(3)); - DashDownloader dashDownloader = - new DashDownloader("test.mpd", cache, new FakeDataSource(fakeDataSet)); - dashDownloader.downloadManifest(); - dashDownloader.selectRepresentations( - new RepresentationKey(0, 0, 0), - new RepresentationKey(0, 1, 0)); - dashDownloader.downloadRepresentations(null); - - dashDownloader.removeAll(); - - assertEquals(0, cache.getCacheSpace()); - } - - public void testRemoveRepresentations() throws Exception { - FakeDataSet fakeDataSet = new FakeDataSet() - .setData("test.mpd", TEST_MPD) - .setData("audio_init_data", TestUtil.buildTestData(10)) - .setData("audio_segment_1", TestUtil.buildTestData(4)) - .setData("audio_segment_2", TestUtil.buildTestData(5)) - .setData("audio_segment_3", TestUtil.buildTestData(6)) - .setData("text_segment_1", TestUtil.buildTestData(1)) - .setData("text_segment_2", TestUtil.buildTestData(2)) - .setData("text_segment_3", TestUtil.buildTestData(3)); - DashDownloader dashDownloader = - new DashDownloader("test.mpd", cache, new FakeDataSource(fakeDataSet)); - dashDownloader.downloadManifest(); - dashDownloader.selectRepresentations( - new RepresentationKey(0, 0, 0), - new RepresentationKey(0, 1, 0)); - dashDownloader.downloadRepresentations(null); - - dashDownloader.removeRepresentations(); - - assertEquals(TEST_MPD.length, cache.getCacheSpace()); - assertCachedData("test.mpd", TEST_MPD); - } - - public void testMpdNoIndex() throws Exception { - FakeDataSet fakeDataSet = new FakeDataSet() - .setData("test.mpd", TEST_MPD_NO_INDEX) - .setData("test_segment_1", TestUtil.buildTestData(4)); - DashDownloader dashDownloader = - new DashDownloader("test.mpd", cache, new FakeDataSource(fakeDataSet)); - dashDownloader.downloadManifest(); - - dashDownloader.selectRepresentations(new RepresentationKey(0, 0, 0)); - dashDownloader.initStatus(); - try { - dashDownloader.downloadRepresentations(null); - fail(); - } catch (DashDownloaderException e) { - // expected interrupt. - } - dashDownloader.removeAll(); - - assertEquals(0, cache.getCacheSpace()); - } - - private void assertCachedData(FakeDataSet fakeDataSet) throws IOException { - int totalLength = 0; - for (FakeData fakeData : fakeDataSet.getAllData()) { - byte[] data = fakeData.getData(); - assertCachedData(fakeData.uri, data); - totalLength += data.length; - } - assertEquals(totalLength, cache.getCacheSpace()); - } - - private void assertCachedData(String uriString, byte[] expected) throws IOException { - CacheDataSource dataSource = new CacheDataSource(cache, DummyDataSource.INSTANCE, 0); - ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); - DataSourceInputStream inputStream = new DataSourceInputStream(dataSource, - new DataSpec(Uri.parse(uriString), DataSpec.FLAG_ALLOW_CACHING_UNKNOWN_LENGTH)); - try { - inputStream.open(); - byte[] buffer = new byte[1024]; - int bytesRead; - while ((bytesRead = inputStream.read(buffer)) != -1) { - outputStream.write(buffer, 0, bytesRead); - } - } catch (IOException e) { - // Ignore - } finally { - inputStream.close(); - } - MoreAsserts.assertEquals(expected, outputStream.toByteArray()); - } - - private static void assertCounters(DashDownloader dashDownloader, int totalSegments, - int downloadedSegments, int downloadedBytes) { - assertEquals(totalSegments, dashDownloader.getTotalSegments()); - assertEquals(downloadedSegments, dashDownloader.getDownloadedSegments()); - assertEquals(downloadedBytes, dashDownloader.getDownloadedBytes()); - } - -} From 757999758b7f77e6d3553d6b50e7ca87215355c5 Mon Sep 17 00:00:00 2001 From: Oliver Woodman Date: Fri, 31 Mar 2017 19:38:41 +0100 Subject: [PATCH 059/119] Cleanup for merged pull requests --- extensions/opus/src/main/jni/opus_jni.cc | 2 +- .../mediacodec/MediaCodecRenderer.java | 20 ++++++++++--------- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/extensions/opus/src/main/jni/opus_jni.cc b/extensions/opus/src/main/jni/opus_jni.cc index 0ba9675484..a1d1ffe478 100644 --- a/extensions/opus/src/main/jni/opus_jni.cc +++ b/extensions/opus/src/main/jni/opus_jni.cc @@ -59,7 +59,7 @@ jint JNI_OnLoad(JavaVM* vm, void* reserved) { } static const int kBytesPerSample = 2; // opus fixed point uses 16 bit samples. -static const int kMaxOpusOutputPacketSizeSamples = 960 * 6;// Maximum packet size used in Xiph's opusdec. +static const int kMaxOpusOutputPacketSizeSamples = 960 * 6; static int channelCount; static int errorCode; 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 4f729746b0..8fb9bc9271 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 @@ -339,7 +339,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer { } String codecName = decoderInfo.name; - codecIsAdaptive = decoderInfo.adaptive && (codecNeedsDisableAdaptationWorkaround(codecName)==false); + codecIsAdaptive = decoderInfo.adaptive && !codecNeedsDisableAdaptationWorkaround(codecName); codecNeedsDiscardToSpsWorkaround = codecNeedsDiscardToSpsWorkaround(codecName, format); codecNeedsFlushWorkaround = codecNeedsFlushWorkaround(codecName); codecNeedsAdaptationWorkaround = codecNeedsAdaptationWorkaround(codecName); @@ -1179,7 +1179,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer { * * @param name The decoder name. * @param format The input format. - * @return True if the device is known to set the number of audio channels in the output format + * @return True if the decoder is known to set the number of audio channels in the output format * to 2 for the given input format, whilst only actually outputting a single channel. False * otherwise. */ @@ -1187,17 +1187,19 @@ public abstract class MediaCodecRenderer extends BaseRenderer { return Util.SDK_INT <= 18 && format.channelCount == 1 && "OMX.MTK.AUDIO.DECODER.MP3".equals(name); } + /** - * Returns whether the decoder is needs Apaptive workaround disabled + * Returns whether the decoder is known to fail when adapting, despite advertising itself as an + * adaptive decoder. *

    - * If TRUE is returned then we explicitly override codecIsAdaptive, - * setting it to false. + * If true is returned then we explicitly disable adaptation for the decoder. + * * @param name The decoder name. - * @return TRUE if the device needs Adaptive workaround disabled + * @return True if the decoder is known to fail when adapting. */ private static boolean codecNeedsDisableAdaptationWorkaround(String name) { - return ( - (Util.SDK_INT <= 19 && Util.MODEL.equals("ODROID-XU3") - && ("OMX.Exynos.AVC.Decoder".equals(name) || "OMX.Exynos.AVC.Decoder.secure".equals(name)))); + return Util.SDK_INT <= 19 && Util.MODEL.equals("ODROID-XU3") + && ("OMX.Exynos.AVC.Decoder".equals(name) || "OMX.Exynos.AVC.Decoder.secure".equals(name)); } + } From 38779de22593e3acf40ab6f7b4906021a59a0804 Mon Sep 17 00:00:00 2001 From: Sergio Moreno Mozota Date: Tue, 4 Apr 2017 15:04:47 +0200 Subject: [PATCH 060/119] Add support for multiple subtitle tracks per PID and for naming hearing impaired subtitles --- .../extractor/ts/DvbSubtitlesReader.java | 68 +++++++++++++++---- 1 file changed, 55 insertions(+), 13 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/DvbSubtitlesReader.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/DvbSubtitlesReader.java index 6caf0692ae..240bfb987d 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/DvbSubtitlesReader.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/DvbSubtitlesReader.java @@ -24,6 +24,7 @@ import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.TrackIdGenerat import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.ParsableByteArray; +import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -33,20 +34,40 @@ import java.util.List; */ public final class DvbSubtitlesReader implements ElementaryStreamReader { - private final String language; - private List initializationData; + private class SubtitleTrack { + private String language; + private List initializationData; + } + + private List subtitles = new ArrayList<>(); private long sampleTimeUs; private int sampleBytesWritten; private boolean writingSample; - private TrackOutput output; + private List outputTracks = new ArrayList<>(); public DvbSubtitlesReader(TsPayloadReader.EsInfo esInfo) { - this.language = esInfo.language; - initializationData = Collections.singletonList(new byte[] {(byte) 0x00, - esInfo.descriptorBytes[6], esInfo.descriptorBytes[7], - esInfo.descriptorBytes[8], esInfo.descriptorBytes[9]}); + int pos = 2; + + while (pos < esInfo.descriptorBytes.length) { + SubtitleTrack subtitle = new SubtitleTrack(); + subtitle.language = new String(new byte[] { + esInfo.descriptorBytes[pos], + esInfo.descriptorBytes[pos + 1], + esInfo.descriptorBytes[pos + 2]}); + + if (((esInfo.descriptorBytes[pos + 3] & 0xF0 ) >> 4 ) == 2 ) { + subtitle.language += " for hard of hearing"; + } + + subtitle.initializationData = Collections.singletonList(new byte[] {(byte) 0x00, + esInfo.descriptorBytes[pos + 4], esInfo.descriptorBytes[pos + 5], + esInfo.descriptorBytes[pos + 6], esInfo.descriptorBytes[pos + 7]}); + + subtitles.add(subtitle); + pos += 8; + } } @@ -57,10 +78,18 @@ public final class DvbSubtitlesReader implements ElementaryStreamReader { @Override public void createTracks(ExtractorOutput extractorOutput, TrackIdGenerator idGenerator) { - idGenerator.generateNewId(); - this.output = extractorOutput.track(idGenerator.getTrackId(), C.TRACK_TYPE_TEXT); - output.format(Format.createImageSampleFormat(idGenerator.getFormatId(), - MimeTypes.APPLICATION_DVBSUBS, null, Format.NO_VALUE, initializationData, language, null)); + TrackOutput output; + SubtitleTrack subtitle; + + for (int i = 0; i < subtitles.size(); i++) { + subtitle = subtitles.get(i); + idGenerator.generateNewId(); + output = extractorOutput.track(idGenerator.getTrackId(), C.TRACK_TYPE_TEXT); + output.format(Format.createImageSampleFormat(idGenerator.getFormatId(), + MimeTypes.APPLICATION_DVBSUBS, null, Format.NO_VALUE, + subtitle.initializationData, subtitle.language, null)); + outputTracks.add(output); + } } @@ -76,7 +105,12 @@ public final class DvbSubtitlesReader implements ElementaryStreamReader { @Override public void packetFinished() { - output.sampleMetadata(sampleTimeUs, C.BUFFER_FLAG_KEY_FRAME, sampleBytesWritten, 0, null); + TrackOutput output; + + for (int i = 0; i < outputTracks.size(); i++) { + output = outputTracks.get(i); + output.sampleMetadata(sampleTimeUs, C.BUFFER_FLAG_KEY_FRAME, sampleBytesWritten, 0, null); + } writingSample = false; } @@ -84,7 +118,15 @@ public final class DvbSubtitlesReader implements ElementaryStreamReader { public void consume(ParsableByteArray data) { if (writingSample) { int bytesAvailable = data.bytesLeft(); - output.sampleData(data, bytesAvailable); + TrackOutput output; + int dataPosition = data.getPosition(); + + for (int i = 0; i < outputTracks.size(); i++) { + data.setPosition(dataPosition); + output = outputTracks.get(i); + output.sampleData(data, bytesAvailable); + } + sampleBytesWritten += bytesAvailable; } } From 5ee122cac88b2340daf9e86d73642c038e42556e Mon Sep 17 00:00:00 2001 From: olly Date: Mon, 3 Apr 2017 04:06:46 -0700 Subject: [PATCH 061/119] Add VERSION_SLASHY to ExoPlayerLibraryInfo ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=151994281 --- .../com/google/android/exoplayer2/ExoPlayerImpl.java | 2 +- .../android/exoplayer2/ExoPlayerLibraryInfo.java | 12 ++++++++++-- .../com/google/android/exoplayer2/util/Util.java | 2 +- 3 files changed, 12 insertions(+), 4 deletions(-) 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 7497ed0da9..7a3da68d02 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 @@ -76,7 +76,7 @@ import java.util.concurrent.CopyOnWriteArraySet; */ @SuppressLint("HandlerLeak") public ExoPlayerImpl(Renderer[] renderers, TrackSelector trackSelector, LoadControl loadControl) { - Log.i(TAG, "Init " + ExoPlayerLibraryInfo.VERSION + " [" + Util.DEVICE_DEBUG_INFO + "]"); + Log.i(TAG, "Init " + ExoPlayerLibraryInfo.VERSION_SLASHY + " [" + Util.DEVICE_DEBUG_INFO + "]"); Assertions.checkState(renderers.length > 0); this.renderers = Assertions.checkNotNull(renderers); this.trackSelector = Assertions.checkNotNull(trackSelector); 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 bee9904590..74f622946e 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 @@ -21,17 +21,25 @@ package com.google.android.exoplayer2; public interface ExoPlayerLibraryInfo { /** - * The version of the library, expressed as a string. + * 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. String VERSION = "2.3.1"; /** - * The version of the library, expressed as an integer. + * The version of the library expressed as {@code "ExoPlayerLib/" + VERSION}. + */ + // Intentionally hardcoded. Do not derive from other constants (e.g. VERSION) or vice versa. + String VERSION_SLASHY = "ExoPlayerLib/2.3.1"; + + /** + * The version of the library expressed as an integer, for example 1002003. *

    * Three digits are used for each component of {@link #VERSION}. For example "1.2.3" has the * corresponding integer version 1002003 (001-002-003), and "123.45.6" has the corresponding * integer version 123045006 (123-045-006). */ + // Intentionally hardcoded. Do not derive from other constants (e.g. VERSION) or vice versa. int VERSION_INT = 2003001; /** diff --git a/library/core/src/main/java/com/google/android/exoplayer2/util/Util.java b/library/core/src/main/java/com/google/android/exoplayer2/util/Util.java index 73ea18e2ca..206349fa07 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/util/Util.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/util/Util.java @@ -766,7 +766,7 @@ public final class Util { versionName = "?"; } return applicationName + "/" + versionName + " (Linux;Android " + Build.VERSION.RELEASE - + ") " + "ExoPlayerLib/" + ExoPlayerLibraryInfo.VERSION; + + ") " + ExoPlayerLibraryInfo.VERSION_SLASHY; } /** From 107e0ebc467612d216bb8b46f66b352ffd6bb254 Mon Sep 17 00:00:00 2001 From: andrewlewis Date: Mon, 3 Apr 2017 06:35:56 -0700 Subject: [PATCH 062/119] Fix scrubbing in timeline with too many windows. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=152003529 --- .../com/google/android/exoplayer2/ui/PlaybackControlView.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlaybackControlView.java b/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlaybackControlView.java index a5c667635a..dfa5e9abe0 100644 --- a/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlaybackControlView.java +++ b/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlaybackControlView.java @@ -817,7 +817,7 @@ public class PlaybackControlView extends FrameLayout { public void onScrubStop(TimeBar timeBar, long position, boolean canceled) { scrubbing = false; if (!canceled && player != null) { - if (showMultiWindowTimeBar) { + if (multiWindowTimeBar) { Timeline timeline = player.getCurrentTimeline(); int windowCount = timeline.getWindowCount(); long remainingMs = position; From dd2914f5809bcfa519b64c19668f6583c13e8b39 Mon Sep 17 00:00:00 2001 From: eguven Date: Mon, 3 Apr 2017 07:03:38 -0700 Subject: [PATCH 063/119] Generalize manifest parser parameters in DASH and SmoothStreaming MediaSources This allows custom parsers to be used. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=152005477 --- .../android/exoplayer2/upstream/ParsingLoadable.java | 4 ++-- .../exoplayer2/source/dash/DashMediaSource.java | 10 ++++++---- .../source/smoothstreaming/SsMediaSource.java | 10 ++++++---- 3 files changed, 14 insertions(+), 10 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/upstream/ParsingLoadable.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/ParsingLoadable.java index c25638ac86..adf245d9aa 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/upstream/ParsingLoadable.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/upstream/ParsingLoadable.java @@ -59,7 +59,7 @@ public final class ParsingLoadable implements Loadable { public final int type; private final DataSource dataSource; - private final Parser parser; + private final Parser parser; private volatile T result; private volatile boolean isCanceled; @@ -71,7 +71,7 @@ public final class ParsingLoadable implements Loadable { * @param type See {@link #type}. * @param parser Parses the object from the response. */ - public ParsingLoadable(DataSource dataSource, Uri uri, int type, Parser parser) { + public ParsingLoadable(DataSource dataSource, Uri uri, int type, Parser parser) { this.dataSource = dataSource; this.dataSpec = new DataSpec(uri, DataSpec.FLAG_ALLOW_GZIP); this.type = type; diff --git a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaSource.java b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaSource.java index 10f88c6460..5ab04ea7be 100644 --- a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaSource.java +++ b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaSource.java @@ -88,7 +88,7 @@ public final class DashMediaSource implements MediaSource { private final int minLoadableRetryCount; private final long livePresentationDelayMs; private final EventDispatcher eventDispatcher; - private final DashManifestParser manifestParser; + private final ParsingLoadable.Parser manifestParser; private final ManifestCallback manifestCallback; private final Object manifestUriLock; private final SparseArray periodsById; @@ -200,15 +200,17 @@ public final class DashMediaSource implements MediaSource { * @param eventListener A listener of events. May be null if delivery of events is not required. */ public DashMediaSource(Uri manifestUri, DataSource.Factory manifestDataSourceFactory, - DashManifestParser manifestParser, DashChunkSource.Factory chunkSourceFactory, - int minLoadableRetryCount, long livePresentationDelayMs, Handler eventHandler, + ParsingLoadable.Parser manifestParser, + DashChunkSource.Factory chunkSourceFactory, int minLoadableRetryCount, + long livePresentationDelayMs, Handler eventHandler, AdaptiveMediaSourceEventListener eventListener) { this(null, manifestUri, manifestDataSourceFactory, manifestParser, chunkSourceFactory, minLoadableRetryCount, livePresentationDelayMs, eventHandler, eventListener); } private DashMediaSource(DashManifest manifest, Uri manifestUri, - DataSource.Factory manifestDataSourceFactory, DashManifestParser manifestParser, + DataSource.Factory manifestDataSourceFactory, + ParsingLoadable.Parser manifestParser, DashChunkSource.Factory chunkSourceFactory, int minLoadableRetryCount, long livePresentationDelayMs, Handler eventHandler, AdaptiveMediaSourceEventListener eventListener) { diff --git a/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsMediaSource.java b/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsMediaSource.java index 0125d45525..e3fb8b606c 100644 --- a/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsMediaSource.java +++ b/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsMediaSource.java @@ -71,7 +71,7 @@ public final class SsMediaSource implements MediaSource, private final int minLoadableRetryCount; private final long livePresentationDelayMs; private final EventDispatcher eventDispatcher; - private final SsManifestParser manifestParser; + private final ParsingLoadable.Parser manifestParser; private final ArrayList mediaPeriods; private Listener sourceListener; @@ -171,15 +171,17 @@ public final class SsMediaSource implements MediaSource, * @param eventListener A listener of events. May be null if delivery of events is not required. */ public SsMediaSource(Uri manifestUri, DataSource.Factory manifestDataSourceFactory, - SsManifestParser manifestParser, SsChunkSource.Factory chunkSourceFactory, - int minLoadableRetryCount, long livePresentationDelayMs, Handler eventHandler, + ParsingLoadable.Parser manifestParser, + SsChunkSource.Factory chunkSourceFactory, int minLoadableRetryCount, + long livePresentationDelayMs, Handler eventHandler, AdaptiveMediaSourceEventListener eventListener) { this(null, manifestUri, manifestDataSourceFactory, manifestParser, chunkSourceFactory, minLoadableRetryCount, livePresentationDelayMs, eventHandler, eventListener); } private SsMediaSource(SsManifest manifest, Uri manifestUri, - DataSource.Factory manifestDataSourceFactory, SsManifestParser manifestParser, + DataSource.Factory manifestDataSourceFactory, + ParsingLoadable.Parser manifestParser, SsChunkSource.Factory chunkSourceFactory, int minLoadableRetryCount, long livePresentationDelayMs, Handler eventHandler, AdaptiveMediaSourceEventListener eventListener) { From 6a7db4b1ec4bf4864197bf5e9cabb0b744abebba Mon Sep 17 00:00:00 2001 From: andrewlewis Date: Tue, 4 Apr 2017 01:31:33 -0700 Subject: [PATCH 064/119] Remove unused variable and parameter. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=152107538 --- .../google/android/exoplayer2/ext/opus/OpusDecoder.java | 4 ++-- extensions/opus/src/main/jni/opus_jni.cc | 8 +++----- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/extensions/opus/src/main/java/com/google/android/exoplayer2/ext/opus/OpusDecoder.java b/extensions/opus/src/main/java/com/google/android/exoplayer2/ext/opus/OpusDecoder.java index 83e461d279..95c38c34bb 100644 --- a/extensions/opus/src/main/java/com/google/android/exoplayer2/ext/opus/OpusDecoder.java +++ b/extensions/opus/src/main/java/com/google/android/exoplayer2/ext/opus/OpusDecoder.java @@ -161,7 +161,7 @@ import java.util.List; cryptoInfo.key, cryptoInfo.iv, cryptoInfo.numSubSamples, cryptoInfo.numBytesOfClearData, cryptoInfo.numBytesOfEncryptedData) : opusDecode(nativeDecoderContext, inputBuffer.timeUs, inputData, inputData.limit(), - outputBuffer, SAMPLE_RATE); + outputBuffer); if (result < 0) { if (result == DRM_ERROR) { String message = "Drm error: " + opusGetErrorMessage(nativeDecoderContext); @@ -210,7 +210,7 @@ import java.util.List; private native long opusInit(int sampleRate, int channelCount, int numStreams, int numCoupled, int gain, byte[] streamMap); private native int opusDecode(long decoder, long timeUs, ByteBuffer inputBuffer, int inputSize, - SimpleOutputBuffer outputBuffer, int sampleRate); + SimpleOutputBuffer outputBuffer); private native int opusSecureDecode(long decoder, long timeUs, ByteBuffer inputBuffer, int inputSize, SimpleOutputBuffer outputBuffer, int sampleRate, ExoMediaCrypto mediaCrypto, int inputMode, byte[] key, byte[] iv, diff --git a/extensions/opus/src/main/jni/opus_jni.cc b/extensions/opus/src/main/jni/opus_jni.cc index a1d1ffe478..8d9c1a4152 100644 --- a/extensions/opus/src/main/jni/opus_jni.cc +++ b/extensions/opus/src/main/jni/opus_jni.cc @@ -93,16 +93,14 @@ DECODER_FUNC(jlong, opusInit, jint sampleRate, jint channelCount, } DECODER_FUNC(jint, opusDecode, jlong jDecoder, jlong jTimeUs, - jobject jInputBuffer, jint inputSize, jobject jOutputBuffer, - jint sampleRate) { + jobject jInputBuffer, jint inputSize, jobject jOutputBuffer) { OpusMSDecoder* decoder = reinterpret_cast(jDecoder); const uint8_t* inputBuffer = reinterpret_cast( env->GetDirectBufferAddress(jInputBuffer)); - const int32_t inputSampleCount = - opus_packet_get_nb_samples(inputBuffer, inputSize, sampleRate); - const jint outputSize = kMaxOpusOutputPacketSizeSamples * kBytesPerSample * channelCount; + const jint outputSize = + kMaxOpusOutputPacketSizeSamples * kBytesPerSample * channelCount; env->CallObjectMethod(jOutputBuffer, outputBufferInit, jTimeUs, outputSize); const jobject jOutputBufferData = env->CallObjectMethod(jOutputBuffer, From 8208a75f0a840f2b666ed2c8f1d4029d582d0d4f Mon Sep 17 00:00:00 2001 From: andrewlewis Date: Tue, 4 Apr 2017 03:53:03 -0700 Subject: [PATCH 065/119] Improve multi-window playback controls behavior. Only enable multi-window mode when the duration of every period in the timeline is known. Also, remove the warning logged when there are too many windows as it doesn't add much. The player's current period index was not masked while there were unacknowledged seeks. This led to the displayed position jumping, between when seekTo was called (after which the position would be masked but not the period index) and the seek being acknowledged (at which point the time bar's position would jump back to the seek position, due to the period index being resolved). Mask the period index, like the window index, to fix this behavior. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=152116040 --- .../android/exoplayer2/ExoPlayerImpl.java | 23 +++++++++- .../exoplayer2/ui/PlaybackControlView.java | 42 ++++++++++++------- 2 files changed, 49 insertions(+), 16 deletions(-) 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 7a3da68d02..b9ab94a543 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 @@ -65,6 +65,7 @@ import java.util.concurrent.CopyOnWriteArraySet; // Playback information when there is a pending seek/set source operation. private int maskingWindowIndex; + private int maskingPeriodIndex; private long maskingWindowPositionMs; /** @@ -187,6 +188,22 @@ import java.util.concurrent.CopyOnWriteArraySet; } pendingSeekAcks++; maskingWindowIndex = windowIndex; + if (timeline.isEmpty()) { + maskingPeriodIndex = 0; + } else { + timeline.getWindow(windowIndex, window); + long resolvedPositionMs = + positionMs == C.TIME_UNSET ? window.getDefaultPositionUs() : positionMs; + int periodIndex = window.firstPeriodIndex; + long periodPositionUs = window.getPositionInFirstPeriodUs() + C.msToUs(resolvedPositionMs); + long periodDurationUs = timeline.getPeriod(periodIndex, period).getDurationUs(); + while (periodDurationUs != C.TIME_UNSET && periodPositionUs >= periodDurationUs + && periodIndex < window.lastPeriodIndex) { + periodPositionUs -= periodDurationUs; + periodDurationUs = timeline.getPeriod(++periodIndex, period).getDurationUs(); + } + maskingPeriodIndex = periodIndex; + } if (positionMs == C.TIME_UNSET) { maskingWindowPositionMs = 0; internalPlayer.seekTo(timeline, windowIndex, C.TIME_UNSET); @@ -235,7 +252,11 @@ import java.util.concurrent.CopyOnWriteArraySet; @Override public int getCurrentPeriodIndex() { - return playbackInfo.periodIndex; + if (timeline.isEmpty() || pendingSeekAcks > 0) { + return maskingPeriodIndex; + } else { + return playbackInfo.periodIndex; + } } @Override diff --git a/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlaybackControlView.java b/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlaybackControlView.java index dfa5e9abe0..405595a5ed 100644 --- a/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlaybackControlView.java +++ b/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlaybackControlView.java @@ -21,7 +21,6 @@ import android.content.Context; import android.content.res.TypedArray; import android.os.SystemClock; import android.util.AttributeSet; -import android.util.Log; import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.View; @@ -34,6 +33,7 @@ import com.google.android.exoplayer2.PlaybackParameters; import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.source.TrackGroupArray; import com.google.android.exoplayer2.trackselection.TrackSelectionArray; +import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Util; import java.util.Arrays; import java.util.Formatter; @@ -345,8 +345,9 @@ public class PlaybackControlView extends FrameLayout { /** * Sets whether the time bar should show all windows, as opposed to just the current one. If the - * timeline has more than {@link #MAX_WINDOWS_FOR_MULTI_WINDOW_TIME_BAR} windows the time bar will - * fall back to showing a single window. + * timeline has a period with unknown duration or more than + * {@link #MAX_WINDOWS_FOR_MULTI_WINDOW_TIME_BAR} windows the time bar will fall back to showing a + * single window. * * @param showMultiWindowTimeBar Whether the time bar should show all windows. */ @@ -523,14 +524,8 @@ public class PlaybackControlView extends FrameLayout { if (player == null) { return; } - if (showMultiWindowTimeBar) { - if (player.getCurrentTimeline().getWindowCount() <= MAX_WINDOWS_FOR_MULTI_WINDOW_TIME_BAR) { - multiWindowTimeBar = true; - return; - } - Log.w(TAG, "Too many windows for multi-window time bar. Falling back to showing one window."); - } - multiWindowTimeBar = false; + multiWindowTimeBar = showMultiWindowTimeBar + && canShowMultiWindowTimeBar(player.getCurrentTimeline(), period); } private void updateProgress() { @@ -568,10 +563,7 @@ public class PlaybackControlView extends FrameLayout { } else { isInAdBreak = false; long periodDurationUs = period.getDurationUs(); - if (periodDurationUs == C.TIME_UNSET) { - durationUs = C.TIME_UNSET; - break; - } + Assertions.checkState(periodDurationUs != C.TIME_UNSET); long periodDurationInWindowUs = periodDurationUs; if (j == window.firstPeriodIndex) { periodDurationInWindowUs -= window.positionInFirstPeriodUs; @@ -797,6 +789,26 @@ public class PlaybackControlView extends FrameLayout { || keyCode == KeyEvent.KEYCODE_MEDIA_PREVIOUS; } + /** + * Returns whether the specified {@code timeline} can be shown on a multi-window time bar. + * + * @param timeline The {@link Timeline} to check. + * @param period A scratch {@link Timeline.Period} instance. + * @return Whether the specified timeline can be shown on a multi-window time bar. + */ + private static boolean canShowMultiWindowTimeBar(Timeline timeline, Timeline.Period period) { + if (timeline.getWindowCount() > MAX_WINDOWS_FOR_MULTI_WINDOW_TIME_BAR) { + return false; + } + int periodCount = timeline.getPeriodCount(); + for (int i = 0; i < periodCount; i++) { + if (timeline.getPeriod(i, period).durationUs == C.TIME_UNSET) { + return false; + } + } + return true; + } + private final class ComponentListener implements ExoPlayer.EventListener, TimeBar.OnScrubListener, OnClickListener { From 9bbc6d107188e4cb4b425933296d2e5a866fc71e Mon Sep 17 00:00:00 2001 From: andrewlewis Date: Tue, 4 Apr 2017 09:34:45 -0700 Subject: [PATCH 066/119] Clean up FFmpeg extension instructions. Also add instructions for building arm64-v8a and x86. Issue: #2561 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=152141270 --- extensions/ffmpeg/README.md | 61 ++++++++++++++++------- extensions/ffmpeg/src/main/jni/Android.mk | 7 +-- 2 files changed, 44 insertions(+), 24 deletions(-) diff --git a/extensions/ffmpeg/README.md b/extensions/ffmpeg/README.md index beafcb6a96..c34b1b68e9 100644 --- a/extensions/ffmpeg/README.md +++ b/extensions/ffmpeg/README.md @@ -31,21 +31,12 @@ FFMPEG_EXT_PATH="${EXOPLAYER_ROOT}/extensions/ffmpeg/src/main" NDK_PATH="" ``` -* Fetch and build FFmpeg. For example, to fetch and build for armv7a: +* Fetch and build FFmpeg. For example, to fetch and build for armeabi-v7a, + arm64-v8a and x86 on Linux x86_64: ``` -cd "${FFMPEG_EXT_PATH}/jni" && \ -git clone git://source.ffmpeg.org/ffmpeg ffmpeg && cd ffmpeg && \ -./configure \ - --libdir=android-libs/armeabi-v7a \ - --arch=arm \ - --cpu=armv7-a \ - --cross-prefix="${NDK_PATH}/toolchains/arm-linux-androideabi-4.9/prebuilt/linux-x86_64/bin/arm-linux-androideabi-" \ +COMMON_OPTIONS="\ --target-os=android \ - --sysroot="${NDK_PATH}/platforms/android-9/arch-arm/" \ - --extra-cflags="-march=armv7-a -mfloat-abi=softfp" \ - --extra-ldflags="-Wl,--fix-cortex-a8" \ - --extra-ldexeflags=-pie \ --disable-static \ --enable-shared \ --disable-doc \ @@ -57,22 +48,56 @@ git clone git://source.ffmpeg.org/ffmpeg ffmpeg && cd ffmpeg && \ --disable-postproc \ --disable-avfilter \ --disable-symver \ + --disable-swresample \ --enable-avresample \ --enable-decoder=vorbis \ --enable-decoder=opus \ --enable-decoder=flac \ - --enable-decoder=alac \ + " && \ +cd "${FFMPEG_EXT_PATH}/jni" && \ +git clone git://source.ffmpeg.org/ffmpeg ffmpeg && cd ffmpeg && \ +./configure \ + --libdir=android-libs/armeabi-v7a \ + --arch=arm \ + --cpu=armv7-a \ + --cross-prefix="${NDK_PATH}/toolchains/arm-linux-androideabi-4.9/prebuilt/linux-x86_64/bin/arm-linux-androideabi-" \ + --sysroot="${NDK_PATH}/platforms/android-9/arch-arm/" \ + --extra-cflags="-march=armv7-a -mfloat-abi=softfp" \ + --extra-ldflags="-Wl,--fix-cortex-a8" \ + --extra-ldexeflags=-pie \ + ${COMMON_OPTIONS} \ && \ -make -j4 && \ -make install-libs +make -j4 && make install-libs && \ +make clean && ./configure \ + --libdir=android-libs/arm64-v8a \ + --arch=aarch64 \ + --cpu=armv8-a \ + --cross-prefix="${NDK_PATH}/toolchains/aarch64-linux-android-4.9/prebuilt/linux-x86_64/bin/aarch64-linux-android-" \ + --sysroot="${NDK_PATH}/platforms/android-21/arch-arm64/" \ + --extra-ldexeflags=-pie \ + ${COMMON_OPTIONS} \ + && \ +make -j4 && make install-libs && \ +make clean && ./configure \ + --libdir=android-libs/x86 \ + --arch=x86 \ + --cpu=i686 \ + --cross-prefix="${NDK_PATH}/toolchains/x86-4.9/prebuilt/linux-x86_64/bin/i686-linux-android-" \ + --sysroot="${NDK_PATH}/platforms/android-9/arch-x86/" \ + --extra-ldexeflags=-pie \ + --disable-asm \ + ${COMMON_OPTIONS} \ + && \ +make -j4 && make install-libs && \ +make clean ``` -* Build the JNI native libraries. Repeat this step for any other architectures - you need to support. +* Build the JNI native libraries, setting `APP_ABI` to include the architectures + built in the previous step. For example: ``` cd "${FFMPEG_EXT_PATH}"/jni && \ -${NDK_PATH}/ndk-build APP_ABI=armeabi-v7a -j4 +${NDK_PATH}/ndk-build APP_ABI="armeabi-v7a arm64-v8a x86" -j4 ``` * In your project, you can add a dependency on the extension by using a rule diff --git a/extensions/ffmpeg/src/main/jni/Android.mk b/extensions/ffmpeg/src/main/jni/Android.mk index f435ab7e98..046f90a5b2 100644 --- a/extensions/ffmpeg/src/main/jni/Android.mk +++ b/extensions/ffmpeg/src/main/jni/Android.mk @@ -31,15 +31,10 @@ LOCAL_MODULE := libavresample LOCAL_SRC_FILES := ffmpeg/android-libs/$(TARGET_ARCH_ABI)/$(LOCAL_MODULE).so include $(PREBUILT_SHARED_LIBRARY) -include $(CLEAR_VARS) -LOCAL_MODULE := libswresample -LOCAL_SRC_FILES := ffmpeg/android-libs/$(TARGET_ARCH_ABI)/$(LOCAL_MODULE).so -include $(PREBUILT_SHARED_LIBRARY) - include $(CLEAR_VARS) LOCAL_MODULE := ffmpeg LOCAL_SRC_FILES := ffmpeg_jni.cc LOCAL_C_INCLUDES := ffmpeg -LOCAL_SHARED_LIBRARIES := libavcodec libavresample libavutil libswresample +LOCAL_SHARED_LIBRARIES := libavcodec libavresample libavutil LOCAL_LDLIBS := -Lffmpeg/android-libs/$(TARGET_ARCH_ABI) -llog include $(BUILD_SHARED_LIBRARY) From 6caa3e795f527328ef873c344399241c861f0cfb Mon Sep 17 00:00:00 2001 From: tonihei Date: Wed, 5 Apr 2017 04:29:26 -0700 Subject: [PATCH 067/119] Demo app repeatedly shows "audio/video not playable" toast Solved by adding saving reference to previous TrackGroup. Toast only displayed after TrackGroup changed. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=152242954 --- .../exoplayer2/demo/PlayerActivity.java | 24 ++++++++++++------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/demo/src/main/java/com/google/android/exoplayer2/demo/PlayerActivity.java b/demo/src/main/java/com/google/android/exoplayer2/demo/PlayerActivity.java index e79e557827..5a812e6e62 100644 --- a/demo/src/main/java/com/google/android/exoplayer2/demo/PlayerActivity.java +++ b/demo/src/main/java/com/google/android/exoplayer2/demo/PlayerActivity.java @@ -113,6 +113,7 @@ public class PlayerActivity extends Activity implements OnClickListener, ExoPlay private TrackSelectionHelper trackSelectionHelper; private DebugTextViewHelper debugViewHelper; private boolean needRetrySource; + private TrackGroupArray lastSeenTrackGroupArray; private boolean shouldAutoPlay; private int resumeWindow; @@ -261,6 +262,7 @@ public class PlayerActivity extends Activity implements OnClickListener, ExoPlay new AdaptiveTrackSelection.Factory(BANDWIDTH_METER); trackSelector = new DefaultTrackSelector(videoTrackSelectionFactory); trackSelectionHelper = new TrackSelectionHelper(trackSelector, videoTrackSelectionFactory); + lastSeenTrackGroupArray = null; player = ExoPlayerFactory.newSimpleInstance(this, trackSelector, new DefaultLoadControl(), drmSessionManager, extensionRendererMode); player.addListener(this); @@ -479,18 +481,22 @@ public class PlayerActivity extends Activity implements OnClickListener, ExoPlay } @Override + @SuppressWarnings("ReferenceEquality") public void onTracksChanged(TrackGroupArray trackGroups, TrackSelectionArray trackSelections) { updateButtonVisibilities(); - MappedTrackInfo mappedTrackInfo = trackSelector.getCurrentMappedTrackInfo(); - if (mappedTrackInfo != null) { - if (mappedTrackInfo.getTrackTypeRendererSupport(C.TRACK_TYPE_VIDEO) - == MappedTrackInfo.RENDERER_SUPPORT_UNSUPPORTED_TRACKS) { - showToast(R.string.error_unsupported_video); - } - if (mappedTrackInfo.getTrackTypeRendererSupport(C.TRACK_TYPE_AUDIO) - == MappedTrackInfo.RENDERER_SUPPORT_UNSUPPORTED_TRACKS) { - showToast(R.string.error_unsupported_audio); + if (trackGroups != lastSeenTrackGroupArray) { + MappedTrackInfo mappedTrackInfo = trackSelector.getCurrentMappedTrackInfo(); + if (mappedTrackInfo != null) { + if (mappedTrackInfo.getTrackTypeRendererSupport(C.TRACK_TYPE_VIDEO) + == MappedTrackInfo.RENDERER_SUPPORT_UNSUPPORTED_TRACKS) { + showToast(R.string.error_unsupported_video); + } + if (mappedTrackInfo.getTrackTypeRendererSupport(C.TRACK_TYPE_AUDIO) + == MappedTrackInfo.RENDERER_SUPPORT_UNSUPPORTED_TRACKS) { + showToast(R.string.error_unsupported_audio); + } } + lastSeenTrackGroupArray = trackGroups; } } From 156bc52c8f73154bd847c352acb9227324ff7301 Mon Sep 17 00:00:00 2001 From: Oliver Woodman Date: Thu, 6 Apr 2017 19:05:59 +0100 Subject: [PATCH 068/119] Clean up DVB support - Removed the PES_STRIPPED flag. It's unnecessary. We can strip PES in the TS extractor instead. - Made nearly all of the object classes in DvbParser immutable. Else it's non-obvious that none of this state can be mutated. - Made a a lot of the methods in DvbParser static for the same reason. - Removed unnecessary null checks, code that was never executed, unused fields etc. - Add proper flushing of DvbParser, to prevent corrupt output following a seek. --- library/core/proguard-rules.txt | 2 +- .../text/subrip/SubripDecoderTest.java | 16 +- .../exoplayer2/text/ttml/TtmlDecoderTest.java | 2 +- .../text/webvtt/Mp4WebvttDecoderTest.java | 8 +- .../text/webvtt/WebvttDecoderTest.java | 4 +- .../extractor/mkv/MatroskaExtractor.java | 5 +- .../ts/DefaultTsPayloadReaderFactory.java | 3 +- .../extractor/ts/DvbSubtitleReader.java | 111 + .../extractor/ts/DvbSubtitlesReader.java | 133 - .../exoplayer2/extractor/ts/TsExtractor.java | 11 +- .../extractor/ts/TsPayloadReader.java | 7 +- .../google/android/exoplayer2/text/Cue.java | 22 +- .../text/SimpleSubtitleDecoder.java | 6 +- .../text/SubtitleDecoderFactory.java | 4 +- .../exoplayer2/text/dvb/DvbDecoder.java | 36 +- .../exoplayer2/text/dvb/DvbParser.java | 2254 +++++++---------- .../exoplayer2/text/dvb/DvbSubtitle.java | 50 +- .../exoplayer2/text/subrip/SubripDecoder.java | 2 +- .../exoplayer2/text/ttml/TtmlDecoder.java | 3 +- .../exoplayer2/text/tx3g/Tx3gDecoder.java | 2 +- .../text/webvtt/Mp4WebvttDecoder.java | 3 +- .../exoplayer2/text/webvtt/WebvttDecoder.java | 3 +- .../exoplayer2/util/ParsableBitArray.java | 51 + .../exoplayer2/ui/SubtitlePainter.java | 2 +- 24 files changed, 1119 insertions(+), 1621 deletions(-) create mode 100644 library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/DvbSubtitleReader.java delete mode 100644 library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/DvbSubtitlesReader.java diff --git a/library/core/proguard-rules.txt b/library/core/proguard-rules.txt index 34d881f891..c5d752f4c6 100644 --- a/library/core/proguard-rules.txt +++ b/library/core/proguard-rules.txt @@ -7,4 +7,4 @@ } -keepclassmembers class com.google.android.exoplayer2.text.dvb.DvbDecoder { public (java.util.List); -} \ No newline at end of file +} diff --git a/library/core/src/androidTest/java/com/google/android/exoplayer2/text/subrip/SubripDecoderTest.java b/library/core/src/androidTest/java/com/google/android/exoplayer2/text/subrip/SubripDecoderTest.java index 502fa9a789..880a214fb3 100644 --- a/library/core/src/androidTest/java/com/google/android/exoplayer2/text/subrip/SubripDecoderTest.java +++ b/library/core/src/androidTest/java/com/google/android/exoplayer2/text/subrip/SubripDecoderTest.java @@ -36,7 +36,7 @@ public final class SubripDecoderTest extends InstrumentationTestCase { public void testDecodeEmpty() throws IOException { SubripDecoder decoder = new SubripDecoder(); byte[] bytes = TestUtil.getByteArray(getInstrumentation(), EMPTY_FILE); - SubripSubtitle subtitle = decoder.decode(bytes, bytes.length); + SubripSubtitle subtitle = decoder.decode(bytes, bytes.length, false); // Assert that the subtitle is empty. assertEquals(0, subtitle.getEventTimeCount()); assertTrue(subtitle.getCues(0).isEmpty()); @@ -45,7 +45,7 @@ public final class SubripDecoderTest extends InstrumentationTestCase { public void testDecodeTypical() throws IOException { SubripDecoder decoder = new SubripDecoder(); byte[] bytes = TestUtil.getByteArray(getInstrumentation(), TYPICAL_FILE); - SubripSubtitle subtitle = decoder.decode(bytes, bytes.length); + SubripSubtitle subtitle = decoder.decode(bytes, bytes.length, false); assertEquals(6, subtitle.getEventTimeCount()); assertTypicalCue1(subtitle, 0); assertTypicalCue2(subtitle, 2); @@ -55,7 +55,7 @@ public final class SubripDecoderTest extends InstrumentationTestCase { public void testDecodeTypicalWithByteOrderMark() throws IOException { SubripDecoder decoder = new SubripDecoder(); byte[] bytes = TestUtil.getByteArray(getInstrumentation(), TYPICAL_WITH_BYTE_ORDER_MARK); - SubripSubtitle subtitle = decoder.decode(bytes, bytes.length); + SubripSubtitle subtitle = decoder.decode(bytes, bytes.length, false); assertEquals(6, subtitle.getEventTimeCount()); assertTypicalCue1(subtitle, 0); assertTypicalCue2(subtitle, 2); @@ -65,7 +65,7 @@ public final class SubripDecoderTest extends InstrumentationTestCase { public void testDecodeTypicalExtraBlankLine() throws IOException { SubripDecoder decoder = new SubripDecoder(); byte[] bytes = TestUtil.getByteArray(getInstrumentation(), TYPICAL_EXTRA_BLANK_LINE); - SubripSubtitle subtitle = decoder.decode(bytes, bytes.length); + SubripSubtitle subtitle = decoder.decode(bytes, bytes.length, false); assertEquals(6, subtitle.getEventTimeCount()); assertTypicalCue1(subtitle, 0); assertTypicalCue2(subtitle, 2); @@ -76,7 +76,7 @@ public final class SubripDecoderTest extends InstrumentationTestCase { // Parsing should succeed, parsing the first and third cues only. SubripDecoder decoder = new SubripDecoder(); byte[] bytes = TestUtil.getByteArray(getInstrumentation(), TYPICAL_MISSING_TIMECODE); - SubripSubtitle subtitle = decoder.decode(bytes, bytes.length); + SubripSubtitle subtitle = decoder.decode(bytes, bytes.length, false); assertEquals(4, subtitle.getEventTimeCount()); assertTypicalCue1(subtitle, 0); assertTypicalCue3(subtitle, 2); @@ -86,7 +86,7 @@ public final class SubripDecoderTest extends InstrumentationTestCase { // Parsing should succeed, parsing the first and third cues only. SubripDecoder decoder = new SubripDecoder(); byte[] bytes = TestUtil.getByteArray(getInstrumentation(), TYPICAL_MISSING_SEQUENCE); - SubripSubtitle subtitle = decoder.decode(bytes, bytes.length); + SubripSubtitle subtitle = decoder.decode(bytes, bytes.length, false); assertEquals(4, subtitle.getEventTimeCount()); assertTypicalCue1(subtitle, 0); assertTypicalCue3(subtitle, 2); @@ -96,7 +96,7 @@ public final class SubripDecoderTest extends InstrumentationTestCase { // Parsing should succeed, parsing the third cue only. SubripDecoder decoder = new SubripDecoder(); byte[] bytes = TestUtil.getByteArray(getInstrumentation(), TYPICAL_NEGATIVE_TIMESTAMPS); - SubripSubtitle subtitle = decoder.decode(bytes, bytes.length); + SubripSubtitle subtitle = decoder.decode(bytes, bytes.length, false); assertEquals(2, subtitle.getEventTimeCount()); assertTypicalCue3(subtitle, 0); } @@ -104,7 +104,7 @@ public final class SubripDecoderTest extends InstrumentationTestCase { public void testDecodeNoEndTimecodes() throws IOException { SubripDecoder decoder = new SubripDecoder(); byte[] bytes = TestUtil.getByteArray(getInstrumentation(), NO_END_TIMECODES_FILE); - SubripSubtitle subtitle = decoder.decode(bytes, bytes.length); + SubripSubtitle subtitle = decoder.decode(bytes, bytes.length, false); // Test event count. assertEquals(3, subtitle.getEventTimeCount()); diff --git a/library/core/src/androidTest/java/com/google/android/exoplayer2/text/ttml/TtmlDecoderTest.java b/library/core/src/androidTest/java/com/google/android/exoplayer2/text/ttml/TtmlDecoderTest.java index d601775009..381aaa34ae 100644 --- a/library/core/src/androidTest/java/com/google/android/exoplayer2/text/ttml/TtmlDecoderTest.java +++ b/library/core/src/androidTest/java/com/google/android/exoplayer2/text/ttml/TtmlDecoderTest.java @@ -482,7 +482,7 @@ public final class TtmlDecoderTest extends InstrumentationTestCase { private TtmlSubtitle getSubtitle(String file) throws IOException, SubtitleDecoderException { TtmlDecoder ttmlDecoder = new TtmlDecoder(); byte[] bytes = TestUtil.getByteArray(getInstrumentation(), file); - return ttmlDecoder.decode(bytes, bytes.length); + return ttmlDecoder.decode(bytes, bytes.length, false); } } diff --git a/library/core/src/androidTest/java/com/google/android/exoplayer2/text/webvtt/Mp4WebvttDecoderTest.java b/library/core/src/androidTest/java/com/google/android/exoplayer2/text/webvtt/Mp4WebvttDecoderTest.java index a0feaea57d..2cdad081c5 100644 --- a/library/core/src/androidTest/java/com/google/android/exoplayer2/text/webvtt/Mp4WebvttDecoderTest.java +++ b/library/core/src/androidTest/java/com/google/android/exoplayer2/text/webvtt/Mp4WebvttDecoderTest.java @@ -81,14 +81,14 @@ public final class Mp4WebvttDecoderTest extends TestCase { public void testSingleCueSample() throws SubtitleDecoderException { Mp4WebvttDecoder decoder = new Mp4WebvttDecoder(); - Subtitle result = decoder.decode(SINGLE_CUE_SAMPLE, SINGLE_CUE_SAMPLE.length); + Subtitle result = decoder.decode(SINGLE_CUE_SAMPLE, SINGLE_CUE_SAMPLE.length, false); Cue expectedCue = new Cue("Hello World"); // Line feed must be trimmed by the decoder assertMp4WebvttSubtitleEquals(result, expectedCue); } public void testTwoCuesSample() throws SubtitleDecoderException { Mp4WebvttDecoder decoder = new Mp4WebvttDecoder(); - Subtitle result = decoder.decode(DOUBLE_CUE_SAMPLE, DOUBLE_CUE_SAMPLE.length); + Subtitle result = decoder.decode(DOUBLE_CUE_SAMPLE, DOUBLE_CUE_SAMPLE.length, false); Cue firstExpectedCue = new Cue("Hello World"); Cue secondExpectedCue = new Cue("Bye Bye"); assertMp4WebvttSubtitleEquals(result, firstExpectedCue, secondExpectedCue); @@ -96,7 +96,7 @@ public final class Mp4WebvttDecoderTest extends TestCase { public void testNoCueSample() throws SubtitleDecoderException { Mp4WebvttDecoder decoder = new Mp4WebvttDecoder(); - Subtitle result = decoder.decode(NO_CUE_SAMPLE, NO_CUE_SAMPLE.length); + Subtitle result = decoder.decode(NO_CUE_SAMPLE, NO_CUE_SAMPLE.length, false); assertMp4WebvttSubtitleEquals(result); } @@ -105,7 +105,7 @@ public final class Mp4WebvttDecoderTest extends TestCase { public void testSampleWithIncompleteHeader() { Mp4WebvttDecoder decoder = new Mp4WebvttDecoder(); try { - decoder.decode(INCOMPLETE_HEADER_SAMPLE, INCOMPLETE_HEADER_SAMPLE.length); + decoder.decode(INCOMPLETE_HEADER_SAMPLE, INCOMPLETE_HEADER_SAMPLE.length, false); } catch (SubtitleDecoderException e) { return; } diff --git a/library/core/src/androidTest/java/com/google/android/exoplayer2/text/webvtt/WebvttDecoderTest.java b/library/core/src/androidTest/java/com/google/android/exoplayer2/text/webvtt/WebvttDecoderTest.java index 6ed0518e3c..e48a2b8b03 100644 --- a/library/core/src/androidTest/java/com/google/android/exoplayer2/text/webvtt/WebvttDecoderTest.java +++ b/library/core/src/androidTest/java/com/google/android/exoplayer2/text/webvtt/WebvttDecoderTest.java @@ -49,7 +49,7 @@ public class WebvttDecoderTest extends InstrumentationTestCase { WebvttDecoder decoder = new WebvttDecoder(); byte[] bytes = TestUtil.getByteArray(getInstrumentation(), EMPTY_FILE); try { - decoder.decode(bytes, bytes.length); + decoder.decode(bytes, bytes.length, false); fail(); } catch (SubtitleDecoderException expected) { // Do nothing. @@ -194,7 +194,7 @@ public class WebvttDecoderTest extends InstrumentationTestCase { SubtitleDecoderException { WebvttDecoder decoder = new WebvttDecoder(); byte[] bytes = TestUtil.getByteArray(getInstrumentation(), asset); - return decoder.decode(bytes, bytes.length); + return decoder.decode(bytes, bytes.length, false); } private Spanned getUniqueSpanTextAt(WebvttSubtitle sub, long timeUs) { diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mkv/MatroskaExtractor.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mkv/MatroskaExtractor.java index 31b2c41f85..cf31a0bbd5 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mkv/MatroskaExtractor.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mkv/MatroskaExtractor.java @@ -1465,8 +1465,9 @@ public final class MatroskaExtractor implements Extractor { break; case CODEC_ID_DVBSUB: mimeType = MimeTypes.APPLICATION_DVBSUBS; - initializationData = Collections.singletonList(new byte[] { - (byte) 0x01, codecPrivate[0], codecPrivate[1], codecPrivate[2], codecPrivate[3]}); + // Init data: composition_page (2), ancillary_page (2) + initializationData = Collections.singletonList(new byte[] {codecPrivate[0], + codecPrivate[1], codecPrivate[2], codecPrivate[3]}); break; default: throw new ParserException("Unrecognized codec identifier."); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/DefaultTsPayloadReaderFactory.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/DefaultTsPayloadReaderFactory.java index d2b0acca85..1e391c3eca 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/DefaultTsPayloadReaderFactory.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/DefaultTsPayloadReaderFactory.java @@ -110,7 +110,8 @@ public final class DefaultTsPayloadReaderFactory implements TsPayloadReader.Fact case TsExtractor.TS_STREAM_TYPE_ID3: return new PesReader(new Id3Reader()); case TsExtractor.TS_STREAM_TYPE_DVBSUBS: - return new PesReader(new DvbSubtitlesReader(esInfo)); + return new PesReader( + new DvbSubtitleReader(esInfo.language, esInfo.dvbSubtitleInitializationData)); default: return null; } 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 new file mode 100644 index 0000000000..f228432a77 --- /dev/null +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/DvbSubtitleReader.java @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.exoplayer2.extractor.ts; + +import com.google.android.exoplayer2.C; +import com.google.android.exoplayer2.Format; +import com.google.android.exoplayer2.extractor.ExtractorOutput; +import com.google.android.exoplayer2.extractor.TrackOutput; +import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.TrackIdGenerator; +import com.google.android.exoplayer2.util.MimeTypes; +import com.google.android.exoplayer2.util.ParsableByteArray; +import java.util.Collections; +import java.util.List; + +/** + * Parses DVB subtitle data and extracts individual frames. + */ +public final class DvbSubtitleReader implements ElementaryStreamReader { + + private final String language; + private final List initializationData; + + private TrackOutput output; + private boolean writingSample; + private int bytesToCheck; + private int sampleBytesWritten; + private long sampleTimeUs; + + /** + * @param language The subtitle language code. + * @param initializationData Initialization data to be included in the track {@link Format}. + */ + public DvbSubtitleReader(String language, byte[] initializationData) { + this.language = language; + this.initializationData = Collections.singletonList(initializationData); + } + + @Override + public void seek() { + writingSample = false; + } + + @Override + public void createTracks(ExtractorOutput extractorOutput, TrackIdGenerator idGenerator) { + idGenerator.generateNewId(); + this.output = extractorOutput.track(idGenerator.getTrackId(), C.TRACK_TYPE_TEXT); + output.format(Format.createImageSampleFormat(idGenerator.getFormatId(), + MimeTypes.APPLICATION_DVBSUBS, null, Format.NO_VALUE, initializationData, language, null)); + } + + @Override + public void packetStarted(long pesTimeUs, boolean dataAlignmentIndicator) { + if (!dataAlignmentIndicator) { + return; + } + writingSample = true; + sampleTimeUs = pesTimeUs; + sampleBytesWritten = 0; + bytesToCheck = 2; + } + + @Override + public void packetFinished() { + if (writingSample) { + output.sampleMetadata(sampleTimeUs, C.BUFFER_FLAG_KEY_FRAME, sampleBytesWritten, 0, null); + writingSample = false; + } + } + + @Override + public void consume(ParsableByteArray data) { + if (writingSample) { + if (bytesToCheck == 2 && !checkNextByte(data, 0x20)) { + // Failed to check data_identifier + return; + } + if (bytesToCheck == 1 && !checkNextByte(data, 0x00)) { + // Check and discard the subtitle_stream_id + return; + } + int bytesAvailable = data.bytesLeft(); + output.sampleData(data, bytesAvailable); + sampleBytesWritten += bytesAvailable; + } + } + + private boolean checkNextByte(ParsableByteArray data, int expectedValue) { + if (data.bytesLeft() == 0) { + return false; + } + if (data.readUnsignedByte() != expectedValue) { + writingSample = false; + } + bytesToCheck--; + return writingSample; + } + +} diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/DvbSubtitlesReader.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/DvbSubtitlesReader.java deleted file mode 100644 index 240bfb987d..0000000000 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/DvbSubtitlesReader.java +++ /dev/null @@ -1,133 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.google.android.exoplayer2.extractor.ts; - - -import com.google.android.exoplayer2.C; -import com.google.android.exoplayer2.Format; -import com.google.android.exoplayer2.extractor.ExtractorOutput; -import com.google.android.exoplayer2.extractor.TrackOutput; -import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.TrackIdGenerator; -import com.google.android.exoplayer2.util.MimeTypes; -import com.google.android.exoplayer2.util.ParsableByteArray; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - - -/** - * Output PES packets to a {@link TrackOutput}. - */ -public final class DvbSubtitlesReader implements ElementaryStreamReader { - - private class SubtitleTrack { - private String language; - private List initializationData; - } - - private List subtitles = new ArrayList<>(); - - private long sampleTimeUs; - private int sampleBytesWritten; - private boolean writingSample; - - private List outputTracks = new ArrayList<>(); - - public DvbSubtitlesReader(TsPayloadReader.EsInfo esInfo) { - int pos = 2; - - while (pos < esInfo.descriptorBytes.length) { - SubtitleTrack subtitle = new SubtitleTrack(); - subtitle.language = new String(new byte[] { - esInfo.descriptorBytes[pos], - esInfo.descriptorBytes[pos + 1], - esInfo.descriptorBytes[pos + 2]}); - - if (((esInfo.descriptorBytes[pos + 3] & 0xF0 ) >> 4 ) == 2 ) { - subtitle.language += " for hard of hearing"; - } - - subtitle.initializationData = Collections.singletonList(new byte[] {(byte) 0x00, - esInfo.descriptorBytes[pos + 4], esInfo.descriptorBytes[pos + 5], - esInfo.descriptorBytes[pos + 6], esInfo.descriptorBytes[pos + 7]}); - - subtitles.add(subtitle); - pos += 8; - } - } - - - @Override - public void seek() { - writingSample = false; - } - - @Override - public void createTracks(ExtractorOutput extractorOutput, TrackIdGenerator idGenerator) { - TrackOutput output; - SubtitleTrack subtitle; - - for (int i = 0; i < subtitles.size(); i++) { - subtitle = subtitles.get(i); - idGenerator.generateNewId(); - output = extractorOutput.track(idGenerator.getTrackId(), C.TRACK_TYPE_TEXT); - output.format(Format.createImageSampleFormat(idGenerator.getFormatId(), - MimeTypes.APPLICATION_DVBSUBS, null, Format.NO_VALUE, - subtitle.initializationData, subtitle.language, null)); - outputTracks.add(output); - } - } - - - @Override - public void packetStarted(long pesTimeUs, boolean dataAlignmentIndicator) { - if (!dataAlignmentIndicator) { - return; - } - writingSample = true; - sampleTimeUs = pesTimeUs; - sampleBytesWritten = 0; - } - - @Override - public void packetFinished() { - TrackOutput output; - - for (int i = 0; i < outputTracks.size(); i++) { - output = outputTracks.get(i); - output.sampleMetadata(sampleTimeUs, C.BUFFER_FLAG_KEY_FRAME, sampleBytesWritten, 0, null); - } - writingSample = false; - } - - @Override - public void consume(ParsableByteArray data) { - if (writingSample) { - int bytesAvailable = data.bytesLeft(); - TrackOutput output; - int dataPosition = data.getPosition(); - - for (int i = 0; i < outputTracks.size(); i++) { - data.setPosition(dataPosition); - output = outputTracks.get(i); - output.sampleData(data, bytesAvailable); - } - - sampleBytesWritten += bytesAvailable; - } - } -} \ No newline at end of file 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 f80c470b59..e242414ff2 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 @@ -92,7 +92,7 @@ public final class TsExtractor implements Extractor { public static final int TS_STREAM_TYPE_H265 = 0x24; public static final int TS_STREAM_TYPE_ID3 = 0x15; public static final int TS_STREAM_TYPE_SPLICE_INFO = 0x86; - public static final int TS_STREAM_TYPE_DVBSUBS = 0x59; + public static final int TS_STREAM_TYPE_DVBSUBS = 0x59; private static final int TS_PACKET_SIZE = 188; private static final int TS_SYNC_BYTE = 0x47; // First byte of each TS packet. @@ -408,7 +408,7 @@ public final class TsExtractor implements Extractor { if (mode == MODE_HLS && id3Reader == null) { // Setup an ID3 track regardless of whether there's a corresponding entry, in case one // appears intermittently during playback. See [Internal: b/20261500]. - EsInfo dummyEsInfo = new EsInfo(TS_STREAM_TYPE_ID3, null, new byte[0]); + EsInfo dummyEsInfo = new EsInfo(TS_STREAM_TYPE_ID3, null, null, new byte[0]); id3Reader = payloadReaderFactory.createPayloadReader(TS_STREAM_TYPE_ID3, dummyEsInfo); id3Reader.init(timestampAdjuster, output, new TrackIdGenerator(programNumber, TS_STREAM_TYPE_ID3, MAX_PID_PLUS_ONE)); @@ -478,6 +478,7 @@ public final class TsExtractor implements Extractor { int descriptorsEndPosition = descriptorsStartPosition + length; int streamType = -1; String language = null; + byte[] dvbSubtitleInitializationData = null; while (data.getPosition() < descriptorsEndPosition) { int descriptorTag = data.readUnsignedByte(); int descriptorLength = data.readUnsignedByte(); @@ -503,12 +504,16 @@ public final class TsExtractor implements Extractor { } else if (descriptorTag == TS_PMT_DESC_DVBSUBS) { streamType = TS_STREAM_TYPE_DVBSUBS; language = new String(data.data, data.getPosition(), 3).trim(); + data.skipBytes(4); // Skip language (3) + subtitling_type (1) + // Init data: composition_page (2), ancillary_page (2) + dvbSubtitleInitializationData = new byte[4]; + data.readBytes(dvbSubtitleInitializationData, 0, 4); } // Skip unused bytes of current descriptor. data.skipBytes(positionOfNextDescriptor - data.getPosition()); } data.setPosition(descriptorsEndPosition); - return new EsInfo(streamType, language, + return new EsInfo(streamType, language, dvbSubtitleInitializationData, Arrays.copyOfRange(data.data, descriptorsStartPosition, descriptorsEndPosition)); } 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 4169e0f3a0..a6ebf770b4 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 @@ -60,17 +60,22 @@ public interface TsPayloadReader { public final int streamType; public final String language; + public final byte[] dvbSubtitleInitializationData; public final byte[] descriptorBytes; /** * @param streamType The type of the stream as defined by the * {@link TsExtractor}{@code .TS_STREAM_TYPE_*}. * @param language The language of the stream, as defined by ISO/IEC 13818-1, section 2.6.18. + * @param dvbSubtitleInitializationData If the descriptors include a DVB subtitle tag, this is + * the corresponding decoder initialization data. Null otherwise. * @param descriptorBytes The descriptor bytes associated to the stream. */ - public EsInfo(int streamType, String language, byte[] descriptorBytes) { + public EsInfo(int streamType, String language, byte[] dvbSubtitleInitializationData, + byte[] descriptorBytes) { this.streamType = streamType; this.language = language; + this.dvbSubtitleInitializationData = dvbSubtitleInitializationData; this.descriptorBytes = descriptorBytes; } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/text/Cue.java b/library/core/src/main/java/com/google/android/exoplayer2/text/Cue.java index f31cf4387d..5ae1f35b7e 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/text/Cue.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/text/Cue.java @@ -168,8 +168,9 @@ public class Cue { public final float size; /** - * The bitmap height as a fraction of the of the viewport size, or -1 if the bitmap should be - * displayed at its natural height given for its specified {@link #size}. + * The bitmap height as a fraction of the of the viewport size, or {@link #DIMEN_UNSET} if the + * bitmap should be displayed at its natural height given the bitmap dimensions and the specified + * {@link #size}. */ public final float bitmapHeight; @@ -195,14 +196,15 @@ public class Cue { * fraction of the viewport height. * @param verticalPositionAnchor The vertical anchor. One of {@link #ANCHOR_TYPE_START}, * {@link #ANCHOR_TYPE_MIDDLE}, {@link #ANCHOR_TYPE_END} and {@link #TYPE_UNSET}. - * @param width The width of the cue, expressed as a fraction of the viewport width. - * @param height The width of the cue, expressed as a fraction of the viewport width. + * @param width The width of the cue as a fraction of the viewport width. + * @param height The height of the cue as a fraction of the viewport height, or + * {@link #DIMEN_UNSET} if the bitmap should be displayed at its natural height for the + * specified {@code width}. */ public Cue(Bitmap bitmap, float horizontalPosition, @AnchorType int horizontalPositionAnchor, - float verticalPosition, @AnchorType int verticalPositionAnchor, float width, float - height) { + float verticalPosition, @AnchorType int verticalPositionAnchor, float width, float height) { this(null, null, bitmap, verticalPosition, LINE_TYPE_FRACTION, verticalPositionAnchor, - horizontalPosition, horizontalPositionAnchor, width, height, false, Color.BLACK); + horizontalPosition, horizontalPositionAnchor, width, height, false, Color.BLACK); } /** @@ -248,10 +250,10 @@ public class Cue { * @param windowColor See {@link #windowColor}. */ public Cue(CharSequence text, Alignment textAlignment, float line, @LineType int lineType, - @AnchorType int lineAnchor, float position, @AnchorType int positionAnchor, float size, - boolean windowColorSet, int windowColor) { + @AnchorType int lineAnchor, float position, @AnchorType int positionAnchor, float size, + boolean windowColorSet, int windowColor) { this(text, textAlignment, null, line, lineType, lineAnchor, position, positionAnchor, size, - -1, windowColorSet, windowColor); + DIMEN_UNSET, windowColorSet, windowColor); } private Cue(CharSequence text, Alignment textAlignment, Bitmap bitmap, float line, diff --git a/library/core/src/main/java/com/google/android/exoplayer2/text/SimpleSubtitleDecoder.java b/library/core/src/main/java/com/google/android/exoplayer2/text/SimpleSubtitleDecoder.java index dd25ef8345..6955f775dd 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/text/SimpleSubtitleDecoder.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/text/SimpleSubtitleDecoder.java @@ -67,7 +67,7 @@ public abstract class SimpleSubtitleDecoder extends SubtitleOutputBuffer outputBuffer, boolean reset) { try { ByteBuffer inputData = inputBuffer.data; - Subtitle subtitle = decode(inputData.array(), inputData.limit()); + Subtitle subtitle = decode(inputData.array(), inputData.limit(), reset); outputBuffer.setContent(inputBuffer.timeUs, subtitle, inputBuffer.subsampleOffsetUs); // Clear BUFFER_FLAG_DECODE_ONLY (see [Internal: b/27893809]). outputBuffer.clearFlag(C.BUFFER_FLAG_DECODE_ONLY); @@ -82,9 +82,11 @@ public abstract class SimpleSubtitleDecoder extends * * @param data An array holding the data to be decoded, starting at position 0. * @param size The size of the data to be decoded. + * @param reset Whether the decoder must be reset before decoding. * @return The decoded {@link Subtitle}. * @throws SubtitleDecoderException If a decoding error occurs. */ - protected abstract Subtitle decode(byte[] data, int size) throws SubtitleDecoderException; + protected abstract Subtitle decode(byte[] data, int size, boolean reset) + throws SubtitleDecoderException; } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/text/SubtitleDecoderFactory.java b/library/core/src/main/java/com/google/android/exoplayer2/text/SubtitleDecoderFactory.java index 83f5c0a290..5f318916b5 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/text/SubtitleDecoderFactory.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/text/SubtitleDecoderFactory.java @@ -24,7 +24,6 @@ import com.google.android.exoplayer2.text.tx3g.Tx3gDecoder; import com.google.android.exoplayer2.text.webvtt.Mp4WebvttDecoder; import com.google.android.exoplayer2.text.webvtt.WebvttDecoder; import com.google.android.exoplayer2.util.MimeTypes; - import java.util.List; /** @@ -86,7 +85,8 @@ public interface SubtitleDecoderFactory { return clazz.asSubclass(SubtitleDecoder.class).getConstructor(Integer.TYPE) .newInstance(format.accessibilityChannel); } else if (format.sampleMimeType.equals(MimeTypes.APPLICATION_DVBSUBS)) { - return clazz.asSubclass(SubtitleDecoder.class).getConstructor(List.class).newInstance(format.initializationData); + return clazz.asSubclass(SubtitleDecoder.class).getConstructor(List.class) + .newInstance(format.initializationData); } else { return clazz.asSubclass(SubtitleDecoder.class).getConstructor().newInstance(); } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/text/dvb/DvbDecoder.java b/library/core/src/main/java/com/google/android/exoplayer2/text/dvb/DvbDecoder.java index 6b2d3dc5e3..dbdc0434a1 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/text/dvb/DvbDecoder.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/text/dvb/DvbDecoder.java @@ -16,7 +16,7 @@ package com.google.android.exoplayer2.text.dvb; import com.google.android.exoplayer2.text.SimpleSubtitleDecoder; - +import com.google.android.exoplayer2.util.ParsableByteArray; import java.util.List; /** @@ -26,27 +26,25 @@ public final class DvbDecoder extends SimpleSubtitleDecoder { private final DvbParser parser; + /** + * @param initializationData The initialization data for the decoder. The initialization data + * must consist of a single byte array containing 5 bytes: flag_pes_stripped (1), + * composition_page (2), ancillary_page (2). + */ public DvbDecoder(List initializationData) { super("DvbDecoder"); - - int subtitleCompositionPage = 1; - int subtitleAncillaryPage = 1; - int flags = 0; - byte[] tempByteArray; - - if ((tempByteArray = initializationData.get(0)) != null && tempByteArray.length == 5) { - if (tempByteArray[0] == 0x01) { - flags |= DvbParser.FLAG_PES_STRIPPED_DVBSUB; - } - subtitleCompositionPage = ((tempByteArray[1] & 0xFF) << 8) | (tempByteArray[2] & 0xFF); - subtitleAncillaryPage = ((tempByteArray[3] & 0xFF) << 8) | (tempByteArray[4] & 0xFF); - } - - parser = new DvbParser(subtitleCompositionPage, subtitleAncillaryPage, flags); + ParsableByteArray data = new ParsableByteArray(initializationData.get(0)); + int subtitleCompositionPage = data.readUnsignedShort(); + int subtitleAncillaryPage = data.readUnsignedShort(); + parser = new DvbParser(subtitleCompositionPage, subtitleAncillaryPage); } @Override - protected DvbSubtitle decode(byte[] data, int length) { - return new DvbSubtitle(parser.dvbSubsDecode(data, length)); + protected DvbSubtitle decode(byte[] data, int length, boolean reset) { + if (reset) { + parser.reset(); + } + return new DvbSubtitle(parser.decode(data, length)); } -} \ No newline at end of file + +} diff --git a/library/core/src/main/java/com/google/android/exoplayer2/text/dvb/DvbParser.java b/library/core/src/main/java/com/google/android/exoplayer2/text/dvb/DvbParser.java index e2254cf007..96c8a89801 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/text/dvb/DvbParser.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/text/dvb/DvbParser.java @@ -20,1550 +20,1006 @@ import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.PorterDuff; -import android.graphics.DashPathEffect; import android.graphics.PorterDuffXfermode; import android.graphics.Region; -import android.support.annotation.IntDef; import android.util.Log; import android.util.SparseArray; - -import com.google.android.exoplayer2.core.BuildConfig; import com.google.android.exoplayer2.text.Cue; import com.google.android.exoplayer2.util.ParsableBitArray; - +import com.google.android.exoplayer2.util.Util; import java.util.ArrayList; +import java.util.Collections; import java.util.List; /** - * Parse and generate a list of {@link Cue}s from DVB subtitling bitstream + * Parses {@link Cue}s from a DVB subtitle bitstream. */ -public class DvbParser { +/* package */ final class DvbParser { - private static final String TAG = "DVBSubs"; + private static final String TAG = "DvbParser"; - @IntDef(flag = true, value = {FLAG_PES_STRIPPED_DVBSUB}) - public @interface Flags { - } + // Segment types, as defined by ETSI EN 300 743 Table 2 + private static final int SEGMENT_TYPE_PAGE_COMPOSITION = 0x10; + private static final int SEGMENT_TYPE_REGION_COMPOSITION = 0x11; + private static final int SEGMENT_TYPE_CLUT_DEFINITION = 0x12; + private static final int SEGMENT_TYPE_OBJECT_DATA = 0x13; + private static final int SEGMENT_TYPE_DISPLAY_DEFINITION = 0x14; - public static final int FLAG_PES_STRIPPED_DVBSUB = 1; + // Page states, as defined by ETSI EN 300 743 Table 3 + private static final int PAGE_STATE_NORMAL = 0; // Update. Only changed elements. + // private static final int PAGE_STATE_ACQUISITION = 1; // Refresh. All elements. + // private static final int PAGE_STATE_CHANGE = 2; // New. All elements. - @Flags - private final int flags; + // Region depths, as defined by ETSI EN 300 743 Table 5 + // private static final int REGION_DEPTH_2_BIT = 1; + private static final int REGION_DEPTH_4_BIT = 2; + private static final int REGION_DEPTH_8_BIT = 3; - /* List of different SEGMENT TYPES */ - /* According to EN 300-743, table 2 */ - private final static int DVBSUB_ST_PAGE_COMPOSITION = 0x10; - private final static int DVBSUB_ST_REGION_COMPOSITION = 0x11; - private final static int DVBSUB_ST_CLUT_DEFINITION = 0x12; - private final static int DVBSUB_ST_OBJECT_DATA = 0x13; - private final static int DVBSUB_ST_DISPLAY_DEFINITION = 0x14; - private final static int DVBSUB_ST_ENDOFDISPLAY = 0x80; - private final static int DVBSUB_ST_STUFFING = 0xff; + // Object codings, as defined by ETSI EN 300 743 Table 8 + private static final int OBJECT_CODING_PIXELS = 0; + private static final int OBJECT_CODING_STRING = 1; - /* List of different Page Composition Segment state */ - /* According to EN 300-743, 7.2.1 table 3 */ - private final static int DVBSUB_PCS_STATE_NORMAL = 0b00; // Update. Only changed elements. - private final static int DVBSUB_PCS_STATE_ACQUISITION = 0b01; // Refresh. All subtitle elements. - private final static int DVBSUB_PCS_STATE_CHANGE = 0b10; // New. All subtitle elements. + // Pixel-data types, as defined by ETSI EN 300 743 Table 9 + private static final int DATA_TYPE_2BP_CODE_STRING = 0x10; + private static final int DATA_TYPE_4BP_CODE_STRING = 0x11; + private static final int DATA_TYPE_8BP_CODE_STRING = 0x12; + private static final int DATA_TYPE_24_TABLE_DATA = 0x20; + private static final int DATA_TYPE_28_TABLE_DATA = 0x21; + private static final int DATA_TYPE_48_TABLE_DATA = 0x22; + private static final int DATA_TYPE_END_LINE = 0xF0; - /* List of different Region Composition Segments CLUT level oc compatibility */ - /* According to EN 300-743, 7.2.1 table 4 */ - private final static int DVBSUB_RCS_CLUT_2 = 0x01; - private final static int DVBSUB_RCS_CLUT_4 = 0x02; - private final static int DVBSUB_RCS_CLUT_8 = 0x03; - - /* List of different Region Composition Segments bit depths */ - /* According to EN 300-743, 7.2.1 table 5 */ - private final static int DVBSUB_RCS_BITDEPTH_2 = 0x01; - private final static int DVBSUB_RCS_BITDEPTH_4 = 0x02; - private final static int DVBSUB_RCS_BITDEPTH_8 = 0x03; - - /* List of different object types in the Region Composition Segment */ - /* According to EN 300-743, table 6 */ - private final static int DVBSUB_OT_BASIC_BITMAP = 0x00; - private final static int DVBSUB_OT_BASIC_CHAR = 0x01; - private final static int DVBSUB_OT_COMPOSITE_STRING = 0x02; - - /* List of different object coding methods in the Object Data Segment */ - /* According to EN 300-743, table 8 */ - private static final int DVBSUB_ODS_PIXEL_CODED = 0x00; - private static final int DVBSUB_ODS_CHAR_CODED = 0x01; - - /* Pixel DATA TYPES */ - /* According to EN 300-743, table 9 */ - private final static int DVBSUB_DT_2BP_CODE_STRING = 0x10; - private final static int DVBSUB_DT_4BP_CODE_STRING = 0x11; - private final static int DVBSUB_DT_8BP_CODE_STRING = 0x12; - private final static int DVBSUB_DT_24_TABLE_DATA = 0x20; - private final static int DVBSUB_DT_28_TABLE_DATA = 0x21; - private final static int DVBSUB_DT_48_TABLE_DATA = 0x22; - private final static int DVBSUB_DT_END_LINE = 0xf0; - - /* Clut mapping tables */ - /* According to EN 300-743, 10.4 10.5 10.6 */ - private byte[] defaultMap24 = {(byte) 0x00, (byte) 0x07, (byte) 0x08, (byte) 0x0f}; - private byte[] defaultMap28 = {(byte) 0x00, (byte) 0x77, (byte) 0x88, (byte) 0xff}; - private byte[] defaultMap48 = {(byte) 0x00, (byte) 0x11, (byte) 0x22, (byte) 0x33, + // Clut mapping tables, as defined by ETSI EN 300 743 10.4, 10.5, 10.6 + private static final byte[] defaultMap2To4 = { + (byte) 0x00, (byte) 0x07, (byte) 0x08, (byte) 0x0F}; + private static final byte[] defaultMap2To8 = { + (byte) 0x00, (byte) 0x77, (byte) 0x88, (byte) 0xFF}; + private static final byte[] defaultMap4To8 = { + (byte) 0x00, (byte) 0x11, (byte) 0x22, (byte) 0x33, (byte) 0x44, (byte) 0x55, (byte) 0x66, (byte) 0x77, - (byte) 0x88, (byte) 0x99, (byte) 0xaa, (byte) 0xbb, - (byte) 0xcc, (byte) 0xdd, (byte) 0xee, (byte) 0xff}; + (byte) 0x88, (byte) 0x99, (byte) 0xAA, (byte) 0xBB, + (byte) 0xCC, (byte) 0xDD, (byte) 0xEE, (byte) 0xFF}; - /* FLAGS */ - private final static int DISPLAY_WINDOW_FLAG = 0x01; + private final Paint defaultPaint; + private final Paint fillRegionPaint; + private final Canvas canvas; + private final DisplayDefinition defaultDisplayDefinition; + private final ClutDefinition defaultClutDefinition; + private final SubtitleService subtitleService; - private final static int REGION_FILL_FLAG = 0x01; - - private final static int OBJECT_NON_MODIFYING_COLOUR_FLAG = 0x01; - - /* instance variables */ - private Paint defaultPaint = new Paint(); - private Paint fillRegionPaint = new Paint(); - private Paint debugRegionPaint = new Paint(); - private Paint debugObjectPaint = new Paint(); private Bitmap bitmap; - private Canvas canvas = new Canvas(); - private ClutDefinition defaultClut = new ClutDefinition(); - private static ParsableBitArray tsStream; - private SubtitleService subtitleService; - - /* - * Contains the current subtitle service definition + /** + * Construct an instance for the given subtitle and ancillary page ids. + * + * @param subtitlePageId The id of the subtitle page carrying the subtitle to be parsed. + * @param ancillaryPageId The id of the ancillary page containing additional data. */ - private class SubtitleService { - int subtitlePageId; - int ancillaryPageId; - - // subtitle page - DisplayDefinition displayDefinition; - PageComposition pageComposition; - SparseArray regions = new SparseArray<>(); - SparseArray cluts = new SparseArray<>(); - SparseArray objects = new SparseArray<>(); - - // ancillary page - SparseArray ancillaryCluts = new SparseArray<>(); - SparseArray ancillaryObjects = new SparseArray<>(); - } - - /* The display definition contains the geometry and active area of the subtitle service [7.2.1] */ - private class DisplayDefinition { - int pageId; - int versionNumber; - - int displayWidth = 719; - int displayHeight = 575; - - int flags; - int displayWindowHorizontalPositionMinimum = 0; - int displayWindowHorizontalPositionMaximum = 719; - int displayWindowVerticalPositionMinimum = 0; - int displayWindowVerticalPositionMaximum = 575; - - void updateBitmapResolution() { - bitmap = Bitmap.createBitmap(this.displayWidth + 1, this.displayHeight + 1, - Bitmap.Config.ARGB_8888); - canvas = new Canvas(bitmap); - } - } - - /* The page is the definition and arrangement of regions in the screen [7.2.2] */ - private class PageComposition { - int pageId; - int pageTimeOut; /* in seconds */ - int pageVersionNumber; - int pageState; - SparseArray pageRegions = new SparseArray<>(); - } - - private class PageRegion { - int regionId; - int regionHorizontalAddress; - int regionVerticalAddress; - } - - /* The Region is an area of the page [7.2.3] composed of a list of objects and a CLUT */ - private class RegionComposition { - int pageId; - int regionId; - int regionVersionNumber; - int flags; - int regionWidth; - int regionHeight; - int regionLevelOfCompatibility; - int regionDepth; - int clutId; - int region8bitPixelCode; - int region4bitPixelCode; - int region2bitPixelCode; - SparseArray regionObjects = new SparseArray<>(); - - /* - * We maintain a reference to the Cue to implement future drawing optimizations, no re-render in case of: - * - * - Page updates not affecting region composition (no clut change/redefinition, no object changes) - * - Incremental subtitle display render (e.g. live captions updates) - */ - Cue cue; - } - - private class RegionObject { - int objectId; - int objectType; - int objectProvider; - int objectHorizontalPosition; - int objectVerticalPosition; - int foregroundPixelCode; - int backgroundPixelCode; - } - - /* An entry in the palette CLUT and associated color space translation methods */ - private class ClutEntry { - int clutEntryId; - byte flags; - byte Y; - byte Cr; - byte Cb; - byte T; - - byte A; - byte R; - byte G; - byte B; - int ARGB; - - void clutYCbCrT(int Y, int Cb, int Cr, int T) { - - this.Y = (byte) Y; - this.Cb = (byte) Cb; - this.Cr = (byte) Cr; - this.T = (byte) T; - - int R = (int) (Y + 1.40200 * (Cr - 128)); - int G = (int) (Y - 0.34414 * (Cb - 128) - 0.71414 * (Cr - 128)); - int B = (int) (Y + 1.77200 * (Cb - 128)); - - if (R > 255) this.R = (byte) 255; - else if (R < 0) this.R = 0; - else this.R = (byte) R; - - if (G > 255) this.G = (byte) 255; - else if (G < 0) this.G = 0; - else this.G = (byte) G; - - if (B > 255) this.B = (byte) 255; - else if (B < 0) this.B = 0; - else this.B = (byte) B; - - this.A = (byte) (0xFF - (this.T & 0xFF)); - this.ARGB = - ((this.A & 0xFF) << 24) | - ((this.R & 0xFF) << 16) | - ((this.G & 0xFF) << 8) | - (this.B & 0xFF); - - } - - void clutRGBA(int R, int G, int B, int A) { - - this.A = (byte) A; - this.R = (byte) R; - this.G = (byte) G; - this.B = (byte) B; - - this.ARGB = - ((A & 0xFF) << 24) | - ((R & 0xFF) << 16) | - ((G & 0xFF) << 8) | - (B & 0xFF); - - int y = (int) (0.299000 * R + 0.587000 * G + 0.114000 * B); - int Cb = 128 + (int) (-0.168736 * R + -0.331264 * G + 0.500000 * B); - int Cr = 128 + (int) (0.500000 * R + -0.418688 * G + -0.081312 * B); - - if (y > 255) this.Y = (byte) 255; - else if (y < 0) this.Y = 0; - else this.Y = (byte) y; - - if (Cb > 255) this.Cb = (byte) 255; - else if (Cb < 0) this.Cb = 0; - else this.Cb = (byte) Cb; - - if (Cr > 255) this.Cr = (byte) 255; - else if (Cr < 0) this.Cr = 0; - else this.Cr = (byte) Cr; - - this.T = (byte) (0xFF - (this.A & 0xFF)); - } - } - - /* CLUT family definition containing the color tables for the three bitdepths defined [7.2.4] */ - private class ClutDefinition { - int pageId; - int clutId; - int clutVersionNumber; - ClutEntry[] clutEntries2bit; - ClutEntry[] clutEntries4bit; - ClutEntry[] clutEntries8bit; - - ClutEntry[] generateDefault2bitClut() { - ClutEntry[] entries = new ClutEntry[4]; - - entries[0] = new ClutEntry(); - entries[0].clutRGBA(0x00, 0x00, 0x00, 0x00); - entries[1] = new ClutEntry(); - entries[1].clutRGBA(0xFF, 0xFF, 0xFF, 0xFF); - entries[2] = new ClutEntry(); - entries[2].clutRGBA(0x00, 0x00, 0x00, 0xFF); - entries[3] = new ClutEntry(); - entries[3].clutRGBA(0x7F, 0x7F, 0x7F, 0xFF); - - return entries; - } - - ClutEntry[] generateDefault4bitClut() { - ClutEntry[] entries = new ClutEntry[16]; - - entries[0] = new ClutEntry(); - entries[0].clutRGBA(0x00, 0x00, 0x00, 0x00); - - int i = 15; - while (i > 0) { - entries[i] = new ClutEntry(); - if (i < 8) { - entries[i].clutRGBA( - ((i & 0x01) != 0 ? 0xFF : 0x00), - ((i & 0x02) != 0 ? 0xFF : 0x00), - ((i & 0x04) != 0 ? 0xFF : 0x00), - 0xFF); - } else { - entries[i].clutRGBA( - ((i & 0x01) != 0 ? 0x7F : 0x00), - ((i & 0x02) != 0 ? 0x7F : 0x00), - ((i & 0x04) != 0 ? 0x7F : 0x00), - 0xFF); - } - - i--; - } - - return entries; - } - - ClutEntry[] generateDefault8bitClut() { - ClutEntry[] entries = new ClutEntry[256]; - - entries[0] = new ClutEntry(); - entries[0].clutRGBA(0x00, 0x00, 0x00, 0x00); - - int i = 255; - while (i > 0) { - entries[i] = new ClutEntry(); - if (i < 8) { - entries[i].clutRGBA( - ((i & 0x01) != 0 ? 0xFF : 0x00), - ((i & 0x02) != 0 ? 0xFF : 0x00), - ((i & 0x04) != 0 ? 0xFF : 0x00), - 0x3F); - } else { - switch (i & 0x88) { - case 0x00: - entries[i].clutRGBA( - (((i & 0x01) != 0 ? 0x55 : 0x00) + ((i & 0x10) != 0 ? 0xAA : 0x00)), - (((i & 0x02) != 0 ? 0x55 : 0x00) + ((i & 0x20) != 0 ? 0xAA : 0x00)), - (((i & 0x04) != 0 ? 0x55 : 0x00) + ((i & 0x40) != 0 ? 0xAA : 0x00)), - 0xFF); - break; - case 0x08: - entries[i].clutRGBA( - (((i & 0x01) != 0 ? 0x55 : 0x00) + ((i & 0x10) != 0 ? 0xAA : 0x00)), - (((i & 0x02) != 0 ? 0x55 : 0x00) + ((i & 0x20) != 0 ? 0xAA : 0x00)), - (((i & 0x04) != 0 ? 0x55 : 0x00) + ((i & 0x40) != 0 ? 0xAA : 0x00)), - 0x7F); - break; - case 0x80: - entries[i].clutRGBA( - (127 + ((i & 0x01) != 0 ? 0x2B : 0x00) + ((i & 0x10) != 0 ? 0x55 : 0x00)), - (127 + ((i & 0x02) != 0 ? 0x2B : 0x00) + ((i & 0x20) != 0 ? 0x55 : 0x00)), - (127 + ((i & 0x04) != 0 ? 0x2B : 0x00) + ((i & 0x40) != 0 ? 0x55 : 0x00)), - 0xFF); - break; - case 0x88: - entries[i].clutRGBA( - (((i & 0x01) != 0 ? 0x2B : 0x00) + ((i & 0x10) != 0 ? 0x55 : 0x00)), - (((i & 0x02) != 0 ? 0x2B : 0x00) + ((i & 0x20) != 0 ? 0x55 : 0x00)), - (((i & 0x04) != 0 ? 0x2B : 0x00) + ((i & 0x40) != 0 ? 0x55 : 0x00)), - 0xFF); - break; - } - - } - - i--; - } - - return entries; - } - - ClutDefinition() { - clutEntries2bit = generateDefault2bitClut(); - clutEntries4bit = generateDefault4bitClut(); - clutEntries8bit = generateDefault8bitClut(); - } - - } - - /* The object data segment contains the textual/graphical representation of an object [7.2.5] */ - private class ObjectData { - int pageId; - int objectId; - int objectVersionNumber; - int objectCodingMethod; - byte flags; - int topFieldDataLength; - byte[] topFieldData; - int bottomFieldDataLength; - byte[] bottomFieldData; - int numberOfCodes; + public DvbParser(int subtitlePageId, int ancillaryPageId) { + defaultPaint = new Paint(); + defaultPaint.setStyle(Paint.Style.FILL_AND_STROKE); + defaultPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC)); + defaultPaint.setPathEffect(null); + fillRegionPaint = new Paint(); + fillRegionPaint.setStyle(Paint.Style.FILL); + fillRegionPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OVER)); + fillRegionPaint.setPathEffect(null); + canvas = new Canvas(); + defaultDisplayDefinition = new DisplayDefinition(719, 575, 0, 719, 0, 575); + defaultClutDefinition = new ClutDefinition(0, generateDefault2BitClutEntries(), + generateDefault4BitClutEntries(), generateDefault8BitClutEntries()); + subtitleService = new SubtitleService(subtitlePageId, ancillaryPageId); } /** - * Construct a subtitle service for the given subtitle and ancillary pageIds - * - * @param subtitlePageId The Id of the subtitle page carrying the selected subtitle track - * @param ancillaryPageId Id of the common subtitle page containing additional data for the current - * subtitle track - * @param flags additional initialisation info to properly configure the parser + * Resets the parser. */ - DvbParser(int subtitlePageId, int ancillaryPageId, @Flags int flags) { - this.subtitleService = new SubtitleService(); - this.flags = flags; - - this.defaultPaint.setStyle(Paint.Style.FILL_AND_STROKE); - this.defaultPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC)); - this.defaultPaint.setPathEffect(null); - - this.fillRegionPaint.setStyle(Paint.Style.FILL); - this.fillRegionPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OVER)); - this.fillRegionPaint.setPathEffect(null); - - this.debugRegionPaint.setColor(0xff00ff00); - this.debugRegionPaint.setStyle(Paint.Style.STROKE); - this.debugRegionPaint.setPathEffect(new DashPathEffect(new float[]{2, 2}, 0)); - - this.debugObjectPaint.setColor(0xffff0000); - this.debugObjectPaint.setStyle(Paint.Style.STROKE); - this.debugObjectPaint.setPathEffect(new DashPathEffect(new float[]{10, 20}, 0)); - - this.subtitleService.subtitlePageId = subtitlePageId; - this.subtitleService.ancillaryPageId = ancillaryPageId; - - this.subtitleService.displayDefinition = new DisplayDefinition(); - this.subtitleService.displayDefinition.updateBitmapResolution(); + public void reset() { + subtitleService.reset(); } - private void parseSubtitlingSegment() { + /** + * Decodes a subtitling packet, returning a list of parsed {@link Cue}s. + * + * @param data The subtitling packet data to decode. + * @param limit The limit in {@code data} at which to stop decoding. + * @return The parsed {@link Cue}s. + */ + public List decode(byte[] data, int limit) { + // Parse the input data. + ParsableBitArray dataBitArray = new ParsableBitArray(data, limit); + while (dataBitArray.bitsLeft() >= 48 // sync_byte (8) + segment header (40) + && dataBitArray.readBits(8) == 0x0F) { + parseSubtitlingSegment(dataBitArray, subtitleService); + } - /* Parse subtitling segment. ETSI EN 300 743 7.2 + if (subtitleService.pageComposition == null) { + return Collections.emptyList(); + } - SYNTAX SIZE SEMANTICS - --------------------------------------- ---- ------------------------------------------ - Subtitling_segment() { - sync_byte 8 An 8-bit field that shall be coded with the value '0000 1111' - segment_type 8 Indicates the type of data contained in the segment data field - page_id 16 Identifies the subtitle service of the data contained in this subtitling_segment - segment_length 16 Number of bytes contained in the segment_data_field - segment_data_field() This is the payload of the segment + // Update the canvas bitmap if necessary. + DisplayDefinition displayDefinition = subtitleService.displayDefinition != null + ? subtitleService.displayDefinition : defaultDisplayDefinition; + if (bitmap == null || displayDefinition.width + 1 != bitmap.getWidth() + || displayDefinition.height + 1 != bitmap.getHeight()) { + bitmap = Bitmap.createBitmap(displayDefinition.width + 1, displayDefinition.height + 1, + Bitmap.Config.ARGB_8888); + canvas.setBitmap(bitmap); + } - */ + // Build the cues. + List cues = new ArrayList<>(); + SparseArray pageRegions = subtitleService.pageComposition.regions; + for (int i = 0; i < pageRegions.size(); i++) { + PageRegion pageRegion = pageRegions.valueAt(i); + int regionId = pageRegions.keyAt(i); + RegionComposition regionComposition = subtitleService.regions.get(regionId); - int pageId, segmentId, segmentLength; - segmentId = tsStream.readBits(8); - switch (segmentId) { - case DVBSUB_ST_DISPLAY_DEFINITION: - if (BuildConfig.DEBUG) Log.d(TAG, " Parse Display Definition segment."); - DisplayDefinition tempDisplay = parseDisplayDefinitionSegment(); - if (tempDisplay != null && tempDisplay.pageId == subtitleService.subtitlePageId) { - if (tempDisplay.displayWidth != subtitleService.displayDefinition.displayWidth || - tempDisplay.displayHeight != subtitleService.displayDefinition.displayHeight || - tempDisplay.displayWindowHorizontalPositionMaximum != subtitleService.displayDefinition.displayWindowHorizontalPositionMaximum || - tempDisplay.displayWindowHorizontalPositionMinimum != subtitleService.displayDefinition.displayWindowHorizontalPositionMinimum || - tempDisplay.displayWindowVerticalPositionMaximum != subtitleService.displayDefinition.displayWindowVerticalPositionMaximum || - tempDisplay.displayWindowVerticalPositionMinimum != subtitleService.displayDefinition.displayWindowVerticalPositionMinimum || - tempDisplay.flags != subtitleService.displayDefinition.flags) { - subtitleService.displayDefinition = tempDisplay; - subtitleService.displayDefinition.updateBitmapResolution(); - } else { - subtitleService.displayDefinition.versionNumber = tempDisplay.versionNumber; - } + // Clip drawing to the current region and display definition window. + int baseHorizontalAddress = pageRegion.horizontalAddress + + displayDefinition.horizontalPositionMinimum; + int baseVerticalAddress = pageRegion.verticalAddress + + displayDefinition.verticalPositionMinimum; + int clipRight = Math.min(baseHorizontalAddress + regionComposition.width, + displayDefinition.horizontalPositionMaximum); + int clipBottom = Math.min(baseVerticalAddress + regionComposition.height, + displayDefinition.verticalPositionMaximum); + canvas.clipRect(baseHorizontalAddress, baseVerticalAddress, clipRight, clipBottom, + Region.Op.REPLACE); - if (BuildConfig.DEBUG) - Log.d(TAG + "/DDS", " [versionNumber] = " + tempDisplay.versionNumber + - " [width/height] = " + (tempDisplay.displayWidth + 1) + "/" + (tempDisplay.displayHeight + 1) + - " Window[minX/minY/maxX/maxY] = " + tempDisplay.displayWindowHorizontalPositionMinimum + - "/" + tempDisplay.displayWindowVerticalPositionMinimum + - "/" + tempDisplay.displayWindowHorizontalPositionMaximum + - "/" + tempDisplay.displayWindowVerticalPositionMaximum - ); + ClutDefinition clutDefinition = subtitleService.cluts.get(regionComposition.clutId); + if (clutDefinition == null) { + clutDefinition = subtitleService.ancillaryCluts.get(regionComposition.clutId); + if (clutDefinition == null) { + clutDefinition = defaultClutDefinition; + } + } + + SparseArray regionObjects = regionComposition.regionObjects; + for (int j = 0; j < regionObjects.size(); j++) { + int objectId = regionObjects.keyAt(j); + RegionObject regionObject = regionObjects.valueAt(j); + ObjectData objectData = subtitleService.objects.get(objectId); + if (objectData == null) { + objectData = subtitleService.ancillaryObjects.get(objectId); + } + if (objectData != null) { + Paint paint = objectData.nonModifyingColorFlag ? null : defaultPaint; + paintPixelDataSubBlocks(objectData, clutDefinition, regionComposition.depth, + baseHorizontalAddress + regionObject.horizontalPosition, + baseVerticalAddress + regionObject.verticalPosition, paint, canvas); + } + } + + if (regionComposition.fillFlag) { + int color; + if (regionComposition.depth == REGION_DEPTH_8_BIT) { + color = clutDefinition.clutEntries8Bit[regionComposition.pixelCode8Bit]; + } else if (regionComposition.depth == REGION_DEPTH_4_BIT) { + color = clutDefinition.clutEntries4Bit[regionComposition.pixelCode4Bit]; + } else { + color = clutDefinition.clutEntries2Bit[regionComposition.pixelCode2Bit]; + } + fillRegionPaint.setColor(color); + canvas.drawRect(baseHorizontalAddress, baseVerticalAddress, + baseHorizontalAddress + regionComposition.width, + baseVerticalAddress + regionComposition.height, + fillRegionPaint); + } + + Bitmap cueBitmap = Bitmap.createBitmap(bitmap, baseHorizontalAddress, baseVerticalAddress, + regionComposition.width, regionComposition.height); + cues.add(new Cue(cueBitmap, (float) baseHorizontalAddress / displayDefinition.width, + Cue.ANCHOR_TYPE_START, (float) baseVerticalAddress / displayDefinition.height, + Cue.ANCHOR_TYPE_START, (float) regionComposition.width / displayDefinition.width, + (float) regionComposition.height / displayDefinition.height)); + + canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR); + } + + return cues; + } + + // Static parsing. + + /** + * Parses a subtitling segment, as defined by ETSI EN 300 743 7.2 + *

    + * The {@link SubtitleService} is updated with the parsed segment data. + */ + private static void parseSubtitlingSegment(ParsableBitArray data, SubtitleService service) { + int segmentType = data.readBits(8); + int pageId = data.readBits(16); + int dataFieldLength = data.readBits(16); + int dataFieldLimit = data.getBytePosition() + dataFieldLength; + + if ((dataFieldLength * 8) > data.bitsLeft()) { + Log.w(TAG, "Data field length exceeds limit"); + // Skip to the very end. + data.skipBits(data.bitsLeft()); + return; + } + + switch (segmentType) { + case SEGMENT_TYPE_DISPLAY_DEFINITION: + if (pageId == service.subtitlePageId) { + service.displayDefinition = parseDisplayDefinition(data); } break; - case DVBSUB_ST_PAGE_COMPOSITION: - if (BuildConfig.DEBUG) Log.d(TAG, " Parse Page Composition segment."); - PageComposition tempPage = parsePageCompositionSegment(); - if (tempPage != null && tempPage.pageId == subtitleService.subtitlePageId) { - if (tempPage.pageState == DVBSUB_PCS_STATE_NORMAL && subtitleService.pageComposition == null) - break; - subtitleService.pageComposition = tempPage; - } - break; - case DVBSUB_ST_REGION_COMPOSITION: - if (BuildConfig.DEBUG) Log.d(TAG, " Parse Region Composition segment."); - RegionComposition tempRegionComposition = parseRegionCompositionSegment(); - if (tempRegionComposition != null && tempRegionComposition.pageId == subtitleService.subtitlePageId) { - subtitleService.regions.put(tempRegionComposition.regionId, tempRegionComposition); - } - break; - case DVBSUB_ST_CLUT_DEFINITION: - if (BuildConfig.DEBUG) Log.d(TAG, " Parse Clut Definition segment."); - ClutDefinition tempClutDefinition = parseClutDefinitionSegment(); - if (tempClutDefinition != null) { - if (tempClutDefinition.pageId == subtitleService.subtitlePageId) { - subtitleService.cluts.put(tempClutDefinition.clutId, tempClutDefinition); - } else if (tempClutDefinition.pageId == subtitleService.ancillaryPageId) { - subtitleService.ancillaryCluts.put(tempClutDefinition.clutId, tempClutDefinition); + case SEGMENT_TYPE_PAGE_COMPOSITION: + if (pageId == service.subtitlePageId) { + PageComposition current = service.pageComposition; + PageComposition pageComposition = parsePageComposition(data, dataFieldLength); + if (pageComposition.state != PAGE_STATE_NORMAL) { + service.pageComposition = pageComposition; + service.regions.clear(); + service.cluts.clear(); + service.objects.clear(); + } else if (current != null && current.version != pageComposition.version) { + service.pageComposition = pageComposition; } } break; - case DVBSUB_ST_OBJECT_DATA: - if (BuildConfig.DEBUG) Log.d(TAG, " Parse Object Data segment."); - ObjectData tempObjectData = parseObjectDataSegment(); - if (tempObjectData != null) { - if (tempObjectData.pageId == subtitleService.subtitlePageId) { - subtitleService.objects.put(tempObjectData.objectId, tempObjectData); - } else if (tempObjectData.pageId == subtitleService.ancillaryPageId) { - subtitleService.ancillaryObjects.put(tempObjectData.objectId, tempObjectData); + case SEGMENT_TYPE_REGION_COMPOSITION: + PageComposition pageComposition = service.pageComposition; + if (pageId == service.subtitlePageId && pageComposition != null) { + RegionComposition regionComposition = parseRegionComposition(data, dataFieldLength); + if (pageComposition.state == PAGE_STATE_NORMAL) { + regionComposition.mergeFrom(service.regions.get(regionComposition.id)); } + service.regions.put(regionComposition.id, regionComposition); } break; - case DVBSUB_ST_ENDOFDISPLAY: - pageId = tsStream.readBits(16); - segmentLength = tsStream.readBits(16); - if (BuildConfig.DEBUG) - Log.d(TAG, "pageId " + pageId + "end of display size = " + segmentLength); - tsStream.skipBits(segmentLength * 8); + case SEGMENT_TYPE_CLUT_DEFINITION: + if (pageId == service.subtitlePageId) { + ClutDefinition clutDefinition = parseClutDefinition(data, dataFieldLength); + service.cluts.put(clutDefinition.id, clutDefinition); + } else if (pageId == service.ancillaryPageId) { + ClutDefinition clutDefinition = parseClutDefinition(data, dataFieldLength); + service.ancillaryCluts.put(clutDefinition.id, clutDefinition); + } break; - case DVBSUB_ST_STUFFING: - pageId = tsStream.readBits(16); - segmentLength = tsStream.readBits(16); - if (BuildConfig.DEBUG) Log.d(TAG, "pageId " + pageId + "stuffing size = " + segmentLength); - tsStream.skipBits(segmentLength * 8); + case SEGMENT_TYPE_OBJECT_DATA: + if (pageId == service.subtitlePageId) { + ObjectData objectData = parseObjectData(data); + service.objects.put(objectData.id, objectData); + } else if (pageId == service.ancillaryPageId) { + ObjectData objectData = parseObjectData(data); + service.ancillaryObjects.put(objectData.id, objectData); + } break; default: + // Do nothing. break; } + + // Skip to the next segment. + data.skipBytes(dataFieldLimit - data.getBytePosition()); } - private DisplayDefinition parseDisplayDefinitionSegment() { + /** + * Parses a display definition segment, as defined by ETSI EN 300 743 7.2.1. + */ + private static DisplayDefinition parseDisplayDefinition(ParsableBitArray data) { + data.skipBits(4); // dds_version_number (4). + boolean displayWindowFlag = data.readBit(); + data.skipBits(3); // Skip reserved. + int width = data.readBits(16); + int height = data.readBits(16); - /* Parse display definition segment. ETSI EN 300 743 7.2.1 - - SYNTAX SIZE SEMANTICS - --------------------------------------- ---- ------------------------------------------ - display_definition_segment(){ - sync_byte 8 An 8-bit field that shall be coded with the value '0000 1111' - segment_type 8 Indicates the type of data contained in the segment data field - page_id 16 Identifies the subtitle service of the data contained in this subtitling_segment - segment_length 16 Number of bytes contained in the segment_data_field - dds_version_number 4 Incremented when any of the contents of this segment change - display_window_flag 1 if "1" display the subtitle in the defined window - reserved 3 - display_width 16 Specifies the maximum horizontal width of the display in pixels minus 1 - display_height 16 Specifies the maximum vertical height of the display in lines minus 1 - if (display_window_flag == 1) { With origin in the top-left of the screen: - display_window_horizontal_position_minimum - 16 Specifies the left-hand most pixel of this DVB subtitle display set - display_window_horizontal_position_maximum - 16 Specifies the right-hand most pixel of this DVB subtitle display set - display_window_vertical_position_minimum - 16 Specifies the upper most line of this DVB subtitle display set - display_window_vertical_position_maximum - 16 Specifies the bottom line of this DVB subtitle display set - } - } - */ - - DisplayDefinition display = new DisplayDefinition(); - - display.pageId = tsStream.readBits(16); - tsStream.skipBits(16); - display.versionNumber = tsStream.readBits(4); - if (tsStream.readBits(1) == 1) { - display.flags |= DISPLAY_WINDOW_FLAG; - } - tsStream.skipBits(3); - display.displayWidth = tsStream.readBits(16); - display.displayHeight = tsStream.readBits(16); - if ((display.flags & DISPLAY_WINDOW_FLAG) != 0) { - display.displayWindowHorizontalPositionMinimum = tsStream.readBits(16); - display.displayWindowHorizontalPositionMaximum = tsStream.readBits(16); - display.displayWindowVerticalPositionMinimum = tsStream.readBits(16); - display.displayWindowVerticalPositionMaximum = tsStream.readBits(16); + int horizontalPositionMinimum; + int horizontalPositionMaximum; + int verticalPositionMinimum; + int verticalPositionMaximum; + if (displayWindowFlag) { + horizontalPositionMinimum = data.readBits(16); + horizontalPositionMaximum = data.readBits(16); + verticalPositionMinimum = data.readBits(16); + verticalPositionMaximum = data.readBits(16); } else { - display.displayWindowHorizontalPositionMinimum = 0; - display.displayWindowHorizontalPositionMaximum = display.displayWidth; - display.displayWindowVerticalPositionMinimum = 0; - display.displayWindowVerticalPositionMaximum = display.displayHeight; + horizontalPositionMinimum = 0; + horizontalPositionMaximum = width; + verticalPositionMinimum = 0; + verticalPositionMaximum = height; } - return display; + return new DisplayDefinition(width, height, horizontalPositionMinimum, + horizontalPositionMaximum, verticalPositionMinimum, verticalPositionMaximum); } - private PageComposition parsePageCompositionSegment() { + /** + * Parses a page composition segment, as defined by ETSI EN 300 743 7.2.2. + */ + private static PageComposition parsePageComposition(ParsableBitArray data, int length) { + int timeoutSecs = data.readBits(8); + int version = data.readBits(4); + int state = data.readBits(2); + data.skipBits(2); + int remainingLength = length - 2; - /* Parse page composition segment. ETSI EN 300 743 7.2.2 - - SYNTAX SIZE SEMANTICS - --------------------------------------- ---- ------------------------------------------ - page_composition_segment() { - sync_byte 8 - segment_type 8 - page_id 16 - segment_length 16 - page_time_out 8 The period after the page instace should be erased - page_version_number 4 Incremented when any of the contents of this segment change - page_state 2 The status of the subtitling page instance - reserved 2 - while (processed_length < segment_length) { Page region list - region_id 8 Uniquely identifies a region within a page - reserved 8 - region_horizontal_address 16 Horizontal address of the top left pixel of this region - region_vertical_address 16 Vertical address of the top line of this region - } - } - */ - - PageComposition page = new PageComposition(); - - page.pageId = tsStream.readBits(16); - int remainingSegmentLength = tsStream.readBits(16); - page.pageTimeOut = tsStream.readBits(8); - page.pageVersionNumber = tsStream.readBits(4); - page.pageState = tsStream.readBits(2); - tsStream.skipBits(2); - - if (page.pageState == DVBSUB_PCS_STATE_NORMAL && - subtitleService.pageComposition != null && - subtitleService.pageComposition.pageId == page.pageId && - (subtitleService.pageComposition.pageVersionNumber + 1) % 16 == page.pageVersionNumber) { - //page.pageRegions = subtitleService.pageComposition.pageRegions; - - if (BuildConfig.DEBUG) { - Log.d(TAG, " Updated Page Composition. pageId: " + page.pageId + - " version: " + page.pageVersionNumber + - " timeout: " + page.pageTimeOut - ); - } - - } else if (subtitleService.subtitlePageId == page.pageId) { - if (BuildConfig.DEBUG) { - if (page.pageState == DVBSUB_PCS_STATE_NORMAL) { - Log.d(TAG, " FAILED Page Composition update. pageId: " + page.pageId + - " Version(Old/New): " + (subtitleService.pageComposition != null ? subtitleService.pageComposition.pageVersionNumber : "NaN") + "/" + page.pageVersionNumber); - } - } - - subtitleService.pageComposition = null; - subtitleService.regions = new SparseArray<>(); - subtitleService.cluts = new SparseArray<>(); - subtitleService.objects = new SparseArray<>(); - - if (BuildConfig.DEBUG) { - if (page.pageState != DVBSUB_PCS_STATE_NORMAL) { - Log.d(TAG, " New Page Composition. pageId: " + page.pageId + - " version: " + page.pageVersionNumber + - " timeout: " + page.pageTimeOut - ); - } - } + SparseArray regions = new SparseArray<>(); + while (remainingLength > 0) { + int regionId = data.readBits(8); + data.skipBits(8); // Skip reserved. + int regionHorizontalAddress = data.readBits(16); + int regionVerticalAddress = data.readBits(16); + remainingLength -= 6; + regions.put(regionId, new PageRegion(regionHorizontalAddress, regionVerticalAddress)); } - remainingSegmentLength -= 2; - while (remainingSegmentLength > 0) { - PageRegion region = new PageRegion(); - - region.regionId = tsStream.readBits(8); - tsStream.skipBits(8); - region.regionHorizontalAddress = tsStream.readBits(16); - region.regionVerticalAddress = tsStream.readBits(16); - - if (BuildConfig.DEBUG) { - Log.d(TAG, " " + - (page.pageRegions.get(region.regionId) == null ? "New" : "Upd.") + - " Page Region. regionId: " + region.regionId + - " (x/y): (" + region.regionHorizontalAddress + "/" + region.regionVerticalAddress + ")"); - } - - page.pageRegions.put(region.regionId, region); - - remainingSegmentLength -= 6; - } - - return page; + return new PageComposition(timeoutSecs, version, state, regions); } - private RegionComposition parseRegionCompositionSegment() { + /** + * Parses a region composition segment, as defined by ETSI EN 300 743 7.2.3. + */ + private static RegionComposition parseRegionComposition(ParsableBitArray data, int length) { + int id = data.readBits(8); + data.skipBits(4); // Skip region_version_number + boolean fillFlag = data.readBit(); + data.skipBits(3); // Skip reserved. + int width = data.readBits(16); + int height = data.readBits(16); + int levelOfCompatibility = data.readBits(3); + int depth = data.readBits(3); + data.skipBits(2); // Skip reserved. + int clutId = data.readBits(8); + int pixelCode8Bit = data.readBits(8); + int pixelCode4Bit = data.readBits(4); + int pixelCode2Bit = data.readBits(2); + data.skipBits(2); // Skip reserved + int remainingLength = length - 10; - /* Parse region composition segment. ETSI EN 300 743 7.2.3 + SparseArray regionObjects = new SparseArray<>(); + while (remainingLength > 0) { + int objectId = data.readBits(16); + int objectType = data.readBits(2); + int objectProvider = data.readBits(2); + int objectHorizontalPosition = data.readBits(12); + data.skipBits(4); // Skip reserved. + int objectVerticalPosition = data.readBits(12); + remainingLength -= 6; - SYNTAX SIZE SEMANTICS - --------------------------------------- ---- ------------------------------------------ - region_composition_segment() { - sync_byte 8 - segment_type 8 - page_id 16 - segment_length 16 - region_id 8 Uniquely identifies the region - region_version_number 4 Indicates the version of this region - region_fill_flag 1 If set the region is to be filled region_n-bit_pixel_code clut index - reserved 3 - region_width 16 Specifies the horizontal length of this region - region_height 16 Specifies the vertical length of the region - region_level_of_compatibility 3 Code that indicates the minimum bithdepth of CLUT - region_depth 3 Identifies the intended pixel depth for this region - reserved 2 - CLUT_id 8 Identifies the family of CLUTs that applies to this region - region_8-bit_pixel_code 8 Specifies the entry of the applied 8-bit CLUT as background colour - region_4-bit_pixel-code 4 Specifies the entry of the applied 4-bit CLUT as background colour - region_2-bit_pixel-code 2 Specifies the entry of the applied 2-bit CLUT as background colour - reserved 2 - while (processed_length < segment_length) { list of region objects - object_id 16 Identifies an object that is shown in the region - object_type 2 Identifies the type of object - object_provider_flag 2 How this object is provided - object_horizontal_position 12 Specifies the horizontal position of the top left pixel of this object - reserved 4 - object_vertical_position 12 Specifies the vertical position of the top left pixel of this object - if (object_type ==0x01 or object_type == 0x02){ UNSUPPORTED - foreground_pixel_code 8 - background_pixel_code 8 - } - } - } - */ - - RegionComposition region = new RegionComposition(); - - region.pageId = tsStream.readBits(16); - int remainingSegmentLength = tsStream.readBits(16); - region.regionId = tsStream.readBits(8); - region.regionVersionNumber = tsStream.readBits(4); - if (tsStream.readBits(1) == 1) { - region.flags |= REGION_FILL_FLAG; - } - tsStream.skipBits(3); - region.regionWidth = tsStream.readBits(16); - region.regionHeight = tsStream.readBits(16); - region.regionLevelOfCompatibility = tsStream.readBits(3); - region.regionDepth = tsStream.readBits(3); - tsStream.skipBits(2); - region.clutId = tsStream.readBits(8); - tsStream.skipBits(16); - - if (BuildConfig.DEBUG) { - Log.d(TAG, " New Region Composition. regionId: " + region.regionId + - " (w/h): (" + region.regionWidth + "/" + region.regionHeight + ")"); - } - - int arrayIndex = 0; // index by an incremental counter to allow repeating objects in one region - - if (subtitleService.pageComposition != null && subtitleService.pageComposition.pageId == region.pageId && - subtitleService.pageComposition.pageState == DVBSUB_PCS_STATE_NORMAL) { - RegionComposition tempRegion = subtitleService.regions.get(region.regionId); - if (tempRegion != null) { - region.regionObjects = tempRegion.regionObjects; - arrayIndex = region.regionObjects.size(); - } - } - - remainingSegmentLength -= 10; - RegionObject object; - while (remainingSegmentLength > 0) { - object = new RegionObject(); - - object.objectId = tsStream.readBits(16); - object.objectType = tsStream.readBits(2); - object.objectProvider = tsStream.readBits(2); - object.objectHorizontalPosition = tsStream.readBits(12); - tsStream.skipBits(4); - object.objectVerticalPosition = tsStream.readBits(12); - remainingSegmentLength -= 6; - - if (object.objectType == 0x01 || object.objectType == 0x02) { // Only seems to affect to char subtitles - object.foregroundPixelCode = tsStream.readBits(8); - object.backgroundPixelCode = tsStream.readBits(8); - remainingSegmentLength -= 2; + int foregroundPixelCode = 0; + int backgroundPixelCode = 0; + if (objectType == 0x01 || objectType == 0x02) { // Only seems to affect to char subtitles. + foregroundPixelCode = data.readBits(8); + backgroundPixelCode = data.readBits(8); + remainingLength -= 2; } - if (BuildConfig.DEBUG) { - Log.d(TAG, " New Region Object[" + arrayIndex + "]." + - " objectId: " + object.objectId + - " (x/y): (" + object.objectHorizontalPosition + "/" + object.objectVerticalPosition + ")"); - } - - region.regionObjects.put(arrayIndex++, object); + regionObjects.put(objectId, new RegionObject(objectType, objectProvider, + objectHorizontalPosition, objectVerticalPosition, foregroundPixelCode, + backgroundPixelCode)); } - - return region; + return new RegionComposition(id, fillFlag, width, height, levelOfCompatibility, depth, clutId, + pixelCode8Bit, pixelCode4Bit, pixelCode2Bit, regionObjects); } - private ClutDefinition parseClutDefinitionSegment() { + /** + * Parses a CLUT definition segment, as defined by ETSI EN 300 743 7.2.4. + */ + private static ClutDefinition parseClutDefinition(ParsableBitArray data, int length) { + int clutId = data.readBits(8); + data.skipBits(8); // Skip clut_version_number (4), reserved (4) + int remainingLength = length - 2; - /* Parse CLUT definition segment. ETSI EN 300 743 7.2.4 + int[] clutEntries2Bit = generateDefault2BitClutEntries(); + int[] clutEntries4Bit = generateDefault4BitClutEntries(); + int[] clutEntries8Bit = generateDefault8BitClutEntries(); - SYNTAX SIZE SEMANTICS - --------------------------------------- ---- ------------------------------------------ - CLUT_definition_segment() { - sync_byte 8 - segment_type 8 - page_id 16 - segment_length 16 - CLUT-id 8 Uniquely identifies within a page the CLUT family - CLUT_version_number 4 Indicates the version of this segment data - reserved 4 - while (processed_length < segment_length) { Clut entries list - CLUT_entry_id 8 Specifies the entry number of the CLUT - 2-bit/entry_CLUT_flag 1 Indicates this entry is to be loaded into the 2-bit/entry CLUT - 4-bit/entry_CLUT_flag 1 Indicates this entry is to be loaded into the 4-bit/entry CLUT - 8-bit/entry_CLUT_flag 1 Indicates this entry is to be loaded into the 8-bit/entry CLUT - reserved 4 - full_range_flag 1 Indicates that the Y_value, Cr_value, Cb_value and T_value - fields have the full 8-bit resolution - if full_range_flag =='1' { - Y-value 8 The Y value for this CLUT entry. - Cr-value 8 The Cr value for this CLUT entry. - Cb-value 8 The Cb value for this CLUT entry. - T-value 8 The Transparency value for this CLUT entry. 0 = no transparency - } else { - Y-value 6 The Y value for this CLUT entry. - Cr-value 4 The Cr value for this CLUT entry. - Cb-value 4 The Cb value for this CLUT entry. - T-value 2 The Transparency value for this CLUT entry. 0 = no transparency - } - } - } - */ - - ClutDefinition clut = new ClutDefinition(); - clut.pageId = tsStream.readBits(16); - int remainingSegmentLength = tsStream.readBits(16); - clut.clutId = tsStream.readBits(8); - clut.clutVersionNumber = tsStream.readBits(4); - tsStream.skipBits(4); - - remainingSegmentLength -= 2; - ClutEntry entry; - int Y, Cb, Cr, T; - int entryId, entryFlags; - while (remainingSegmentLength > 0) { - entryId = tsStream.readBits(8); - entryFlags = tsStream.readBits(8); + while (remainingLength > 0) { + int entryId = data.readBits(8); + int entryFlags = data.readBits(8); + remainingLength -= 2; + int[] clutEntries; if ((entryFlags & 0x80) != 0) { - entry = clut.clutEntries2bit[entryId]; + clutEntries = clutEntries2Bit; } else if ((entryFlags & 0x40) != 0) { - entry = clut.clutEntries4bit[entryId]; + clutEntries = clutEntries4Bit; } else { - entry = clut.clutEntries8bit[entryId]; + clutEntries = clutEntries8Bit; } - entry.flags = (byte) (entryFlags & 0xE1); - if ((entry.flags & 0x01) != 0) { - Y = tsStream.readBits(8); - Cr = tsStream.readBits(8); - Cb = tsStream.readBits(8); - T = tsStream.readBits(8); - remainingSegmentLength -= 6; + int y; + int cr; + int cb; + int t; + if ((entryFlags & 0x01) != 0) { + y = data.readBits(8); + cr = data.readBits(8); + cb = data.readBits(8); + t = data.readBits(8); + remainingLength -= 4; } else { - Y = tsStream.readBits(6) << 2; - Cr = tsStream.readBits(4) << 4; - Cb = tsStream.readBits(4) << 4; - T = tsStream.readBits(2) << 6; - remainingSegmentLength -= 4; + y = data.readBits(6) << 2; + cr = data.readBits(4) << 4; + cb = data.readBits(4) << 4; + t = data.readBits(2) << 6; + remainingLength -= 2; } - if (Y == 0x00) { - Cr = 0x00; - Cb = 0x00; - T = 0xFF; + if (y == 0x00) { + cr = 0x00; + cb = 0x00; + t = 0xFF; } - entry.clutYCbCrT(Y, Cb, Cr, T); + int a = (byte) (0xFF - (t & 0xFF)); + int r = (int) (y + (1.40200 * (cr - 128))); + int g = (int) (y - (0.34414 * (cb - 128)) - (0.71414 * (cr - 128))); + int b = (int) (y + (1.77200 * (cb - 128))); + clutEntries[entryId] = getColor(a, Util.constrainValue(r, 0, 255), + Util.constrainValue(g, 0, 255), Util.constrainValue(b, 0, 255)); } - return clut; + + return new ClutDefinition(clutId, clutEntries2Bit, clutEntries4Bit, clutEntries8Bit); } - private ObjectData parseObjectDataSegment() { + /** + * Parses an object data segment, as defined by ETSI EN 300 743 7.2.5. + * + * @return The parsed object data. + */ + private static ObjectData parseObjectData(ParsableBitArray data) { + int objectId = data.readBits(16); + data.skipBits(4); // Skip object_version_number + int objectCodingMethod = data.readBits(2); + boolean nonModifyingColorFlag = data.readBit(); + data.skipBits(1); // Skip reserved. - /* Parse object data segment. ETSI EN 300 743 7.2.5 + byte[] topFieldData = null; + byte[] bottomFieldData = null; - SYNTAX SIZE SEMANTICS - --------------------------------------- ---- ------------------------------------------ - object_data_segment() { - sync_byte 8 - segment_type 8 - page_id 16 - segment_length 16 - object_id 16 Uniquely identifies within the page the object - object_version_number 4 Indicates the version of this segment data - object_coding_method 2 Specifies the method used to code the object - non_modifying_colour_flag 1 Indicates that the CLUT entry value '1' is a non modifying colour - reserved 1 - if (object_coding_method == '00'){ - top_field_data_block_length 16 Specifies the number of bytes contained in his pixel-data_sub-blocks - bottom_field_data_block_length 16 Specifies the number of bytes contained in his pixel-data_sub-blocks - while(processed_length 0) { + topFieldData = new byte[topFieldDataLength]; + data.readBytes(topFieldData, 0, topFieldDataLength); + } + if (bottomFieldDataLength > 0) { + bottomFieldData = new byte[bottomFieldDataLength]; + data.readBytes(bottomFieldData, 0, bottomFieldDataLength); } else { - System.arraycopy(tsStream.data, tsStream.getPosition() / 8, object.bottomFieldData, 0, object.bottomFieldDataLength); - tsStream.skipBits(object.bottomFieldDataLength * 8); + bottomFieldData = topFieldData; } } - return object; + return new ObjectData(objectId, nonModifyingColorFlag, topFieldData, bottomFieldData); } - private Bitmap parsePixelDataSubBlocks(ObjectData object, ClutDefinition clut, int regionDepth, - int horizontalAddress, int verticalAddress) { + private static int[] generateDefault2BitClutEntries() { + int[] entries = new int[4]; + entries[0] = 0x00000000; + entries[1] = 0xFFFFFFFF; + entries[2] = 0xFF000000; + entries[3] = 0xFF7F7F7F; + return entries; + } - /* Parse pixel-data sub-block. ETSI EN 300 743 7.2.5.1 + private static int[] generateDefault4BitClutEntries() { + int[] entries = new int[16]; + entries[0] = 0x00000000; + for (int i = 1; i < entries.length; i++) { + if (i < 8) { + entries[i] = getColor( + 0xFF, + ((i & 0x01) != 0 ? 0xFF : 0x00), + ((i & 0x02) != 0 ? 0xFF : 0x00), + ((i & 0x04) != 0 ? 0xFF : 0x00)); + } else { + entries[i] = getColor( + 0xFF, + ((i & 0x01) != 0 ? 0x7F : 0x00), + ((i & 0x02) != 0 ? 0x7F : 0x00), + ((i & 0x04) != 0 ? 0x7F : 0x00)); + } + } + return entries; + } - SYNTAX SIZE - --------------------------------------- ---- - pixel-data_sub-block() { - data_type 8 - if data_type =='0x10' { - repeat { - 2-bit/pixel_code_string() - } until (end of 2-bit/pixel_code_string) - while (!bytealigned()) - 2_stuff_bits 2 - if data_type =='0x11' { - repeat { - 4-bit/pixel_code_string() - } until (end of 4-bit/pixel_code_string) - if (!bytealigned()) - 4_stuff_bits 4 - } - } - if data_type =='0x12' { - repeat { - 8-bit/pixel_code_string() - } until (end of 8-bit/pixel_code_string) - } - if data_type =='0x20' - 2_to_4-bit_map-table 16 - if data_type =='0x21' - 2_to_8-bit_map-table 32 - if data_type =='0x22' - 4_to_8-bit_map-table 128 + private static int[] generateDefault8BitClutEntries() { + int[] entries = new int[256]; + entries[0] = 0x00000000; + for (int i = 0; i < entries.length; i++) { + if (i < 8) { + entries[i] = getColor( + 0x3F, + ((i & 0x01) != 0 ? 0xFF : 0x00), + ((i & 0x02) != 0 ? 0xFF : 0x00), + ((i & 0x04) != 0 ? 0xFF : 0x00)); + } else { + switch (i & 0x88) { + case 0x00: + entries[i] = getColor( + 0xFF, + (((i & 0x01) != 0 ? 0x55 : 0x00) + ((i & 0x10) != 0 ? 0xAA : 0x00)), + (((i & 0x02) != 0 ? 0x55 : 0x00) + ((i & 0x20) != 0 ? 0xAA : 0x00)), + (((i & 0x04) != 0 ? 0x55 : 0x00) + ((i & 0x40) != 0 ? 0xAA : 0x00))); + break; + case 0x08: + entries[i] = getColor( + 0x7F, + (((i & 0x01) != 0 ? 0x55 : 0x00) + ((i & 0x10) != 0 ? 0xAA : 0x00)), + (((i & 0x02) != 0 ? 0x55 : 0x00) + ((i & 0x20) != 0 ? 0xAA : 0x00)), + (((i & 0x04) != 0 ? 0x55 : 0x00) + ((i & 0x40) != 0 ? 0xAA : 0x00))); + break; + case 0x80: + entries[i] = getColor( + 0xFF, + (127 + ((i & 0x01) != 0 ? 0x2B : 0x00) + ((i & 0x10) != 0 ? 0x55 : 0x00)), + (127 + ((i & 0x02) != 0 ? 0x2B : 0x00) + ((i & 0x20) != 0 ? 0x55 : 0x00)), + (127 + ((i & 0x04) != 0 ? 0x2B : 0x00) + ((i & 0x40) != 0 ? 0x55 : 0x00))); + break; + case 0x88: + entries[i] = getColor( + 0xFF, + (((i & 0x01) != 0 ? 0x2B : 0x00) + ((i & 0x10) != 0 ? 0x55 : 0x00)), + (((i & 0x02) != 0 ? 0x2B : 0x00) + ((i & 0x20) != 0 ? 0x55 : 0x00)), + (((i & 0x04) != 0 ? 0x2B : 0x00) + ((i & 0x40) != 0 ? 0x55 : 0x00))); + break; } - */ + } + } + return entries; + } - int line, column; - int i; - byte[] clutMapTable, clutMapTable24, clutMapTable28, clutMapTable48; + private static int getColor(int a, int r, int g, int b) { + return (a << 24) | (r << 16) | (g << 8) | b; + } + // Static drawing. - ClutEntry[] clutEntries; - if (regionDepth == DVBSUB_RCS_BITDEPTH_8) { - clutEntries = clut.clutEntries8bit; - } else if (regionDepth == DVBSUB_RCS_BITDEPTH_4) { - clutEntries = clut.clutEntries4bit; + /** + * Draws a pixel data sub-block, as defined by ETSI EN 300 743 7.2.5.1, into a canvas. + */ + private static void paintPixelDataSubBlocks(ObjectData objectData, ClutDefinition clutDefinition, + int regionDepth, int horizontalAddress, int verticalAddress, Paint paint, Canvas canvas) { + int[] clutEntries; + if (regionDepth == REGION_DEPTH_8_BIT) { + clutEntries = clutDefinition.clutEntries8Bit; + } else if (regionDepth == REGION_DEPTH_4_BIT) { + clutEntries = clutDefinition.clutEntries4Bit; } else { - clutEntries = clut.clutEntries2bit; + clutEntries = clutDefinition.clutEntries2Bit; } - - int lineHeight; - - ParsableBitArray[] pixelData = new ParsableBitArray[2]; - pixelData[0] = new ParsableBitArray(object.topFieldData); - if (object.bottomFieldDataLength == 0) { - lineHeight = 2; - } else { - lineHeight = 1; - pixelData[1] = new ParsableBitArray(object.bottomFieldData); - - } - - ParsableBitArray data; - int field = 0; - while (field < 2) { - data = pixelData[field]; - column = horizontalAddress; - line = verticalAddress + field; - clutMapTable24 = null; - clutMapTable28 = null; - clutMapTable48 = null; - - while (data.bitsLeft() > 0) { - switch (data.readBits(8)) { - case DVBSUB_DT_2BP_CODE_STRING: - if (regionDepth == DVBSUB_RCS_BITDEPTH_8) { - clutMapTable = clutMapTable28 == null ? defaultMap28 : clutMapTable28; - } else if (regionDepth == DVBSUB_RCS_BITDEPTH_4) { - clutMapTable = clutMapTable24 == null ? defaultMap24 : clutMapTable24; - } else { - clutMapTable = null; - } - column += dvbSub2BitPixelCodeString(data, lineHeight, clutEntries, clutMapTable, - column, line, (object.flags & OBJECT_NON_MODIFYING_COLOUR_FLAG) == 0); - if ((i = data.getPosition() % 8) != 0) { - data.skipBits(7 - i + 1); - } - break; - case DVBSUB_DT_4BP_CODE_STRING: - if (regionDepth == DVBSUB_RCS_BITDEPTH_8) - clutMapTable = clutMapTable48 == null ? defaultMap48 : clutMapTable48; - else - clutMapTable = null; - column += dvbSub4BitPixelCodeString(data, lineHeight, clutEntries, clutMapTable, - column, line, (object.flags & OBJECT_NON_MODIFYING_COLOUR_FLAG) == 0); - if ((i = data.getPosition() % 8) != 0) { - data.skipBits(7 - i + 1); - } - break; - case DVBSUB_DT_8BP_CODE_STRING: - column += dvbSub8BitPixelCodeString(data, lineHeight, clutEntries, null, - column, line, (object.flags & OBJECT_NON_MODIFYING_COLOUR_FLAG) == 0); - break; - case DVBSUB_DT_24_TABLE_DATA: - clutMapTable24 = new byte[4]; - for (i = 0; i < 4; i++) { - clutMapTable24[i] = (byte) data.readBits(4); - } - break; - case DVBSUB_DT_28_TABLE_DATA: - clutMapTable28 = new byte[4]; - for (i = 0; i < 4; i++) { - clutMapTable28[i] = (byte) data.readBits(8); - } - break; - case DVBSUB_DT_48_TABLE_DATA: - clutMapTable48 = new byte[16]; - for (i = 0; i < 4; i++) { - clutMapTable48[i] = (byte) data.readBits(8); - } - break; - case DVBSUB_DT_END_LINE: - column = horizontalAddress; - line += 2; - break; - default: - break; - } - } - field += lineHeight; - } - - return null; + paintPixelDataSubBlock(objectData.topFieldData, clutEntries, regionDepth, horizontalAddress, + verticalAddress, paint, canvas); + paintPixelDataSubBlock(objectData.bottomFieldData, clutEntries, regionDepth, horizontalAddress, + verticalAddress + 1, paint, canvas); } - private int dvbSub2BitPixelCodeString(ParsableBitArray data, int lineHeigth, - ClutEntry[] clutEntries, byte[] clutMapTable, - int column, int line, boolean paint) { + /** + * Draws a pixel data sub-block, as defined by ETSI EN 300 743 7.2.5.1, into a canvas. + */ + private static void paintPixelDataSubBlock(byte[] pixelData, int[] clutEntries, int regionDepth, + int horizontalAddress, int verticalAddress, Paint paint, Canvas canvas) { + ParsableBitArray data = new ParsableBitArray(pixelData); + int column = horizontalAddress; + int line = verticalAddress; + byte[] clutMapTable2To4 = null; + byte[] clutMapTable2To8 = null; + byte[] clutMapTable4To8 = null; - /* Parse 2-bit/pixel code string. ETSI EN 300 743 7.2.5.2 - - SYNTAX SIZE - --------------------------------------- ---- - 2-bit/pixel_code_string() { - if (nextbits() != '00') { - 2-bit_pixel-code 2 - } else { - 2-bit_zero 2 - switch_1 1 bslbf - if (switch_1 == '1') { - run_length_3-10 3 - 2-bit_pixel-code 2 - } else { - switch_2 1 - if (switch_2 == '0') { - switch_3 2 - if (switch_3 == '10') { - run_length_12-27 4 - 2-bit_pixel-code 2 - } - if (switch_3 == '11') { - run_length_29-284 8 - 2-bit_pixel-code 2 - } - } - } - } - } - */ - - int savedColumn = column, peek, runLength, clutIdx = 0x00, colour; - boolean endOfPixelCodeString = false; - - while (!endOfPixelCodeString) { - runLength = 0; - peek = data.readBits(2); - if (peek != 0x00) { - runLength = 1; - clutIdx = peek; - } else { - peek = data.readBits(1); - if (peek == 0x01) { - runLength = 3 + data.readBits(3); - clutIdx = data.readBits(2); - } else { - peek = data.readBits(1); - if (peek == 0x00) { - peek = data.readBits(2); - switch (peek) { - case 0x00: - endOfPixelCodeString = true; - break; - case 0x01: - runLength = 2; - clutIdx = 0x00; - break; - case 0x02: - runLength = 12 + data.readBits(4); - clutIdx = data.readBits(2); - break; - case 0x03: - runLength = 29 + data.readBits(8); - clutIdx = data.readBits(2); - break; - } - } - } - } - - if (runLength != 0 && paint) { - colour = clutMapTable != null ? clutEntries[clutMapTable[clutIdx]].ARGB - : clutEntries[clutIdx].ARGB; - defaultPaint.setColor(colour); - canvas.drawRect( - column, line, column + runLength, line + lineHeigth, defaultPaint); - } - - column += runLength; - } - - return column - savedColumn; - } - - private int dvbSub4BitPixelCodeString(ParsableBitArray data, int lineHeigth, - ClutEntry[] clutEntries, byte[] clutMapTable, - int column, int line, boolean paint) { - - /* Parse 4-bit/pixel code string. ETSI EN 300 743 7.2.5.2 - - SYNTAX SIZE - --------------------------------------- ---- - 4-bit/pixel_code_string() { - if (nextbits() != '0000') { - 4-bit_pixel-code 4 - } else { - 4-bit_zero 4 - switch_1 1 - if (switch_1 == '0') { - if (nextbits() != '000') - run_length_3-9 3 - else - end_of_string_signal 3 - } else { - switch_2 1 - if (switch_2 == '0') { - run_length_4-7 2 - 4-bit_pixel-code 4 - } else { - switch_3 2 - if (switch_3 == '10') { - run_length_9-24 4 - 4-bit_pixel-code 4 - } - if (switch_3 == '11') { - run_length_25-280 8 - 4-bit_pixel-code 4 - } - } - } - } - } - */ - - int savedColumn = column, peek, runLength, clutIdx = 0x00, colour; - boolean endOfPixelCodeString = false; - - while (!endOfPixelCodeString) { - runLength = 0; - peek = data.readBits(4); - if (peek != 0x00) { - runLength = 1; - clutIdx = peek; - } else { - peek = data.readBits(1); - if (peek == 0x00) { - peek = data.readBits(3); - if (peek != 0x00) { - runLength = 2 + peek; - clutIdx = 0x00; + while (data.bitsLeft() != 0) { + int dataType = data.readBits(8); + switch (dataType) { + case DATA_TYPE_2BP_CODE_STRING: + byte[] clutMapTable2ToX; + if (regionDepth == REGION_DEPTH_8_BIT) { + clutMapTable2ToX = clutMapTable2To8 == null ? defaultMap2To8 : clutMapTable2To8; + } else if (regionDepth == REGION_DEPTH_4_BIT) { + clutMapTable2ToX = clutMapTable2To4 == null ? defaultMap2To4 : clutMapTable2To4; } else { + clutMapTable2ToX = null; + } + column = paint2BitPixelCodeString(data, clutEntries, clutMapTable2ToX, column, line, + paint, canvas); + data.byteAlign(); + break; + case DATA_TYPE_4BP_CODE_STRING: + byte[] clutMapTable4ToX; + if (regionDepth == REGION_DEPTH_8_BIT) { + clutMapTable4ToX = clutMapTable4To8 == null ? defaultMap4To8 : clutMapTable4To8; + } else { + clutMapTable4ToX = null; + } + column = paint4BitPixelCodeString(data, clutEntries, clutMapTable4ToX, column, line, + paint, canvas); + data.byteAlign(); + break; + case DATA_TYPE_8BP_CODE_STRING: + column = paint8BitPixelCodeString(data, clutEntries, null, column, line, paint, canvas); + break; + case DATA_TYPE_24_TABLE_DATA: + clutMapTable2To4 = buildClutMapTable(4, 4, data); + break; + case DATA_TYPE_28_TABLE_DATA: + clutMapTable2To8 = buildClutMapTable(4, 8, data); + break; + case DATA_TYPE_48_TABLE_DATA: + clutMapTable2To8 = buildClutMapTable(16, 8, data); + break; + case DATA_TYPE_END_LINE: + column = horizontalAddress; + line += 2; + break; + default: + // Do nothing. + break; + } + } + } + + /** + * Paint a 2-bit/pixel code string, as defined by ETSI EN 300 743 7.2.5.2, to a canvas. + */ + private static int paint2BitPixelCodeString(ParsableBitArray data, int[] clutEntries, + byte[] clutMapTable, int column, int line, Paint paint, Canvas canvas) { + boolean endOfPixelCodeString = false; + do { + int runLength = 0; + int clutIndex = 0; + int peek = data.readBits(2); + if (!data.readBit()) { + runLength = 1; + clutIndex = peek; + } else if (data.readBit()) { + runLength = 3 + data.readBits(3); + clutIndex = data.readBits(2); + } else if (!data.readBit()) { + switch (data.readBits(2)) { + case 0x00: endOfPixelCodeString = true; - } - } else { - peek = data.readBits(1); - if (peek == 0x00) { - runLength = 4 + data.readBits(2); - clutIdx = data.readBits(4); - } else { - peek = data.readBits(2); - switch (peek) { - case 0x00: - runLength = 1; - clutIdx = 0x00; - break; - case 0x01: - runLength = 2; - clutIdx = 0x00; - break; - case 0x02: - runLength = 9 + data.readBits(4); - clutIdx = data.readBits(4); - break; - case 0x03: - runLength = 25 + data.readBits(8); - clutIdx = data.readBits(4); - break; - } - } + break; + case 0x01: + runLength = 2; + break; + case 0x02: + runLength = 12 + data.readBits(4); + clutIndex = data.readBits(2); + break; + case 0x03: + runLength = 29 + data.readBits(8); + clutIndex = data.readBits(2); + break; } } - if (runLength != 0 && paint) { - colour = clutMapTable != null ? clutEntries[clutMapTable[clutIdx]].ARGB - : clutEntries[clutIdx].ARGB; - defaultPaint.setColor(colour); - canvas.drawRect( - column, line, column + runLength, line + lineHeigth, defaultPaint); + if (runLength != 0 && paint != null) { + paint.setColor(clutEntries[clutMapTable != null ? clutMapTable[clutIndex] : clutIndex]); + canvas.drawRect(column, line, column + runLength, line + 1, paint); } column += runLength; - } + } while (!endOfPixelCodeString); - return column - savedColumn; + return column; } - private int dvbSub8BitPixelCodeString(ParsableBitArray data, int lineHeigth, - ClutEntry[] clutEntries, byte[] clutMapTable, - int column, int line, boolean paint) { - - /* Parse 8-bit/pixel code string. ETSI EN 300 743 7.2.5.2 - - SYNTAX SIZE - --------------------------------------- ---- - - 8-bit/pixel_code_string() { - if (nextbits() != '0000 0000') { - 8-bit_pixel-code 8 - } else { - 8-bit_zero 8 - switch_1 1 - if switch_1 == '0' { - if nextbits() != '000 0000' - run_length_1-127 7 - else - end_of_string_signal 7 - } else { - run_length_3-127 7 - 8-bit_pixel-code 8 - } - } - } - */ - - int savedColumn = column, peek, runLength, clutIdx = 0x00, colour; + /** + * Paint a 4-bit/pixel code string, as defined by ETSI EN 300 743 7.2.5.2, to a canvas. + */ + private static int paint4BitPixelCodeString(ParsableBitArray data, int[] clutEntries, + byte[] clutMapTable, int column, int line, Paint paint, Canvas canvas) { boolean endOfPixelCodeString = false; - - while (!endOfPixelCodeString) { - runLength = 0; - peek = data.readBits(8); + do { + int runLength = 0; + int clutIndex = 0; + int peek = data.readBits(4); if (peek != 0x00) { runLength = 1; - clutIdx = peek; + clutIndex = peek; + } else if (!data.readBit()) { + peek = data.readBits(3); + if (peek != 0x00) { + runLength = 2 + peek; + clutIndex = 0x00; + } else { + endOfPixelCodeString = true; + } + } else if (!data.readBit()) { + runLength = 4 + data.readBits(2); + clutIndex = data.readBits(4); } else { - peek = data.readBits(1); - if (peek == 0x00) { + switch (data.readBits(2)) { + case 0x00: + runLength = 1; + break; + case 0x01: + runLength = 2; + break; + case 0x02: + runLength = 9 + data.readBits(4); + clutIndex = data.readBits(4); + break; + case 0x03: + runLength = 25 + data.readBits(8); + clutIndex = data.readBits(4); + break; + } + } + + if (runLength != 0 && paint != null) { + paint.setColor(clutEntries[clutMapTable != null ? clutMapTable[clutIndex] : clutIndex]); + canvas.drawRect(column, line, column + runLength, line + 1, paint); + } + + column += runLength; + } while (!endOfPixelCodeString); + + return column; + } + + /** + * Paint an 8-bit/pixel code string, as defined by ETSI EN 300 743 7.2.5.2, to a canvas. + */ + private static int paint8BitPixelCodeString(ParsableBitArray data, int[] clutEntries, + byte[] clutMapTable, int column, int line, Paint paint, Canvas canvas) { + boolean endOfPixelCodeString = false; + do { + int runLength = 0; + int clutIndex = 0; + int peek = data.readBits(8); + if (peek != 0x00) { + runLength = 1; + clutIndex = peek; + } else { + if (!data.readBit()) { peek = data.readBits(7); if (peek != 0x00) { runLength = peek; - clutIdx = 0x00; + clutIndex = 0x00; } else { endOfPixelCodeString = true; } } else { runLength = data.readBits(7); - clutIdx = data.readBits(8); + clutIndex = data.readBits(8); } } - if (runLength != 0 && paint) { - colour = clutMapTable != null ? clutEntries[clutMapTable[clutIdx]].ARGB - : clutEntries[clutIdx].ARGB; - defaultPaint.setColor(colour); - canvas.drawRect( - column, line, column + runLength, line + lineHeigth, defaultPaint); + if (runLength != 0 && paint != null) { + paint.setColor(clutEntries[clutMapTable != null ? clutMapTable[clutIndex] : clutIndex]); + canvas.drawRect(column, line, column + runLength, line + 1, paint); } - column += runLength; + } while (!endOfPixelCodeString); + + return column; + } + + private static byte[] buildClutMapTable(int length, int bitsPerEntry, ParsableBitArray data) { + byte[] clutMapTable = new byte[length]; + for (int i = 0; i < length; i++) { + clutMapTable[i] = (byte) data.readBits(bitsPerEntry); + } + return clutMapTable; + } + + // Private inner classes. + + /** + * The subtitle service definition. + */ + private static final class SubtitleService { + + public final int subtitlePageId; + public final int ancillaryPageId; + + public final SparseArray regions = new SparseArray<>(); + public final SparseArray cluts = new SparseArray<>(); + public final SparseArray objects = new SparseArray<>(); + public final SparseArray ancillaryCluts = new SparseArray<>(); + public final SparseArray ancillaryObjects = new SparseArray<>(); + + public DisplayDefinition displayDefinition; + public PageComposition pageComposition; + + public SubtitleService(int subtitlePageId, int ancillaryPageId) { + this.subtitlePageId = subtitlePageId; + this.ancillaryPageId = ancillaryPageId; + } + + public void reset() { + regions.clear(); + cluts.clear(); + objects.clear(); + ancillaryCluts.clear(); + ancillaryObjects.clear(); + displayDefinition = null; + pageComposition = null; } - return column - savedColumn; } /** - * Takes a subtitling packet, parses the included segments and returns the list of {@link Cue}s - * defined in them - * - * @param input - * @param inputSize - * @return list of {@link Cue}s contained in the packet or null if there is an error or subtitle - * is incomplete + * Contains the geometry and active area of the subtitle service. + *

    + * See ETSI EN 300 743 7.2.1 */ - List dvbSubsDecode(byte[] input, int inputSize) { + private static final class DisplayDefinition { - /* process PES PACKET. ETSI EN 300 743 7.1 + public final int width; + public final int height; - SYNTAX SIZE SEMANTICS - --------------------------------------- ---- ------------------------------------------ - PES_data_field() { - data_identifier 8 For DVB subtitle streams it shall be 0x20 - subtitle_stream_id 8 For DVB subtitling stream it shall be 0x00 - while nextbits() == '0000 1111' { - Subtitling_segment() - } - end_of_PES_data_field_marker 8 An 8-bit field with fixed contents '1111 1111' + public final int horizontalPositionMinimum; + public final int horizontalPositionMaximum; + public final int verticalPositionMinimum; + public final int verticalPositionMaximum; - */ - - if (input != null) { - tsStream = new ParsableBitArray(input, inputSize); - } else { - return null; - } - if (!isSet(FLAG_PES_STRIPPED_DVBSUB)) { - if (tsStream.readBits(8) != 0x20) { // data_identifier - return null; - } - if (tsStream.readBits(8) != 0x00) { // subtitle_stream_id - return null; - } + public DisplayDefinition(int width, int height, int horizontalPositionMinimum, + int horizontalPositionMaximum, int verticalPositionMinimum, int verticalPositionMaximum) { + this.width = width; + this.height = height; + this.horizontalPositionMinimum = horizontalPositionMinimum; + this.horizontalPositionMaximum = horizontalPositionMaximum; + this.verticalPositionMinimum = verticalPositionMinimum; + this.verticalPositionMaximum = verticalPositionMaximum; } - if (BuildConfig.DEBUG) Log.d(TAG, "New PES subtitle packet."); - - int sync = tsStream.readBits(8); - // test for segment Sync Byte and account for possible additional wordalign byte in Object data segment - while (sync == 0x0f || (sync == 0x00 && (sync = tsStream.readBits(8)) == 0x0f)) { - parseSubtitlingSegment(); - if (isSet(FLAG_PES_STRIPPED_DVBSUB) && tsStream.bitsLeft() == 0) { - break; - } - sync = tsStream.readBits(8); - - } - - if (sync == 0xff || (isSet(FLAG_PES_STRIPPED_DVBSUB) && tsStream.bitsLeft() == 0)) { // end_of_PES_data_field_marker - // paint the current Subtitle definition - if (subtitleService.pageComposition != null) { - List cueList = new ArrayList<>(); - - if (BuildConfig.DEBUG) { - Log.d(TAG, "Rendering subtitle. w: " + subtitleService.displayDefinition.displayWidth + - " h: " + subtitleService.displayDefinition.displayHeight); - - if ((subtitleService.displayDefinition.flags & DISPLAY_WINDOW_FLAG) != 0) { - Log.d(TAG, " Window dimensions (x/y/w/h): (" + - subtitleService.displayDefinition.displayWindowHorizontalPositionMinimum + "/" + - subtitleService.displayDefinition.displayWindowVerticalPositionMinimum + "/" + - (subtitleService.displayDefinition.displayWindowHorizontalPositionMaximum - - subtitleService.displayDefinition.displayWindowHorizontalPositionMinimum) + "/" + - (subtitleService.displayDefinition.displayWindowVerticalPositionMaximum - - subtitleService.displayDefinition.displayWindowVerticalPositionMinimum) + ")"); - } - } - - int a, b; - PageRegion pageRegion; - RegionComposition regionComposition; - int baseHorizontalAddress, baseVerticalAddress; - ObjectData object; - ClutDefinition clut; - int regionKey; - // process page regions - for (a = 0; a < subtitleService.pageComposition.pageRegions.size(); a++) { - regionKey = subtitleService.pageComposition.pageRegions.keyAt(a); - pageRegion = subtitleService.pageComposition.pageRegions.get(regionKey); - regionComposition = subtitleService.regions.get(regionKey); - - baseHorizontalAddress = pageRegion.regionHorizontalAddress; - baseVerticalAddress = pageRegion.regionVerticalAddress; - - if ((subtitleService.displayDefinition.flags & DISPLAY_WINDOW_FLAG) != 0) { - baseHorizontalAddress += - subtitleService.displayDefinition.displayWindowHorizontalPositionMinimum; - baseVerticalAddress += - subtitleService.displayDefinition.displayWindowVerticalPositionMinimum; - } - - // clip object drawing to the current region and display definition window - canvas.clipRect( - baseHorizontalAddress, baseVerticalAddress, - Math.min(baseHorizontalAddress + regionComposition.regionWidth, - subtitleService.displayDefinition.displayWindowHorizontalPositionMaximum), - Math.min(baseVerticalAddress + regionComposition.regionHeight, - subtitleService.displayDefinition.displayWindowVerticalPositionMaximum), - Region.Op.REPLACE); - - if ((clut = subtitleService.cluts.get(regionComposition.clutId)) == null) { - if ((clut = subtitleService.ancillaryCluts.get(regionComposition.clutId)) == null) { - clut = defaultClut; - } - } - - if (BuildConfig.DEBUG) { - Log.d(TAG, " Region: " + regionKey + " (x/y/w/h): (" + - baseHorizontalAddress + "/" + baseVerticalAddress + "/" + - (baseHorizontalAddress + regionComposition.regionWidth - 1) + "/" + - (baseVerticalAddress + regionComposition.regionHeight - 1) + ")" - ); - - canvas.drawRect( - baseHorizontalAddress, baseVerticalAddress, - baseHorizontalAddress + regionComposition.regionWidth - 1, - baseVerticalAddress + regionComposition.regionHeight - 1, - debugRegionPaint); - } - - RegionObject regionObject; - int objectKey; - // process regions compositions - for (b = 0; b < regionComposition.regionObjects.size(); b++) { - objectKey = regionComposition.regionObjects.keyAt(b); - regionObject = regionComposition.regionObjects.get(objectKey); - - if (BuildConfig.DEBUG) { - Log.d(TAG, " Object[" + objectKey + "]. objectId: " + regionObject.objectId + " (x/y): (" + - (baseHorizontalAddress + regionObject.objectHorizontalPosition) + "/" + - (baseVerticalAddress + regionObject.objectVerticalPosition) + ")" - ); - - canvas.drawRect( - baseHorizontalAddress + regionObject.objectHorizontalPosition, - baseVerticalAddress + regionObject.objectVerticalPosition, - baseHorizontalAddress + regionObject.objectHorizontalPosition + regionComposition.regionWidth - 1, - baseVerticalAddress + regionObject.objectVerticalPosition + regionComposition.regionHeight - 1, - debugObjectPaint); - } - - if ((object = subtitleService.objects.get(regionObject.objectId)) == null) { - if ((object = subtitleService.ancillaryObjects.get(regionObject.objectId)) == null) { - continue; - } - } - - parsePixelDataSubBlocks(object, clut, regionComposition.regionDepth, - baseHorizontalAddress + regionObject.objectHorizontalPosition, - baseVerticalAddress + regionObject.objectVerticalPosition); - - } - - // fill the region if needed - if ((regionComposition.flags & REGION_FILL_FLAG) != 0) { - int colour; - if (regionComposition.regionDepth == DVBSUB_RCS_BITDEPTH_8) { - colour = clut.clutEntries8bit[regionComposition.region8bitPixelCode].ARGB; - } else if (regionComposition.regionDepth == DVBSUB_RCS_BITDEPTH_4) { - colour = clut.clutEntries4bit[regionComposition.region4bitPixelCode].ARGB; - } else { - colour = clut.clutEntries2bit[regionComposition.region2bitPixelCode].ARGB; - } - - fillRegionPaint.setColor(colour); - - canvas.drawRect( - baseHorizontalAddress, baseVerticalAddress, - baseHorizontalAddress + regionComposition.regionWidth, - baseVerticalAddress + regionComposition.regionHeight, - fillRegionPaint); - } - - Bitmap cueBitmap = Bitmap.createBitmap(bitmap, - baseHorizontalAddress, baseVerticalAddress, - regionComposition.regionWidth, regionComposition.regionHeight); - canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR); - regionComposition.cue = new Cue(cueBitmap, - (float) baseHorizontalAddress / subtitleService.displayDefinition.displayWidth, Cue.ANCHOR_TYPE_START, - (float) baseVerticalAddress / subtitleService.displayDefinition.displayHeight, Cue.ANCHOR_TYPE_START, - (float) regionComposition.regionWidth / subtitleService.displayDefinition.displayWidth, - (float) regionComposition.regionHeight / subtitleService.displayDefinition.displayHeight); - cueList.add(regionComposition.cue); - } - - return cueList; - } - } else { - Log.d(TAG, "Unexpected..."); - } - return null; } - private boolean isSet(@Flags int flag) { - return (flags & flag) != 0; + /** + * The page is the definition and arrangement of regions in the screen. + *

    + * See ETSI EN 300 743 7.2.2 + */ + private static final class PageComposition { + + public final int timeOutSecs; // TODO: Use this or remove it. + public final int version; + public final int state; + public final SparseArray regions; + + public PageComposition(int timeoutSecs, int version, int state, + SparseArray regions) { + this.timeOutSecs = timeoutSecs; + this.version = version; + this.state = state; + this.regions = regions; + } + + } + + /** + * A region within a {@link PageComposition}. + *

    + * See ETSI EN 300 743 7.2.2 + */ + private static final class PageRegion { + + public final int horizontalAddress; + public final int verticalAddress; + + public PageRegion(int horizontalAddress, int verticalAddress) { + this.horizontalAddress = horizontalAddress; + this.verticalAddress = verticalAddress; + } + + } + + /** + * An area of the page composed of a list of objects and a CLUT. + *

    + * See ETSI EN 300 743 7.2.3 + */ + private static final class RegionComposition { + + public final int id; + public final boolean fillFlag; + public final int width; + public final int height; + public final int levelOfCompatibility; // TODO: Use this or remove it. + public final int depth; + public final int clutId; + public final int pixelCode8Bit; + public final int pixelCode4Bit; + public final int pixelCode2Bit; + public final SparseArray regionObjects; + + public RegionComposition(int id, boolean fillFlag, int width, int height, + int levelOfCompatibility, int depth, int clutId, int pixelCode8Bit, int pixelCode4Bit, + int pixelCode2Bit, SparseArray regionObjects) { + this.id = id; + this.fillFlag = fillFlag; + this.width = width; + this.height = height; + this.levelOfCompatibility = levelOfCompatibility; + this.depth = depth; + this.clutId = clutId; + this.pixelCode8Bit = pixelCode8Bit; + this.pixelCode4Bit = pixelCode4Bit; + this.pixelCode2Bit = pixelCode2Bit; + this.regionObjects = regionObjects; + } + + public void mergeFrom(RegionComposition otherRegionComposition) { + if (otherRegionComposition == null) { + return; + } + SparseArray otherRegionObjects = otherRegionComposition.regionObjects; + for (int i = 0; i < otherRegionObjects.size(); i++) { + regionObjects.put(otherRegionObjects.keyAt(i), otherRegionObjects.valueAt(i)); + } + } + + } + + /** + * An object within a {@link RegionComposition}. + *

    + * See ETSI EN 300 743 7.2.3 + */ + private static final class RegionObject { + + public final int type; // TODO: Use this or remove it. + public final int provider; // TODO: Use this or remove it. + public final int horizontalPosition; + public final int verticalPosition; + public final int foregroundPixelCode; // TODO: Use this or remove it. + public final int backgroundPixelCode; // TODO: Use this or remove it. + + public RegionObject(int type, int provider, int horizontalPosition, + int verticalPosition, int foregroundPixelCode, int backgroundPixelCode) { + this.type = type; + this.provider = provider; + this.horizontalPosition = horizontalPosition; + this.verticalPosition = verticalPosition; + this.foregroundPixelCode = foregroundPixelCode; + this.backgroundPixelCode = backgroundPixelCode; + } + + } + + /** + * CLUT family definition containing the color tables for the three bit depths defined + *

    + * See ETSI EN 300 743 7.2.4 + */ + private static final class ClutDefinition { + + public final int id; + public final int[] clutEntries2Bit; + public final int[] clutEntries4Bit; + public final int[] clutEntries8Bit; + + public ClutDefinition(int id, int[] clutEntries2Bit, int[] clutEntries4Bit, + int[] clutEntries8bit) { + this.id = id; + this.clutEntries2Bit = clutEntries2Bit; + this.clutEntries4Bit = clutEntries4Bit; + this.clutEntries8Bit = clutEntries8bit; + } + + } + + /** + * The textual or graphical representation of an object. + *

    + * See ETSI EN 300 743 7.2.5 + */ + private static final class ObjectData { + + public final int id; + public final boolean nonModifyingColorFlag; + public final byte[] topFieldData; + public final byte[] bottomFieldData; + + public ObjectData(int id, boolean nonModifyingColorFlag, byte[] topFieldData, + byte[] bottomFieldData) { + this.id = id; + this.nonModifyingColorFlag = nonModifyingColorFlag; + this.topFieldData = topFieldData; + this.bottomFieldData = bottomFieldData; + } + } } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/text/dvb/DvbSubtitle.java b/library/core/src/main/java/com/google/android/exoplayer2/text/dvb/DvbSubtitle.java index d614f1c498..75728359c7 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/text/dvb/DvbSubtitle.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/text/dvb/DvbSubtitle.java @@ -18,41 +18,37 @@ package com.google.android.exoplayer2.text.dvb; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.text.Cue; import com.google.android.exoplayer2.text.Subtitle; - -import java.util.Collections; import java.util.List; /** * A representation of a DVB subtitle. */ /* package */ final class DvbSubtitle implements Subtitle { - private final List cues; - public DvbSubtitle(List cues) { - if (cues == null) { - this.cues = Collections.emptyList(); - } else { - this.cues = cues; - } - } + private final List cues; - @Override - public int getNextEventTimeIndex(long timeUs) { - return C.INDEX_UNSET; - } + public DvbSubtitle(List cues) { + this.cues = cues; + } - @Override - public int getEventTimeCount() { - return 1; - } + @Override + public int getNextEventTimeIndex(long timeUs) { + return C.INDEX_UNSET; + } - @Override - public long getEventTime(int index) { - return 0; - } + @Override + public int getEventTimeCount() { + return 1; + } - @Override - public List getCues(long timeUs) { - return cues; - } -} \ No newline at end of file + @Override + public long getEventTime(int index) { + return 0; + } + + @Override + public List getCues(long timeUs) { + return cues; + } + +} diff --git a/library/core/src/main/java/com/google/android/exoplayer2/text/subrip/SubripDecoder.java b/library/core/src/main/java/com/google/android/exoplayer2/text/subrip/SubripDecoder.java index a848022ba9..e76f0fd7e2 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/text/subrip/SubripDecoder.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/text/subrip/SubripDecoder.java @@ -46,7 +46,7 @@ public final class SubripDecoder extends SimpleSubtitleDecoder { } @Override - protected SubripSubtitle decode(byte[] bytes, int length) { + protected SubripSubtitle decode(byte[] bytes, int length, boolean reset) { ArrayList cues = new ArrayList<>(); LongArray cueTimesUs = new LongArray(); ParsableByteArray subripData = new ParsableByteArray(bytes, length); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/text/ttml/TtmlDecoder.java b/library/core/src/main/java/com/google/android/exoplayer2/text/ttml/TtmlDecoder.java index 34ff757e2f..71ce17eeed 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/text/ttml/TtmlDecoder.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/text/ttml/TtmlDecoder.java @@ -94,7 +94,8 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder { } @Override - protected TtmlSubtitle decode(byte[] bytes, int length) throws SubtitleDecoderException { + protected TtmlSubtitle decode(byte[] bytes, int length, boolean reset) + throws SubtitleDecoderException { try { XmlPullParser xmlParser = xmlParserFactory.newPullParser(); Map globalStyles = new HashMap<>(); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/text/tx3g/Tx3gDecoder.java b/library/core/src/main/java/com/google/android/exoplayer2/text/tx3g/Tx3gDecoder.java index 7484a5244d..dccb64caec 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/text/tx3g/Tx3gDecoder.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/text/tx3g/Tx3gDecoder.java @@ -35,7 +35,7 @@ public final class Tx3gDecoder extends SimpleSubtitleDecoder { } @Override - protected Subtitle decode(byte[] bytes, int length) { + protected Subtitle decode(byte[] bytes, int length, boolean reset) { parsableByteArray.reset(bytes, length); int textLength = parsableByteArray.readUnsignedShort(); if (textLength == 0) { diff --git a/library/core/src/main/java/com/google/android/exoplayer2/text/webvtt/Mp4WebvttDecoder.java b/library/core/src/main/java/com/google/android/exoplayer2/text/webvtt/Mp4WebvttDecoder.java index 916e67128a..159dd4f2e0 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/text/webvtt/Mp4WebvttDecoder.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/text/webvtt/Mp4WebvttDecoder.java @@ -45,7 +45,8 @@ public final class Mp4WebvttDecoder extends SimpleSubtitleDecoder { } @Override - protected Mp4WebvttSubtitle decode(byte[] bytes, int length) throws SubtitleDecoderException { + protected Mp4WebvttSubtitle decode(byte[] bytes, int length, boolean reset) + throws SubtitleDecoderException { // Webvtt in Mp4 samples have boxes inside of them, so we have to do a traditional box parsing: // first 4 bytes size and then 4 bytes type. sampleData.reset(bytes, length); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/text/webvtt/WebvttDecoder.java b/library/core/src/main/java/com/google/android/exoplayer2/text/webvtt/WebvttDecoder.java index fb177dcea7..7c3262fbba 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/text/webvtt/WebvttDecoder.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/text/webvtt/WebvttDecoder.java @@ -54,7 +54,8 @@ public final class WebvttDecoder extends SimpleSubtitleDecoder { } @Override - protected WebvttSubtitle decode(byte[] bytes, int length) throws SubtitleDecoderException { + protected WebvttSubtitle decode(byte[] bytes, int length, boolean reset) + throws SubtitleDecoderException { parsableWebvttData.reset(bytes, length); // Initialization for consistent starting state. webvttCueBuilder.reset(); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/util/ParsableBitArray.java b/library/core/src/main/java/com/google/android/exoplayer2/util/ParsableBitArray.java index 83f83c7921..df9f04f067 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/util/ParsableBitArray.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/util/ParsableBitArray.java @@ -89,6 +89,16 @@ public final class ParsableBitArray { return byteOffset * 8 + bitOffset; } + /** + * Returns the current byte offset. Must only be called when the position is byte aligned. + * + * @throws IllegalStateException If the position isn't byte aligned. + */ + public int getBytePosition() { + Assertions.checkState(bitOffset == 0); + return byteOffset; + } + /** * Sets the current bit offset. * @@ -177,6 +187,47 @@ public final class ParsableBitArray { return returnValue; } + /** + * Aligns the position to the next byte boundary. Does nothing if the position is already aligned. + */ + public void byteAlign() { + if (bitOffset == 0) { + return; + } + bitOffset = 0; + byteOffset++; + assertValidOffset(); + } + + /** + * Reads the next {@code length} bytes into {@code buffer}. Must only be called when the position + * is byte aligned. + * + * @see System#arraycopy(Object, int, Object, int, int) + * @param buffer The array into which the read data should be written. + * @param offset The offset in {@code buffer} at which the read data should be written. + * @param length The number of bytes to read. + * @throws IllegalStateException If the position isn't byte aligned. + */ + public void readBytes(byte[] buffer, int offset, int length) { + Assertions.checkState(bitOffset == 0); + System.arraycopy(data, byteOffset, buffer, offset, length); + byteOffset += length; + assertValidOffset(); + } + + /** + * Skips the next {@code length} bytes. Must only be called when the position is byte aligned. + * + * @param length The number of bytes to read. + * @throws IllegalStateException If the position isn't byte aligned. + */ + public void skipBytes(int length) { + Assertions.checkState(bitOffset == 0); + byteOffset += length; + assertValidOffset(); + } + private void assertValidOffset() { // It is fine for position to be at the end of the array, but no further. Assertions.checkState(byteOffset >= 0 diff --git a/library/ui/src/main/java/com/google/android/exoplayer2/ui/SubtitlePainter.java b/library/ui/src/main/java/com/google/android/exoplayer2/ui/SubtitlePainter.java index 9255f35302..bd03464b20 100644 --- a/library/ui/src/main/java/com/google/android/exoplayer2/ui/SubtitlePainter.java +++ b/library/ui/src/main/java/com/google/android/exoplayer2/ui/SubtitlePainter.java @@ -315,7 +315,7 @@ import com.google.android.exoplayer2.util.Util; float anchorX = parentLeft + (parentWidth * cuePosition); float anchorY = parentTop + (parentHeight * cueLine); int width = Math.round(parentWidth * cueSize); - int height = cueBitmapHeight != -1 ? Math.round(parentHeight * cueBitmapHeight) + int height = cueBitmapHeight != Cue.DIMEN_UNSET ? Math.round(parentHeight * cueBitmapHeight) : Math.round(width * ((float) cueBitmap.getHeight() / cueBitmap.getWidth())); int x = Math.round(cueLineAnchor == Cue.ANCHOR_TYPE_END ? (anchorX - width) : cueLineAnchor == Cue.ANCHOR_TYPE_MIDDLE ? (anchorX - (width / 2)) : anchorX); From ac0a4353dacd5deff660dbba10c0a63e354fe29b Mon Sep 17 00:00:00 2001 From: tonihei Date: Fri, 7 Apr 2017 03:38:00 -0700 Subject: [PATCH 069/119] SubtitleView - New method setApplyEmbeddedFontSizes When option is set to false, SubtitlePainter strips all absolute and relative font size spans from the cue text. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=152484724 --- .../exoplayer2/ui/SubtitlePainter.java | 41 ++++++++++++++----- .../android/exoplayer2/ui/SubtitleView.java | 26 ++++++++++-- 2 files changed, 54 insertions(+), 13 deletions(-) diff --git a/library/ui/src/main/java/com/google/android/exoplayer2/ui/SubtitlePainter.java b/library/ui/src/main/java/com/google/android/exoplayer2/ui/SubtitlePainter.java index bd03464b20..b75ac2b990 100644 --- a/library/ui/src/main/java/com/google/android/exoplayer2/ui/SubtitlePainter.java +++ b/library/ui/src/main/java/com/google/android/exoplayer2/ui/SubtitlePainter.java @@ -27,9 +27,12 @@ import android.graphics.Paint.Style; import android.graphics.Rect; import android.graphics.RectF; import android.text.Layout.Alignment; +import android.text.SpannableStringBuilder; import android.text.StaticLayout; import android.text.TextPaint; import android.text.TextUtils; +import android.text.style.AbsoluteSizeSpan; +import android.text.style.RelativeSizeSpan; import android.util.DisplayMetrics; import android.util.Log; import com.google.android.exoplayer2.text.CaptionStyleCompat; @@ -79,6 +82,7 @@ import com.google.android.exoplayer2.util.Util; private float cueSize; private float cueBitmapHeight; private boolean applyEmbeddedStyles; + private boolean applyEmbeddedFontSizes; private int foregroundColor; private int backgroundColor; private int windowColor; @@ -133,6 +137,8 @@ import com.google.android.exoplayer2.util.Util; * * @param cue The cue to draw. * @param applyEmbeddedStyles Whether styling embedded within the cue should be applied. + * @param applyEmbeddedFontSizes If {@code applyEmbeddedStyles} is true, defines whether font + * sizes embedded within the cue should be applied. Otherwise, it is ignored. * @param style The style to use when drawing the cue text. * @param textSizePx The text size to use when drawing the cue text, in pixels. * @param bottomPaddingFraction The bottom padding fraction to apply when {@link Cue#line} is @@ -143,24 +149,37 @@ import com.google.android.exoplayer2.util.Util; * @param cueBoxRight The right position of the enclosing cue box. * @param cueBoxBottom The bottom position of the enclosing cue box. */ - public void draw(Cue cue, boolean applyEmbeddedStyles, CaptionStyleCompat style, float textSizePx, - float bottomPaddingFraction, Canvas canvas, int cueBoxLeft, int cueBoxTop, int cueBoxRight, - int cueBoxBottom) { + public void draw(Cue cue, boolean applyEmbeddedStyles, boolean applyEmbeddedFontSizes, + CaptionStyleCompat style, float textSizePx, float bottomPaddingFraction, Canvas canvas, + int cueBoxLeft, int cueBoxTop, int cueBoxRight, int cueBoxBottom) { boolean isTextCue = cue.bitmap == null; CharSequence cueText = null; Bitmap cueBitmap = null; int windowColor = Color.BLACK; if (isTextCue) { - cueText = cue.text; - if (TextUtils.isEmpty(cueText)) { + if (TextUtils.isEmpty(cue.text)) { // Nothing to draw. return; } - windowColor = cue.windowColorSet ? cue.windowColor : style.windowColor; - if (!applyEmbeddedStyles) { - // Strip out any embedded styling. - cueText = cueText.toString(); - windowColor = style.windowColor; + windowColor = (cue.windowColorSet && applyEmbeddedStyles) + ? cue.windowColor : style.windowColor; + // Remove embedded styling or font size if requested. + if (applyEmbeddedFontSizes && applyEmbeddedStyles) { + cueText = cue.text; + } else if (!applyEmbeddedStyles) { + cueText = cue.text.toString(); // Equivalent to erasing all spans. + } else { + SpannableStringBuilder newCueText = new SpannableStringBuilder(cue.text); + int cueLength = newCueText.length(); + AbsoluteSizeSpan[] absSpans = newCueText.getSpans(0, cueLength, AbsoluteSizeSpan.class); + RelativeSizeSpan[] relSpans = newCueText.getSpans(0, cueLength, RelativeSizeSpan.class); + for (AbsoluteSizeSpan absSpan : absSpans) { + newCueText.removeSpan(absSpan); + } + for (RelativeSizeSpan relSpan : relSpans) { + newCueText.removeSpan(relSpan); + } + cueText = newCueText; } } else { cueBitmap = cue.bitmap; @@ -176,6 +195,7 @@ import com.google.android.exoplayer2.util.Util; && this.cueSize == cue.size && this.cueBitmapHeight == cue.bitmapHeight && this.applyEmbeddedStyles == applyEmbeddedStyles + && this.applyEmbeddedFontSizes == applyEmbeddedFontSizes && this.foregroundColor == style.foregroundColor && this.backgroundColor == style.backgroundColor && this.windowColor == windowColor @@ -204,6 +224,7 @@ import com.google.android.exoplayer2.util.Util; this.cueSize = cue.size; this.cueBitmapHeight = cue.bitmapHeight; this.applyEmbeddedStyles = applyEmbeddedStyles; + this.applyEmbeddedFontSizes = applyEmbeddedFontSizes; this.foregroundColor = style.foregroundColor; this.backgroundColor = style.backgroundColor; this.windowColor = windowColor; diff --git a/library/ui/src/main/java/com/google/android/exoplayer2/ui/SubtitleView.java b/library/ui/src/main/java/com/google/android/exoplayer2/ui/SubtitleView.java index 49516ab6f4..3bcfcc3ef3 100644 --- a/library/ui/src/main/java/com/google/android/exoplayer2/ui/SubtitleView.java +++ b/library/ui/src/main/java/com/google/android/exoplayer2/ui/SubtitleView.java @@ -60,6 +60,7 @@ public final class SubtitleView extends View implements TextRenderer.Output { private int textSizeType; private float textSize; private boolean applyEmbeddedStyles; + private boolean applyEmbeddedFontSizes; private CaptionStyleCompat style; private float bottomPaddingFraction; @@ -73,6 +74,7 @@ public final class SubtitleView extends View implements TextRenderer.Output { textSizeType = FRACTIONAL; textSize = DEFAULT_TEXT_SIZE_FRACTION; applyEmbeddedStyles = true; + applyEmbeddedFontSizes = true; style = CaptionStyleCompat.DEFAULT; bottomPaddingFraction = DEFAULT_BOTTOM_PADDING_FRACTION; } @@ -166,14 +168,32 @@ public final class SubtitleView extends View implements TextRenderer.Output { /** * Sets whether styling embedded within the cues should be applied. Enabled by default. + * Overrides any setting made with {@link SubtitleView#setApplyEmbeddedFontSizes}. * * @param applyEmbeddedStyles Whether styling embedded within the cues should be applied. */ public void setApplyEmbeddedStyles(boolean applyEmbeddedStyles) { - if (this.applyEmbeddedStyles == applyEmbeddedStyles) { + if (this.applyEmbeddedStyles == applyEmbeddedStyles + && this.applyEmbeddedFontSizes == applyEmbeddedStyles) { return; } this.applyEmbeddedStyles = applyEmbeddedStyles; + this.applyEmbeddedFontSizes = applyEmbeddedStyles; + // Invalidate to trigger drawing. + invalidate(); + } + + /** + * Sets whether font sizes embedded within the cues should be applied. Enabled by default. + * Only takes effect if {@link SubtitleView#setApplyEmbeddedStyles} is set to true. + * + * @param applyEmbeddedFontSizes Whether font sizes embedded within the cues should be applied. + */ + public void setApplyEmbeddedFontSizes(boolean applyEmbeddedFontSizes) { + if (this.applyEmbeddedFontSizes == applyEmbeddedFontSizes) { + return; + } + this.applyEmbeddedFontSizes = applyEmbeddedFontSizes; // Invalidate to trigger drawing. invalidate(); } @@ -243,8 +263,8 @@ public final class SubtitleView extends View implements TextRenderer.Output { } for (int i = 0; i < cueCount; i++) { - painters.get(i).draw(cues.get(i), applyEmbeddedStyles, style, textSizePx, - bottomPaddingFraction, canvas, left, top, right, bottom); + painters.get(i).draw(cues.get(i), applyEmbeddedStyles, applyEmbeddedFontSizes, style, + textSizePx, bottomPaddingFraction, canvas, left, top, right, bottom); } } From 4b948774767305499fcc4a95e213e02752de1149 Mon Sep 17 00:00:00 2001 From: andrewlewis Date: Fri, 7 Apr 2017 06:00:16 -0700 Subject: [PATCH 070/119] Fix DefaultTimeBar vertical positioning in the demo app ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=152492104 --- .../java/com/google/android/exoplayer2/ui/DefaultTimeBar.java | 2 +- .../com/google/android/exoplayer2/ui/PlaybackControlView.java | 2 -- library/ui/src/main/res/layout/exo_playback_control_view.xml | 2 +- 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/library/ui/src/main/java/com/google/android/exoplayer2/ui/DefaultTimeBar.java b/library/ui/src/main/java/com/google/android/exoplayer2/ui/DefaultTimeBar.java index 85e30d207d..a6042fd48b 100644 --- a/library/ui/src/main/java/com/google/android/exoplayer2/ui/DefaultTimeBar.java +++ b/library/ui/src/main/java/com/google/android/exoplayer2/ui/DefaultTimeBar.java @@ -60,7 +60,7 @@ public class DefaultTimeBar extends View implements TimeBar { private static final long STOP_SCRUBBING_TIMEOUT_MS = 1000; private static final int DEFAULT_INCREMENT_COUNT = 20; private static final int DEFAULT_BAR_HEIGHT = 4; - private static final int DEFAULT_TOUCH_TARGET_HEIGHT = 24; + private static final int DEFAULT_TOUCH_TARGET_HEIGHT = 26; private static final int DEFAULT_PLAYED_COLOR = 0x33FFFFFF; private static final int DEFAULT_BUFFERED_COLOR = 0xCCFFFFFF; private static final int DEFAULT_AD_MARKER_COLOR = 0xB2FFFF00; diff --git a/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlaybackControlView.java b/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlaybackControlView.java index 405595a5ed..a9023787b6 100644 --- a/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlaybackControlView.java +++ b/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlaybackControlView.java @@ -146,8 +146,6 @@ import java.util.Locale; */ public class PlaybackControlView extends FrameLayout { - private static final String TAG = "PlaybackControlView"; - /** * Listener to be notified about changes of the visibility of the UI control. */ diff --git a/library/ui/src/main/res/layout/exo_playback_control_view.xml b/library/ui/src/main/res/layout/exo_playback_control_view.xml index 665852936b..1d6267e7f0 100644 --- a/library/ui/src/main/res/layout/exo_playback_control_view.xml +++ b/library/ui/src/main/res/layout/exo_playback_control_view.xml @@ -69,7 +69,7 @@ android:id="@id/exo_progress" android:layout_width="0dp" android:layout_weight="1" - android:layout_height="24dp"/> + android:layout_height="26dp"/> Date: Fri, 7 Apr 2017 06:38:09 -0700 Subject: [PATCH 071/119] Fix VP9 extension surface attach/detach + make it seamless Issue: #2582 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=152494408 --- .../ext/vp9/LibvpxVideoRenderer.java | 190 +++++++++--------- 1 file changed, 93 insertions(+), 97 deletions(-) diff --git a/extensions/vp9/src/main/java/com/google/android/exoplayer2/ext/vp9/LibvpxVideoRenderer.java b/extensions/vp9/src/main/java/com/google/android/exoplayer2/ext/vp9/LibvpxVideoRenderer.java index bf9f1f9ced..627a64a989 100644 --- a/extensions/vp9/src/main/java/com/google/android/exoplayer2/ext/vp9/LibvpxVideoRenderer.java +++ b/extensions/vp9/src/main/java/com/google/android/exoplayer2/ext/vp9/LibvpxVideoRenderer.java @@ -186,57 +186,39 @@ public final class LibvpxVideoRenderer extends BaseRenderer { } // We have a format. - if (isRendererAvailable()) { - drmSession = pendingDrmSession; - ExoMediaCrypto mediaCrypto = null; - if (drmSession != null) { - int drmSessionState = drmSession.getState(); - if (drmSessionState == DrmSession.STATE_ERROR) { - throw ExoPlaybackException.createForRenderer(drmSession.getError(), getIndex()); - } else if (drmSessionState == DrmSession.STATE_OPENED - || drmSessionState == DrmSession.STATE_OPENED_WITH_KEYS) { - mediaCrypto = drmSession.getMediaCrypto(); - } else { - // The drm session isn't open yet. - return; - } + drmSession = pendingDrmSession; + ExoMediaCrypto mediaCrypto = null; + if (drmSession != null) { + int drmSessionState = drmSession.getState(); + if (drmSessionState == DrmSession.STATE_ERROR) { + throw ExoPlaybackException.createForRenderer(drmSession.getError(), getIndex()); + } else if (drmSessionState == DrmSession.STATE_OPENED + || drmSessionState == DrmSession.STATE_OPENED_WITH_KEYS) { + mediaCrypto = drmSession.getMediaCrypto(); + } else { + // The drm session isn't open yet. + return; } - try { - if (decoder == null) { - // If we don't have a decoder yet, we need to instantiate one. - long codecInitializingTimestamp = SystemClock.elapsedRealtime(); - TraceUtil.beginSection("createVpxDecoder"); - decoder = new VpxDecoder(NUM_BUFFERS, NUM_BUFFERS, INITIAL_INPUT_BUFFER_SIZE, - mediaCrypto); - decoder.setOutputMode(outputMode); - TraceUtil.endSection(); - long codecInitializedTimestamp = SystemClock.elapsedRealtime(); - eventDispatcher.decoderInitialized(decoder.getName(), codecInitializedTimestamp, - codecInitializedTimestamp - codecInitializingTimestamp); - decoderCounters.decoderInitCount++; - } - TraceUtil.beginSection("drainAndFeed"); - while (drainOutputBuffer(positionUs)) {} - while (feedInputBuffer()) {} + } + try { + if (decoder == null) { + // If we don't have a decoder yet, we need to instantiate one. + long codecInitializingTimestamp = SystemClock.elapsedRealtime(); + TraceUtil.beginSection("createVpxDecoder"); + decoder = new VpxDecoder(NUM_BUFFERS, NUM_BUFFERS, INITIAL_INPUT_BUFFER_SIZE, mediaCrypto); + decoder.setOutputMode(outputMode); TraceUtil.endSection(); - } catch (VpxDecoderException e) { - throw ExoPlaybackException.createForRenderer(e, getIndex()); - } - } else { - skipSource(positionUs); - // We need to read any format changes despite not having a codec so that drmSession can be - // updated, and so that we have the most recent format should the codec be initialized. We may - // also reach the end of the stream. Note that readSource will not read a sample into a - // flags-only buffer. - flagsOnlyBuffer.clear(); - int result = readSource(formatHolder, flagsOnlyBuffer, false); - if (result == C.RESULT_FORMAT_READ) { - onInputFormatChanged(formatHolder.format); - } else if (result == C.RESULT_BUFFER_READ) { - Assertions.checkState(flagsOnlyBuffer.isEndOfStream()); - inputStreamEnded = true; - outputStreamEnded = true; + long codecInitializedTimestamp = SystemClock.elapsedRealtime(); + eventDispatcher.decoderInitialized(decoder.getName(), codecInitializedTimestamp, + codecInitializedTimestamp - codecInitializingTimestamp); + decoderCounters.decoderInitCount++; } + TraceUtil.beginSection("drainAndFeed"); + while (drainOutputBuffer(positionUs)) {} + while (feedInputBuffer()) {} + TraceUtil.endSection(); + } catch (VpxDecoderException e) { + throw ExoPlaybackException.createForRenderer(e, getIndex()); } decoderCounters.ensureUpdated(); } @@ -271,27 +253,26 @@ public final class LibvpxVideoRenderer extends BaseRenderer { return false; } + if (outputMode == VpxDecoder.OUTPUT_MODE_NONE) { + // Skip frames in sync with playback, so we'll be at the right frame if the mode changes. + if (outputBuffer.timeUs <= positionUs) { + skipBuffer(); + return true; + } + return false; + } + // Drop the frame if we're joining and are more than 30ms late, or if we have the next frame // and that's also late. Else we'll render what we have. if ((joiningDeadlineMs != C.TIME_UNSET && outputBuffer.timeUs < positionUs - 30000) || (nextOutputBuffer != null && !nextOutputBuffer.isEndOfStream() && nextOutputBuffer.timeUs < positionUs)) { - decoderCounters.droppedOutputBufferCount++; - droppedFrames++; - consecutiveDroppedFrameCount++; - decoderCounters.maxConsecutiveDroppedOutputBufferCount = Math.max( - consecutiveDroppedFrameCount, - decoderCounters.maxConsecutiveDroppedOutputBufferCount); - if (droppedFrames == maxDroppedFramesToNotify) { - maybeNotifyDroppedFrames(); - } - outputBuffer.release(); - outputBuffer = null; + dropBuffer(); return true; } - // If we have not rendered any frame so far (either initially or immediately following a seek), - // render one frame irrespective of the state or current position. + // If we have yet to render a frame to the current output (either initially or immediately + // following a seek), render one irrespective of the state or current position. if (!renderedFirstFrame || (getState() == STATE_STARTED && outputBuffer.timeUs <= positionUs + 30000)) { renderBuffer(); @@ -300,26 +281,43 @@ public final class LibvpxVideoRenderer extends BaseRenderer { } private void renderBuffer() { - decoderCounters.renderedOutputBufferCount++; - consecutiveDroppedFrameCount = 0; - maybeNotifyVideoSizeChanged(outputBuffer.width, outputBuffer.height); - if (outputBuffer.mode == VpxDecoder.OUTPUT_MODE_RGB && surface != null) { - renderRgbFrame(outputBuffer, scaleToFit); - if (!renderedFirstFrame) { - renderedFirstFrame = true; - eventDispatcher.renderedFirstFrame(surface); - } - outputBuffer.release(); - } else if (outputBuffer.mode == VpxDecoder.OUTPUT_MODE_YUV && outputBufferRenderer != null) { - // The renderer will release the buffer. - outputBufferRenderer.setOutputBuffer(outputBuffer); - if (!renderedFirstFrame) { - renderedFirstFrame = true; - eventDispatcher.renderedFirstFrame(null); - } + int bufferMode = outputBuffer.mode; + boolean renderRgb = bufferMode == VpxDecoder.OUTPUT_MODE_RGB && surface != null; + boolean renderYuv = bufferMode == VpxDecoder.OUTPUT_MODE_YUV && outputBufferRenderer != null; + if (!renderRgb && !renderYuv) { + dropBuffer(); } else { - outputBuffer.release(); + maybeNotifyVideoSizeChanged(outputBuffer.width, outputBuffer.height); + if (renderRgb) { + renderRgbFrame(outputBuffer, scaleToFit); + outputBuffer.release(); + } else /* renderYuv */ { + outputBufferRenderer.setOutputBuffer(outputBuffer); + // The renderer will release the buffer. + } + outputBuffer = null; + consecutiveDroppedFrameCount = 0; + decoderCounters.renderedOutputBufferCount++; + maybeNotifyRenderedFirstFrame(); } + } + + private void dropBuffer() { + decoderCounters.droppedOutputBufferCount++; + droppedFrames++; + consecutiveDroppedFrameCount++; + decoderCounters.maxConsecutiveDroppedOutputBufferCount = Math.max( + consecutiveDroppedFrameCount, decoderCounters.maxConsecutiveDroppedOutputBufferCount); + if (droppedFrames == maxDroppedFramesToNotify) { + maybeNotifyDroppedFrames(); + } + outputBuffer.release(); + outputBuffer = null; + } + + private void skipBuffer() { + decoderCounters.skippedOutputBufferCount++; + outputBuffer.release(); outputBuffer = null; } @@ -420,7 +418,7 @@ public final class LibvpxVideoRenderer extends BaseRenderer { return false; } if (format != null && (isSourceReady() || outputBuffer != null) - && (renderedFirstFrame || !isRendererAvailable())) { + && (renderedFirstFrame || outputMode == VpxDecoder.OUTPUT_MODE_NONE)) { // Ready. If we were joining then we've now joined, so clear the joining deadline. joiningDeadlineMs = C.TIME_UNSET; return true; @@ -551,32 +549,23 @@ public final class LibvpxVideoRenderer extends BaseRenderer { private void setOutput(Surface surface, VpxOutputBufferRenderer outputBufferRenderer) { // At most one output may be non-null. Both may be null if the output is being cleared. Assertions.checkState(surface == null || outputBufferRenderer == null); - // Clear state so that we always call the event listener with the video size and when a frame - // is rendered, even if the output hasn't changed. - renderedFirstFrame = false; - clearReportedVideoSize(); // We only need to update the decoder if the output has changed. if (this.surface != surface || this.outputBufferRenderer != outputBufferRenderer) { this.surface = surface; this.outputBufferRenderer = outputBufferRenderer; outputMode = outputBufferRenderer != null ? VpxDecoder.OUTPUT_MODE_YUV : surface != null ? VpxDecoder.OUTPUT_MODE_RGB : VpxDecoder.OUTPUT_MODE_NONE; - updateDecoder(); - } - } - - private void updateDecoder() { - if (decoder != null) { - if (outputMode == VpxDecoder.OUTPUT_MODE_NONE) { - releaseDecoder(); - } else { + // If outputMode is OUTPUT_MODE_NONE we leave the mode of the underlying decoder unchanged in + // anticipation that a subsequent output will likely be of the same type as the one that was + // set previously. + if (decoder != null && outputMode != VpxDecoder.OUTPUT_MODE_NONE) { decoder.setOutputMode(outputMode); } } - } - - private boolean isRendererAvailable() { - return surface != null || outputBufferRenderer != null; + // Clear state so that we always call the event listener with the video size and when a frame + // is rendered, even if the output hasn't changed. + renderedFirstFrame = false; + clearReportedVideoSize(); } private void clearReportedVideoSize() { @@ -584,6 +573,13 @@ public final class LibvpxVideoRenderer extends BaseRenderer { reportedHeight = Format.NO_VALUE; } + private void maybeNotifyRenderedFirstFrame() { + if (!renderedFirstFrame) { + renderedFirstFrame = true; + eventDispatcher.renderedFirstFrame(surface); + } + } + private void maybeNotifyVideoSizeChanged(int width, int height) { if (reportedWidth != width || reportedHeight != height) { reportedWidth = width; From d8c71df255352b45c4a0354b0830f3d75ac890cc Mon Sep 17 00:00:00 2001 From: andrewlewis Date: Fri, 7 Apr 2017 07:05:27 -0700 Subject: [PATCH 072/119] On re-preparation, suppress source info refresh until ack'ed ExoPlayerImpl.prepare() replaces the timeline with an empty timeline. After this happens, MSG_SOURCE_INFO_REFRESHED could be handled on the main thread and could relate to the old source, so the player could expose a stale timeline. Count pending prepares in ExoPlayerImpl so that source info refreshes can be suppressed until preparation actually completes. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=152496255 --- .../android/exoplayer2/ExoPlayerTest.java | 108 +++++++++++++++--- .../android/exoplayer2/ExoPlayerImpl.java | 44 +++---- .../exoplayer2/ExoPlayerImplInternal.java | 2 + 3 files changed, 120 insertions(+), 34 deletions(-) diff --git a/library/core/src/androidTest/java/com/google/android/exoplayer2/ExoPlayerTest.java b/library/core/src/androidTest/java/com/google/android/exoplayer2/ExoPlayerTest.java index 434a0249df..2c10bfe6a0 100644 --- a/library/core/src/androidTest/java/com/google/android/exoplayer2/ExoPlayerTest.java +++ b/library/core/src/androidTest/java/com/google/android/exoplayer2/ExoPlayerTest.java @@ -17,6 +17,7 @@ package com.google.android.exoplayer2; import android.os.Handler; import android.os.HandlerThread; +import android.util.Pair; import com.google.android.exoplayer2.decoder.DecoderInputBuffer; import com.google.android.exoplayer2.source.MediaPeriod; import com.google.android.exoplayer2.source.MediaSource; @@ -32,6 +33,7 @@ import com.google.android.exoplayer2.util.MediaClock; import com.google.android.exoplayer2.util.MimeTypes; import java.io.IOException; import java.util.ArrayList; +import java.util.LinkedList; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; @@ -70,8 +72,7 @@ public final class ExoPlayerTest extends TestCase { assertEquals(0, renderer.formatReadCount); assertEquals(0, renderer.bufferReadCount); assertFalse(renderer.isEnded); - assertEquals(timeline, playerWrapper.timeline); - assertNull(playerWrapper.manifest); + playerWrapper.assertSourceInfosEquals(Pair.create(timeline, null)); } /** @@ -89,9 +90,8 @@ public final class ExoPlayerTest extends TestCase { assertEquals(1, renderer.formatReadCount); assertEquals(1, renderer.bufferReadCount); assertTrue(renderer.isEnded); - assertEquals(timeline, playerWrapper.timeline); - assertEquals(manifest, playerWrapper.manifest); assertEquals(new TrackGroupArray(new TrackGroup(TEST_VIDEO_FORMAT)), playerWrapper.trackGroups); + playerWrapper.assertSourceInfosEquals(Pair.create(timeline, manifest)); } /** @@ -111,8 +111,7 @@ public final class ExoPlayerTest extends TestCase { assertEquals(3, renderer.formatReadCount); assertEquals(1, renderer.bufferReadCount); assertTrue(renderer.isEnded); - assertEquals(timeline, playerWrapper.timeline); - assertNull(playerWrapper.manifest); + playerWrapper.assertSourceInfosEquals(Pair.create(timeline, null)); } /** @@ -163,8 +162,60 @@ public final class ExoPlayerTest extends TestCase { assertEquals(1, audioRenderer.positionResetCount); assertTrue(videoRenderer.isEnded); assertTrue(audioRenderer.isEnded); - assertEquals(timeline, playerWrapper.timeline); - assertNull(playerWrapper.manifest); + playerWrapper.assertSourceInfosEquals(Pair.create(timeline, null)); + } + + public void testRepreparationGivesFreshSourceInfo() throws Exception { + PlayerWrapper playerWrapper = new PlayerWrapper(); + Timeline timeline = new FakeTimeline(new TimelineWindowDefinition(false, false, 0)); + FakeRenderer renderer = new FakeRenderer(TEST_VIDEO_FORMAT); + + // Prepare the player with a source with the first manifest and a non-empty timeline + Object firstSourceManifest = new Object(); + playerWrapper.setup(new FakeMediaSource(timeline, firstSourceManifest, TEST_VIDEO_FORMAT), + renderer); + playerWrapper.blockUntilSourceInfoRefreshed(TIMEOUT_MS); + + // Prepare the player again with a source and a new manifest, which will never be exposed. + final CountDownLatch queuedSourceInfoCountDownLatch = new CountDownLatch(1); + final CountDownLatch completePreparationCountDownLatch = new CountDownLatch(1); + playerWrapper.prepare(new FakeMediaSource(timeline, new Object(), TEST_VIDEO_FORMAT) { + @Override + public void prepareSource(ExoPlayer player, boolean isTopLevelSource, Listener listener) { + super.prepareSource(player, isTopLevelSource, listener); + // We've queued a source info refresh on the playback thread's event queue. Allow the test + // thread to prepare the player with the third source, and block this thread (the playback + // thread) until the test thread's call to prepare() has returned. + queuedSourceInfoCountDownLatch.countDown(); + try { + completePreparationCountDownLatch.await(); + } catch (InterruptedException e) { + throw new IllegalStateException(e); + } + } + }); + + // Prepare the player again with a third source. + queuedSourceInfoCountDownLatch.await(); + Object thirdSourceManifest = new Object(); + playerWrapper.prepare(new FakeMediaSource(timeline, thirdSourceManifest, TEST_VIDEO_FORMAT)); + completePreparationCountDownLatch.countDown(); + + // Wait for playback to complete. + playerWrapper.blockUntilEnded(TIMEOUT_MS); + assertEquals(0, playerWrapper.positionDiscontinuityCount); + assertEquals(1, renderer.formatReadCount); + assertEquals(1, renderer.bufferReadCount); + assertTrue(renderer.isEnded); + assertEquals(new TrackGroupArray(new TrackGroup(TEST_VIDEO_FORMAT)), playerWrapper.trackGroups); + + // The first source's preparation completed with a non-empty timeline. When the player was + // re-prepared with the second source, it immediately exposed an empty timeline, but the source + // info refresh from the second source was suppressed as we re-prepared with the third source. + playerWrapper.assertSourceInfosEquals( + Pair.create(timeline, firstSourceManifest), + Pair.create(Timeline.EMPTY, null), + Pair.create(timeline, thirdSourceManifest)); } /** @@ -172,13 +223,13 @@ public final class ExoPlayerTest extends TestCase { */ private static final class PlayerWrapper implements ExoPlayer.EventListener { + private final CountDownLatch sourceInfoCountDownLatch; private final CountDownLatch endedCountDownLatch; private final HandlerThread playerThread; private final Handler handler; + private final LinkedList> sourceInfos; private ExoPlayer player; - private Timeline timeline; - private Object manifest; private TrackGroupArray trackGroups; private Exception exception; @@ -186,17 +237,19 @@ public final class ExoPlayerTest extends TestCase { private volatile int positionDiscontinuityCount; public PlayerWrapper() { + sourceInfoCountDownLatch = new CountDownLatch(1); endedCountDownLatch = new CountDownLatch(1); playerThread = new HandlerThread("ExoPlayerTest thread"); playerThread.start(); handler = new Handler(playerThread.getLooper()); + sourceInfos = new LinkedList<>(); } // Called on the test thread. public void blockUntilEnded(long timeoutMs) throws Exception { if (!endedCountDownLatch.await(timeoutMs, TimeUnit.MILLISECONDS)) { - exception = new TimeoutException("Test playback timed out."); + exception = new TimeoutException("Test playback timed out waiting for playback to end."); } release(); // Throw any pending exception (from playback, timing out or releasing). @@ -205,6 +258,12 @@ public final class ExoPlayerTest extends TestCase { } } + public void blockUntilSourceInfoRefreshed(long timeoutMs) throws Exception { + if (!sourceInfoCountDownLatch.await(timeoutMs, TimeUnit.MILLISECONDS)) { + throw new TimeoutException("Test playback timed out waiting for source info."); + } + } + public void setup(final MediaSource mediaSource, final Renderer... renderers) { handler.post(new Runnable() { @Override @@ -221,6 +280,19 @@ public final class ExoPlayerTest extends TestCase { }); } + public void prepare(final MediaSource mediaSource) { + handler.post(new Runnable() { + @Override + public void run() { + try { + player.prepare(mediaSource); + } catch (Exception e) { + handleError(e); + } + } + }); + } + public void release() throws InterruptedException { handler.post(new Runnable() { @Override @@ -246,6 +318,14 @@ public final class ExoPlayerTest extends TestCase { endedCountDownLatch.countDown(); } + @SafeVarargs + public final void assertSourceInfosEquals(Pair... sourceInfos) { + assertEquals(sourceInfos.length, this.sourceInfos.size()); + for (Pair sourceInfo : sourceInfos) { + assertEquals(sourceInfo, this.sourceInfos.remove()); + } + } + // ExoPlayer.EventListener implementation. @Override @@ -262,8 +342,8 @@ public final class ExoPlayerTest extends TestCase { @Override public void onTimelineChanged(Timeline timeline, Object manifest) { - this.timeline = timeline; - this.manifest = manifest; + sourceInfos.add(Pair.create(timeline, manifest)); + sourceInfoCountDownLatch.countDown(); } @Override @@ -352,7 +432,7 @@ public final class ExoPlayerTest extends TestCase { * Fake {@link MediaSource} that provides a given timeline (which must have one period). Creating * the period will return a {@link FakeMediaPeriod}. */ - private static final class FakeMediaSource implements MediaSource { + private static class FakeMediaSource implements MediaSource { private final Timeline timeline; private final Object manifest; 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 b9ab94a543..4131b97954 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 @@ -53,6 +53,7 @@ import java.util.concurrent.CopyOnWriteArraySet; private boolean playWhenReady; private int playbackState; private int pendingSeekAcks; + private int pendingPrepareAcks; private boolean isLoading; private Timeline timeline; private Object manifest; @@ -142,6 +143,7 @@ import java.util.concurrent.CopyOnWriteArraySet; } } } + pendingPrepareAcks++; internalPlayer.prepare(mediaSource, resetPosition); } @@ -310,18 +312,12 @@ import java.util.concurrent.CopyOnWriteArraySet; @Override public boolean isCurrentWindowDynamic() { - if (timeline.isEmpty()) { - return false; - } - return timeline.getWindow(getCurrentWindowIndex(), window).isDynamic; + return !timeline.isEmpty() && timeline.getWindow(getCurrentWindowIndex(), window).isDynamic; } @Override public boolean isCurrentWindowSeekable() { - if (timeline.isEmpty()) { - return false; - } - return timeline.getWindow(getCurrentWindowIndex(), window).isSeekable; + return !timeline.isEmpty() && timeline.getWindow(getCurrentWindowIndex(), window).isSeekable; } @Override @@ -357,6 +353,10 @@ import java.util.concurrent.CopyOnWriteArraySet; // Not private so it can be called from an inner class without going through a thunk method. /* package */ void handleEvent(Message msg) { switch (msg.what) { + case ExoPlayerImplInternal.MSG_PREPARE_ACK: { + pendingPrepareAcks--; + break; + } case ExoPlayerImplInternal.MSG_STATE_CHANGED: { playbackState = msg.arg1; for (EventListener listener : listeners) { @@ -372,13 +372,15 @@ import java.util.concurrent.CopyOnWriteArraySet; break; } case ExoPlayerImplInternal.MSG_TRACKS_CHANGED: { - TrackSelectorResult trackSelectorResult = (TrackSelectorResult) msg.obj; - tracksSelected = true; - trackGroups = trackSelectorResult.groups; - trackSelections = trackSelectorResult.selections; - trackSelector.onSelectionActivated(trackSelectorResult.info); - for (EventListener listener : listeners) { - listener.onTracksChanged(trackGroups, trackSelections); + if (pendingPrepareAcks == 0) { + TrackSelectorResult trackSelectorResult = (TrackSelectorResult) msg.obj; + tracksSelected = true; + trackGroups = trackSelectorResult.groups; + trackSelections = trackSelectorResult.selections; + trackSelector.onSelectionActivated(trackSelectorResult.info); + for (EventListener listener : listeners) { + listener.onTracksChanged(trackGroups, trackSelections); + } } break; } @@ -404,12 +406,14 @@ import java.util.concurrent.CopyOnWriteArraySet; } case ExoPlayerImplInternal.MSG_SOURCE_INFO_REFRESHED: { SourceInfo sourceInfo = (SourceInfo) msg.obj; - timeline = sourceInfo.timeline; - manifest = sourceInfo.manifest; - playbackInfo = sourceInfo.playbackInfo; pendingSeekAcks -= sourceInfo.seekAcks; - for (EventListener listener : listeners) { - listener.onTimelineChanged(timeline, manifest); + if (pendingPrepareAcks == 0) { + timeline = sourceInfo.timeline; + manifest = sourceInfo.manifest; + playbackInfo = sourceInfo.playbackInfo; + for (EventListener listener : listeners) { + listener.onTimelineChanged(timeline, manifest); + } } break; } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java index f6fa0d39ac..916f5f6657 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java @@ -90,6 +90,7 @@ import java.io.IOException; private static final String TAG = "ExoPlayerImplInternal"; // External messages + public static final int MSG_PREPARE_ACK = 0; public static final int MSG_STATE_CHANGED = 1; public static final int MSG_LOADING_CHANGED = 2; public static final int MSG_TRACKS_CHANGED = 3; @@ -383,6 +384,7 @@ import java.io.IOException; } private void prepareInternal(MediaSource mediaSource, boolean resetPosition) { + eventHandler.sendEmptyMessage(MSG_PREPARE_ACK); resetInternal(true); loadControl.onPrepared(); if (resetPosition) { From 147020f81610bb59088be5970ba44424aab92f45 Mon Sep 17 00:00:00 2001 From: andrewlewis Date: Mon, 10 Apr 2017 02:17:35 -0700 Subject: [PATCH 073/119] Hide player controls when an ad is playing Also fix an issue where ad timelines with unknown ad period durations would not be shown in multi-window mode. (The time bar doesn't use the duration of ad periods, but shows a fix-size indicator instead.) ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=152666055 --- .../google/android/exoplayer2/ui/PlaybackControlView.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlaybackControlView.java b/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlaybackControlView.java index a9023787b6..ab5a8d4c97 100644 --- a/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlaybackControlView.java +++ b/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlaybackControlView.java @@ -508,6 +508,10 @@ public class PlaybackControlView extends FrameLayout { isSeekable = window.isSeekable; enablePrevious = windowIndex > 0 || isSeekable || !window.isDynamic; enableNext = (windowIndex < timeline.getWindowCount() - 1) || window.isDynamic; + if (timeline.getPeriod(player.getCurrentPeriodIndex(), period).isAd) { + // Always hide player controls during ads. + hide(); + } } setButtonEnabled(enablePrevious, previousButton); setButtonEnabled(enableNext, nextButton); @@ -800,7 +804,8 @@ public class PlaybackControlView extends FrameLayout { } int periodCount = timeline.getPeriodCount(); for (int i = 0; i < periodCount; i++) { - if (timeline.getPeriod(i, period).durationUs == C.TIME_UNSET) { + timeline.getPeriod(i, period); + if (!period.isAd && period.durationUs == C.TIME_UNSET) { return false; } } From 8da2e2c8d535db53a527fc8d6e16f6c51b684aee Mon Sep 17 00:00:00 2001 From: aquilescanta Date: Mon, 10 Apr 2017 04:13:12 -0700 Subject: [PATCH 074/119] Track format changes in (E-)AC-3 syncframes This allows propagating format changes to the track output. Issue:#2552 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=152672983 --- .../android/exoplayer2/audio/Ac3Util.java | 163 ++++++++++-------- .../exoplayer2/extractor/ts/Ac3Reader.java | 27 ++- 2 files changed, 100 insertions(+), 90 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/audio/Ac3Util.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/Ac3Util.java index adb3d5999b..4b64ffb030 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/audio/Ac3Util.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/audio/Ac3Util.java @@ -28,6 +28,44 @@ import java.nio.ByteBuffer; */ public final class Ac3Util { + /** + * Holds sample format information as presented by a syncframe header. + */ + public static final class Ac3SyncFrameInfo { + + /** + * The sample mime type of the bitstream. One of {@link MimeTypes#AUDIO_AC3} and + * {@link MimeTypes#AUDIO_E_AC3}. + */ + public final String mimeType; + /** + * The audio sampling rate in Hz. + */ + public final int sampleRate; + /** + * The number of audio channels + */ + public final int channelCount; + /** + * The size of the frame. + */ + public final int frameSize; + /** + * Number of audio samples in the frame. + */ + public final int sampleCount; + + private Ac3SyncFrameInfo(String mimeType, int channelCount, int sampleRate, int frameSize, + int sampleCount) { + this.mimeType = mimeType; + this.channelCount = channelCount; + this.sampleRate = sampleRate; + this.frameSize = frameSize; + this.sampleCount = sampleCount; + } + + } + /** * The number of new samples per (E-)AC-3 audio block. */ @@ -114,62 +152,61 @@ public final class Ac3Util { } /** - * Returns the AC-3 format given {@code data} containing a syncframe. The reading position of - * {@code data} will be modified. + * Returns (E-)AC-3 format information given {@code data} containing a syncframe. The reading + * position of {@code data} will be modified. * * @param data The data to parse, positioned at the start of the syncframe. - * @param trackId The track identifier to set on the format, or null. - * @param language The language to set on the format. - * @param drmInitData {@link DrmInitData} to be included in the format. - * @return The AC-3 format parsed from data in the header. + * @return The (E-)AC-3 format data parsed from the header. */ - public static Format parseAc3SyncframeFormat(ParsableBitArray data, String trackId, - String language, DrmInitData drmInitData) { - data.skipBits(16 + 16); // syncword, crc1 - int fscod = data.readBits(2); - data.skipBits(6 + 5 + 3); // frmsizecod, bsid, bsmod - int acmod = data.readBits(3); - if ((acmod & 0x01) != 0 && acmod != 1) { - data.skipBits(2); // cmixlev - } - if ((acmod & 0x04) != 0) { - data.skipBits(2); // surmixlev - } - if (acmod == 2) { - data.skipBits(2); // dsurmod - } - boolean lfeon = data.readBit(); - return Format.createAudioSampleFormat(trackId, MimeTypes.AUDIO_AC3, null, Format.NO_VALUE, - Format.NO_VALUE, CHANNEL_COUNT_BY_ACMOD[acmod] + (lfeon ? 1 : 0), - SAMPLE_RATE_BY_FSCOD[fscod], null, drmInitData, 0, language); - } - - /** - * Returns the E-AC-3 format given {@code data} containing a syncframe. The reading position of - * {@code data} will be modified. - * - * @param data The data to parse, positioned at the start of the syncframe. - * @param trackId The track identifier to set on the format, or null. - * @param language The language to set on the format. - * @param drmInitData {@link DrmInitData} to be included in the format. - * @return The E-AC-3 format parsed from data in the header. - */ - public static Format parseEac3SyncframeFormat(ParsableBitArray data, String trackId, - String language, DrmInitData drmInitData) { - data.skipBits(16 + 2 + 3 + 11); // syncword, strmtype, substreamid, frmsiz + public static Ac3SyncFrameInfo parseAc3SyncframeInfo(ParsableBitArray data) { + int initialPosition = data.getPosition(); + data.skipBits(40); + boolean isEac3 = data.readBits(5) == 16; + data.setPosition(initialPosition); + String mimeType; int sampleRate; - int fscod = data.readBits(2); - if (fscod == 3) { - sampleRate = SAMPLE_RATE_BY_FSCOD2[data.readBits(2)]; - } else { - data.skipBits(2); // numblkscod + int acmod; + int frameSize; + int sampleCount; + if (isEac3) { + mimeType = MimeTypes.AUDIO_E_AC3; + data.skipBits(16 + 2 + 3); // syncword, strmtype, substreamid + frameSize = (data.readBits(11) + 1) * 2; + int fscod = data.readBits(2); + int audioBlocks; + if (fscod == 3) { + sampleRate = SAMPLE_RATE_BY_FSCOD2[data.readBits(2)]; + audioBlocks = 6; + } else { + int numblkscod = data.readBits(2); + audioBlocks = BLOCKS_PER_SYNCFRAME_BY_NUMBLKSCOD[numblkscod]; + sampleRate = SAMPLE_RATE_BY_FSCOD[fscod]; + } + sampleCount = AUDIO_SAMPLES_PER_AUDIO_BLOCK * audioBlocks; + acmod = data.readBits(3); + } else /* is AC-3 */ { + mimeType = MimeTypes.AUDIO_AC3; + data.skipBits(16 + 16); // syncword, crc1 + int fscod = data.readBits(2); + int frmsizecod = data.readBits(6); + frameSize = getAc3SyncframeSize(fscod, frmsizecod); + data.skipBits(5 + 3); // bsid, bsmod + acmod = data.readBits(3); + if ((acmod & 0x01) != 0 && acmod != 1) { + data.skipBits(2); // cmixlev + } + if ((acmod & 0x04) != 0) { + data.skipBits(2); // surmixlev + } + if (acmod == 2) { + data.skipBits(2); // dsurmod + } sampleRate = SAMPLE_RATE_BY_FSCOD[fscod]; + sampleCount = AC3_SYNCFRAME_AUDIO_SAMPLE_COUNT; } - int acmod = data.readBits(3); boolean lfeon = data.readBit(); - return Format.createAudioSampleFormat(trackId, MimeTypes.AUDIO_E_AC3, null, Format.NO_VALUE, - Format.NO_VALUE, CHANNEL_COUNT_BY_ACMOD[acmod] + (lfeon ? 1 : 0), sampleRate, null, - drmInitData, 0, language); + int channelCount = CHANNEL_COUNT_BY_ACMOD[acmod] + (lfeon ? 1 : 0); + return new Ac3SyncFrameInfo(mimeType, channelCount, sampleRate, frameSize, sampleCount); } /** @@ -187,16 +224,6 @@ public final class Ac3Util { return getAc3SyncframeSize(fscod, frmsizecod); } - /** - * Returns the size in bytes of the given E-AC-3 syncframe. - * - * @param data The syncframe to parse. - * @return The syncframe size in bytes. - */ - public static int parseEAc3SyncframeSize(byte[] data) { - return 2 * (((data[2] & 0x07) << 8) + (data[3] & 0xFF) + 1); // frmsiz - } - /** * Returns the number of audio samples in an AC-3 syncframe. */ @@ -205,22 +232,10 @@ public final class Ac3Util { } /** - * Returns the number of audio samples represented by the given E-AC-3 syncframe. + * Reads the number of audio samples represented by the given E-AC-3 syncframe. The buffer's + * position is not modified. * - * @param data The syncframe to parse. - * @return The number of audio samples represented by the syncframe. - */ - public static int parseEAc3SyncframeAudioSampleCount(byte[] data) { - // See ETSI TS 102 366 subsection E.1.2.2. - return AUDIO_SAMPLES_PER_AUDIO_BLOCK * (((data[4] & 0xC0) >> 6) == 0x03 ? 6 // fscod - : BLOCKS_PER_SYNCFRAME_BY_NUMBLKSCOD[(data[4] & 0x30) >> 4]); - } - - /** - * Like {@link #parseEAc3SyncframeAudioSampleCount(byte[])} but reads from a {@link ByteBuffer}. - * The buffer's position is not modified. - * - * @param buffer The {@link ByteBuffer} from which to read. + * @param buffer The {@link ByteBuffer} from which to read the syncframe. * @return The number of audio samples represented by the syncframe. */ public static int parseEAc3SyncframeAudioSampleCount(ByteBuffer buffer) { 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 248161f28f..58d5310bd7 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 @@ -52,7 +52,6 @@ public final class Ac3Reader implements ElementaryStreamReader { private long sampleDurationUs; private Format format; private int sampleSize; - private boolean isEac3; // Used when reading the samples. private long timeUs; @@ -177,26 +176,22 @@ public final class Ac3Reader implements ElementaryStreamReader { /** * Parses the sample header. */ + @SuppressWarnings("ReferenceEquality") private void parseHeader() { - if (format == null) { - // We read ahead to distinguish between AC-3 and E-AC-3. - headerScratchBits.skipBits(40); - isEac3 = headerScratchBits.readBits(5) == 16; - headerScratchBits.setPosition(headerScratchBits.getPosition() - 45); - format = isEac3 - ? Ac3Util.parseEac3SyncframeFormat(headerScratchBits, trackFormatId, language , null) - : Ac3Util.parseAc3SyncframeFormat(headerScratchBits, trackFormatId, language, null); + headerScratchBits.setPosition(0); + Ac3Util.Ac3SyncFrameInfo frameInfo = Ac3Util.parseAc3SyncframeInfo(headerScratchBits); + if (format == null || frameInfo.channelCount != format.channelCount + || frameInfo.sampleRate != format.sampleRate + || frameInfo.mimeType != format.sampleMimeType) { + format = Format.createAudioSampleFormat(trackFormatId, frameInfo.mimeType, null, + Format.NO_VALUE, Format.NO_VALUE, frameInfo.channelCount, frameInfo.sampleRate, null, + null, 0, language); output.format(format); } - sampleSize = isEac3 ? Ac3Util.parseEAc3SyncframeSize(headerScratchBits.data) - : Ac3Util.parseAc3SyncframeSize(headerScratchBits.data); - int audioSamplesPerSyncframe = isEac3 - ? Ac3Util.parseEAc3SyncframeAudioSampleCount(headerScratchBits.data) - : Ac3Util.getAc3SyncframeAudioSampleCount(); + sampleSize = frameInfo.frameSize; // In this class a sample is an access unit (syncframe in AC-3), but the MediaFormat sample rate // specifies the number of PCM audio samples per second. - sampleDurationUs = - (int) (C.MICROS_PER_SECOND * audioSamplesPerSyncframe / format.sampleRate); + sampleDurationUs = C.MICROS_PER_SECOND * frameInfo.sampleCount / format.sampleRate; } } From c185963355d619b33579748f4bfed65b7dbeec87 Mon Sep 17 00:00:00 2001 From: aquilescanta Date: Mon, 10 Apr 2017 05:05:22 -0700 Subject: [PATCH 075/119] Add IntDef to reader internal state and default branch Adding the default branch prevents linting warnings. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=152675789 --- .../android/exoplayer2/extractor/ts/Ac3Reader.java | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) 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 58d5310bd7..6a1c566faf 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 @@ -15,6 +15,7 @@ */ package com.google.android.exoplayer2.extractor.ts; +import android.support.annotation.IntDef; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.audio.Ac3Util; @@ -23,12 +24,17 @@ import com.google.android.exoplayer2.extractor.TrackOutput; import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.TrackIdGenerator; import com.google.android.exoplayer2.util.ParsableBitArray; import com.google.android.exoplayer2.util.ParsableByteArray; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; /** * Parses a continuous (E-)AC-3 byte stream and extracts individual samples. */ public final class Ac3Reader implements ElementaryStreamReader { + @Retention(RetentionPolicy.SOURCE) + @IntDef({STATE_FINDING_SYNC, STATE_READING_HEADER, STATE_READING_SAMPLE}) + private @interface State {} private static final int STATE_FINDING_SYNC = 0; private static final int STATE_READING_HEADER = 1; private static final int STATE_READING_SAMPLE = 2; @@ -42,7 +48,7 @@ public final class Ac3Reader implements ElementaryStreamReader { private String trackFormatId; private TrackOutput output; - private int state; + @State private int state; private int bytesRead; // Used to find the header. @@ -124,6 +130,8 @@ public final class Ac3Reader implements ElementaryStreamReader { state = STATE_FINDING_SYNC; } break; + default: + break; } } } From 0aee235e0aa2a9733ec22b77085961751bdc3b30 Mon Sep 17 00:00:00 2001 From: olly Date: Mon, 10 Apr 2017 09:41:54 -0700 Subject: [PATCH 076/119] Make ID3 parser more robust (again) I've also removed unnecessary "empty" cases, since to add them everywhere would bloat the code quite a lot. Note that new String(new byte[0], 0, 0, encoding) is valid and will produce and empty string. Issue: #2663 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=152697288 --- .../metadata/id3/Id3DecoderTest.java | 186 +++++++++++++++--- .../exoplayer2/metadata/id3/Id3Decoder.java | 61 +++--- 2 files changed, 202 insertions(+), 45 deletions(-) diff --git a/library/core/src/androidTest/java/com/google/android/exoplayer2/metadata/id3/Id3DecoderTest.java b/library/core/src/androidTest/java/com/google/android/exoplayer2/metadata/id3/Id3DecoderTest.java index e271108ce4..6b39ed1645 100644 --- a/library/core/src/androidTest/java/com/google/android/exoplayer2/metadata/id3/Id3DecoderTest.java +++ b/library/core/src/androidTest/java/com/google/android/exoplayer2/metadata/id3/Id3DecoderTest.java @@ -18,6 +18,7 @@ package com.google.android.exoplayer2.metadata.id3; import android.test.MoreAsserts; import com.google.android.exoplayer2.metadata.Metadata; import com.google.android.exoplayer2.metadata.MetadataDecoderException; +import com.google.android.exoplayer2.util.Assertions; import junit.framework.TestCase; /** @@ -25,10 +26,13 @@ import junit.framework.TestCase; */ public final class Id3DecoderTest extends TestCase { + private static final byte[] TAG_HEADER = new byte[] {73, 68, 51, 4, 0, 0, 0, 0, 0, 0}; + private static final int FRAME_HEADER_LENGTH = 10; + private static final int ID3_TEXT_ENCODING_UTF_8 = 3; + public void testDecodeTxxxFrame() throws MetadataDecoderException { - byte[] rawId3 = new byte[] {73, 68, 51, 4, 0, 0, 0, 0, 0, 41, 84, 88, 88, 88, 0, 0, 0, 31, 0, 0, - 3, 0, 109, 100, 105, 97, 108, 111, 103, 95, 86, 73, 78, 68, 73, 67, 79, 49, 53, 50, 55, 54, - 54, 52, 95, 115, 116, 97, 114, 116, 0}; + byte[] rawId3 = buildSingleFrameTag("TXXX", new byte[] {3, 0, 109, 100, 105, 97, 108, 111, 103, + 95, 86, 73, 78, 68, 73, 67, 79, 49, 53, 50, 55, 54, 54, 52, 95, 115, 116, 97, 114, 116, 0}); Id3Decoder decoder = new Id3Decoder(); Metadata metadata = decoder.decode(rawId3, rawId3.length); assertEquals(1, metadata.length()); @@ -36,12 +40,118 @@ public final class Id3DecoderTest extends TestCase { assertEquals("TXXX", textInformationFrame.id); assertEquals("", textInformationFrame.description); assertEquals("mdialog_VINDICO1527664_start", textInformationFrame.value); + + // Test empty. + rawId3 = buildSingleFrameTag("TXXX", new byte[0]); + metadata = decoder.decode(rawId3, rawId3.length); + assertEquals(0, metadata.length()); + + // Test encoding byte only. + rawId3 = buildSingleFrameTag("TXXX", new byte[] {ID3_TEXT_ENCODING_UTF_8}); + metadata = decoder.decode(rawId3, rawId3.length); + assertEquals(1, metadata.length()); + textInformationFrame = (TextInformationFrame) metadata.get(0); + assertEquals("TXXX", textInformationFrame.id); + assertEquals("", textInformationFrame.description); + assertEquals("", textInformationFrame.value); + } + + public void testDecodeTextInformationFrame() throws MetadataDecoderException { + byte[] rawId3 = buildSingleFrameTag("TIT2", new byte[] {3, 72, 101, 108, 108, 111, 32, 87, 111, + 114, 108, 100, 0}); + Id3Decoder decoder = new Id3Decoder(); + Metadata metadata = decoder.decode(rawId3, rawId3.length); + assertEquals(1, metadata.length()); + TextInformationFrame textInformationFrame = (TextInformationFrame) metadata.get(0); + assertEquals("TIT2", textInformationFrame.id); + assertNull(textInformationFrame.description); + assertEquals("Hello World", textInformationFrame.value); + + // Test empty. + rawId3 = buildSingleFrameTag("TIT2", new byte[0]); + metadata = decoder.decode(rawId3, rawId3.length); + assertEquals(0, metadata.length()); + + // Test encoding byte only. + rawId3 = buildSingleFrameTag("TIT2", new byte[] {ID3_TEXT_ENCODING_UTF_8}); + metadata = decoder.decode(rawId3, rawId3.length); + assertEquals(1, metadata.length()); + textInformationFrame = (TextInformationFrame) metadata.get(0); + assertEquals("TIT2", textInformationFrame.id); + assertEquals(null, textInformationFrame.description); + assertEquals("", textInformationFrame.value); + } + + public void testDecodeWxxxFrame() throws MetadataDecoderException { + byte[] rawId3 = buildSingleFrameTag("WXXX", new byte[] {ID3_TEXT_ENCODING_UTF_8, 116, 101, 115, + 116, 0, 104, 116, 116, 112, 115, 58, 47, 47, 116, 101, 115, 116, 46, 99, 111, 109, 47, 97, + 98, 99, 63, 100, 101, 102}); + Id3Decoder decoder = new Id3Decoder(); + Metadata metadata = decoder.decode(rawId3, rawId3.length); + assertEquals(1, metadata.length()); + UrlLinkFrame urlLinkFrame = (UrlLinkFrame) metadata.get(0); + assertEquals("WXXX", urlLinkFrame.id); + assertEquals("test", urlLinkFrame.description); + assertEquals("https://test.com/abc?def", urlLinkFrame.url); + + // Test empty. + rawId3 = buildSingleFrameTag("WXXX", new byte[0]); + metadata = decoder.decode(rawId3, rawId3.length); + assertEquals(0, metadata.length()); + + // Test encoding byte only. + rawId3 = buildSingleFrameTag("WXXX", new byte[] {ID3_TEXT_ENCODING_UTF_8}); + metadata = decoder.decode(rawId3, rawId3.length); + assertEquals(1, metadata.length()); + urlLinkFrame = (UrlLinkFrame) metadata.get(0); + assertEquals("WXXX", urlLinkFrame.id); + assertEquals("", urlLinkFrame.description); + assertEquals("", urlLinkFrame.url); + } + + public void testDecodeUrlLinkFrame() throws MetadataDecoderException { + byte[] rawId3 = buildSingleFrameTag("WCOM", new byte[] {104, 116, 116, 112, 115, 58, 47, 47, + 116, 101, 115, 116, 46, 99, 111, 109, 47, 97, 98, 99, 63, 100, 101, 102}); + Id3Decoder decoder = new Id3Decoder(); + Metadata metadata = decoder.decode(rawId3, rawId3.length); + assertEquals(1, metadata.length()); + UrlLinkFrame urlLinkFrame = (UrlLinkFrame) metadata.get(0); + assertEquals("WCOM", urlLinkFrame.id); + assertEquals(null, urlLinkFrame.description); + assertEquals("https://test.com/abc?def", urlLinkFrame.url); + + // Test empty. + rawId3 = buildSingleFrameTag("WCOM", new byte[0]); + metadata = decoder.decode(rawId3, rawId3.length); + assertEquals(1, metadata.length()); + urlLinkFrame = (UrlLinkFrame) metadata.get(0); + assertEquals("WCOM", urlLinkFrame.id); + assertEquals(null, urlLinkFrame.description); + assertEquals("", urlLinkFrame.url); + } + + public void testDecodePrivFrame() throws MetadataDecoderException { + byte[] rawId3 = buildSingleFrameTag("PRIV", new byte[] {116, 101, 115, 116, 0, 1, 2, 3, 4}); + Id3Decoder decoder = new Id3Decoder(); + Metadata metadata = decoder.decode(rawId3, rawId3.length); + assertEquals(1, metadata.length()); + PrivFrame privFrame = (PrivFrame) metadata.get(0); + assertEquals("test", privFrame.owner); + MoreAsserts.assertEquals(new byte[] {1, 2, 3, 4}, privFrame.privateData); + + // Test empty. + rawId3 = buildSingleFrameTag("PRIV", new byte[0]); + metadata = decoder.decode(rawId3, rawId3.length); + assertEquals(1, metadata.length()); + privFrame = (PrivFrame) metadata.get(0); + assertEquals("", privFrame.owner); + MoreAsserts.assertEquals(new byte[0], privFrame.privateData); } public void testDecodeApicFrame() throws MetadataDecoderException { - byte[] rawId3 = new byte[] {73, 68, 51, 4, 0, 0, 0, 0, 0, 45, 65, 80, 73, 67, 0, 0, 0, 35, 0, 0, - 3, 105, 109, 97, 103, 101, 47, 106, 112, 101, 103, 0, 16, 72, 101, 108, 108, 111, 32, 87, - 111, 114, 108, 100, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0}; + byte[] rawId3 = buildSingleFrameTag("APIC", new byte[] {3, 105, 109, 97, 103, 101, 47, 106, 112, + 101, 103, 0, 16, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 0, 1, 2, 3, 4, 5, 6, 7, + 8, 9, 0}); Id3Decoder decoder = new Id3Decoder(); Metadata metadata = decoder.decode(rawId3, rawId3.length); assertEquals(1, metadata.length()); @@ -53,27 +163,59 @@ public final class Id3DecoderTest extends TestCase { MoreAsserts.assertEquals(new byte[] {1, 2, 3, 4, 5, 6, 7, 8, 9, 0}, apicFrame.pictureData); } - public void testDecodeTextInformationFrame() throws MetadataDecoderException { - byte[] rawId3 = new byte[] {73, 68, 51, 4, 0, 0, 0, 0, 0, 23, 84, 73, 84, 50, 0, 0, 0, 13, 0, 0, - 3, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 0}; + public void testDecodeCommentFrame() throws MetadataDecoderException { + byte[] rawId3 = buildSingleFrameTag("COMM", new byte[] {ID3_TEXT_ENCODING_UTF_8, 101, 110, 103, + 100, 101, 115, 99, 114, 105, 112, 116, 105, 111, 110, 0, 116, 101, 120, 116, 0}); Id3Decoder decoder = new Id3Decoder(); Metadata metadata = decoder.decode(rawId3, rawId3.length); assertEquals(1, metadata.length()); - TextInformationFrame textInformationFrame = (TextInformationFrame) metadata.get(0); - assertEquals("TIT2", textInformationFrame.id); - assertNull(textInformationFrame.description); - assertEquals("Hello World", textInformationFrame.value); + CommentFrame commentFrame = (CommentFrame) metadata.get(0); + assertEquals("eng", commentFrame.language); + assertEquals("description", commentFrame.description); + assertEquals("text", commentFrame.text); + + // Test empty. + rawId3 = buildSingleFrameTag("COMM", new byte[0]); + metadata = decoder.decode(rawId3, rawId3.length); + assertEquals(0, metadata.length()); + + // Test language only. + rawId3 = buildSingleFrameTag("COMM", new byte[] {ID3_TEXT_ENCODING_UTF_8, 101, 110, 103}); + metadata = decoder.decode(rawId3, rawId3.length); + assertEquals(1, metadata.length()); + commentFrame = (CommentFrame) metadata.get(0); + assertEquals("eng", commentFrame.language); + assertEquals("", commentFrame.description); + assertEquals("", commentFrame.text); } - public void testDecodePrivFrame() throws MetadataDecoderException { - byte[] rawId3 = new byte[] {73, 68, 51, 4, 0, 0, 0, 0, 0, 19, 80, 82, 73, 86, 0, 0, 0, 9, 0, 0, - 116, 101, 115, 116, 0, 1, 2, 3, 4}; - Id3Decoder decoder = new Id3Decoder(); - Metadata metadata = decoder.decode(rawId3, rawId3.length); - assertEquals(1, metadata.length()); - PrivFrame privFrame = (PrivFrame) metadata.get(0); - assertEquals("test", privFrame.owner); - MoreAsserts.assertEquals(new byte[] {1, 2, 3, 4}, privFrame.privateData); + private static byte[] buildSingleFrameTag(String frameId, byte[] frameData) { + byte[] frameIdBytes = frameId.getBytes(); + Assertions.checkState(frameIdBytes.length == 4); + + byte[] tagData = new byte[TAG_HEADER.length + FRAME_HEADER_LENGTH + frameData.length]; + System.arraycopy(TAG_HEADER, 0, tagData, 0, TAG_HEADER.length); + // Fill in the size part of the tag header. + int offset = TAG_HEADER.length - 4; + int tagSize = frameData.length + FRAME_HEADER_LENGTH; + tagData[offset++] = (byte) ((tagSize >> 21) & 0x7F); + tagData[offset++] = (byte) ((tagSize >> 14) & 0x7F); + tagData[offset++] = (byte) ((tagSize >> 7) & 0x7F); + tagData[offset++] = (byte) (tagSize & 0x7F); + // Fill in the frame header. + tagData[offset++] = frameIdBytes[0]; + tagData[offset++] = frameIdBytes[1]; + tagData[offset++] = frameIdBytes[2]; + tagData[offset++] = frameIdBytes[3]; + tagData[offset++] = (byte) ((frameData.length >> 24) & 0xFF); + tagData[offset++] = (byte) ((frameData.length >> 16) & 0xFF); + tagData[offset++] = (byte) ((frameData.length >> 8) & 0xFF); + tagData[offset++] = (byte) (frameData.length & 0xFF); + offset += 2; // Frame flags set to 0 + // Fill in the frame data. + System.arraycopy(frameData, 0, tagData, offset, frameData.length); + + return tagData; } } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/metadata/id3/Id3Decoder.java b/library/core/src/main/java/com/google/android/exoplayer2/metadata/id3/Id3Decoder.java index 4faebd510a..df3353fb18 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/metadata/id3/Id3Decoder.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/metadata/id3/Id3Decoder.java @@ -346,17 +346,13 @@ public final class Id3Decoder implements MetadataDecoder { && (majorVersion == 2 || frameId3 == 'X')) { frame = decodeTxxxFrame(id3Data, frameSize); } else if (frameId0 == 'T') { - String id = majorVersion == 2 - ? String.format(Locale.US, "%c%c%c", frameId0, frameId1, frameId2) - : String.format(Locale.US, "%c%c%c%c", frameId0, frameId1, frameId2, frameId3); + String id = getFrameId(majorVersion, frameId0, frameId1, frameId2, frameId3); frame = decodeTextInformationFrame(id3Data, frameSize, id); } else if (frameId0 == 'W' && frameId1 == 'X' && frameId2 == 'X' && (majorVersion == 2 || frameId3 == 'X')) { frame = decodeWxxxFrame(id3Data, frameSize); } else if (frameId0 == 'W') { - String id = majorVersion == 2 - ? String.format(Locale.US, "%c%c%c", frameId0, frameId1, frameId2) - : String.format(Locale.US, "%c%c%c%c", frameId0, frameId1, frameId2, frameId3); + String id = getFrameId(majorVersion, frameId0, frameId1, frameId2, frameId3); frame = decodeUrlLinkFrame(id3Data, frameSize, id); } else if (frameId0 == 'P' && frameId1 == 'R' && frameId2 == 'I' && frameId3 == 'V') { frame = decodePrivFrame(id3Data, frameSize); @@ -376,11 +372,14 @@ public final class Id3Decoder implements MetadataDecoder { frame = decodeChapterTOCFrame(id3Data, frameSize, majorVersion, unsignedIntFrameSizeHack, frameHeaderSize, framePredicate); } else { - String id = majorVersion == 2 - ? String.format(Locale.US, "%c%c%c", frameId0, frameId1, frameId2) - : String.format(Locale.US, "%c%c%c%c", frameId0, frameId1, frameId2, frameId3); + String id = getFrameId(majorVersion, frameId0, frameId1, frameId2, frameId3); frame = decodeBinaryFrame(id3Data, frameSize, id); } + if (frame == null) { + Log.w(TAG, "Failed to decode frame: id=" + + getFrameId(majorVersion, frameId0, frameId1, frameId2, frameId3) + ", frameSize=" + + frameSize); + } return frame; } catch (UnsupportedEncodingException e) { Log.w(TAG, "Unsupported character encoding"); @@ -392,6 +391,11 @@ public final class Id3Decoder implements MetadataDecoder { private static TextInformationFrame decodeTxxxFrame(ParsableByteArray id3Data, int frameSize) throws UnsupportedEncodingException { + if (frameSize < 1) { + // Frame is malformed. + return null; + } + int encoding = id3Data.readUnsignedByte(); String charset = getCharsetName(encoding); @@ -415,9 +419,9 @@ public final class Id3Decoder implements MetadataDecoder { private static TextInformationFrame decodeTextInformationFrame(ParsableByteArray id3Data, int frameSize, String id) throws UnsupportedEncodingException { - if (frameSize <= 1) { - // Frame is empty or contains only the text encoding byte. - return new TextInformationFrame(id, null, ""); + if (frameSize < 1) { + // Frame is malformed. + return null; } int encoding = id3Data.readUnsignedByte(); @@ -434,6 +438,11 @@ public final class Id3Decoder implements MetadataDecoder { private static UrlLinkFrame decodeWxxxFrame(ParsableByteArray id3Data, int frameSize) throws UnsupportedEncodingException { + if (frameSize < 1) { + // Frame is malformed. + return null; + } + int encoding = id3Data.readUnsignedByte(); String charset = getCharsetName(encoding); @@ -457,11 +466,6 @@ public final class Id3Decoder implements MetadataDecoder { private static UrlLinkFrame decodeUrlLinkFrame(ParsableByteArray id3Data, int frameSize, String id) throws UnsupportedEncodingException { - if (frameSize == 0) { - // Frame is empty. - return new UrlLinkFrame(id, null, ""); - } - byte[] data = new byte[frameSize]; id3Data.readBytes(data, 0, frameSize); @@ -473,19 +477,19 @@ public final class Id3Decoder implements MetadataDecoder { private static PrivFrame decodePrivFrame(ParsableByteArray id3Data, int frameSize) throws UnsupportedEncodingException { - if (frameSize == 0) { - // Frame is empty. - return new PrivFrame("", new byte[0]); - } - byte[] data = new byte[frameSize]; id3Data.readBytes(data, 0, frameSize); int ownerEndIndex = indexOfZeroByte(data, 0); String owner = new String(data, 0, ownerEndIndex, "ISO-8859-1"); + byte[] privateData; int privateDataStartIndex = ownerEndIndex + 1; - byte[] privateData = Arrays.copyOfRange(data, privateDataStartIndex, data.length); + if (privateDataStartIndex < data.length) { + privateData = Arrays.copyOfRange(data, privateDataStartIndex, data.length); + } else { + privateData = new byte[0]; + } return new PrivFrame(owner, privateData); } @@ -556,6 +560,11 @@ public final class Id3Decoder implements MetadataDecoder { private static CommentFrame decodeCommentFrame(ParsableByteArray id3Data, int frameSize) throws UnsupportedEncodingException { + if (frameSize < 4) { + // Frame is malformed. + return null; + } + int encoding = id3Data.readUnsignedByte(); String charset = getCharsetName(encoding); @@ -701,6 +710,12 @@ public final class Id3Decoder implements MetadataDecoder { } } + private static String getFrameId(int majorVersion, int frameId0, int frameId1, int frameId2, + int frameId3) { + return majorVersion == 2 ? String.format(Locale.US, "%c%c%c", frameId0, frameId1, frameId2) + : String.format(Locale.US, "%c%c%c%c", frameId0, frameId1, frameId2, frameId3); + } + private static int indexOfEos(byte[] data, int fromIndex, int encoding) { int terminationPos = indexOfZeroByte(data, fromIndex); From 2a4df60b0167a9a4ea42afeaaf123dc7903600ea Mon Sep 17 00:00:00 2001 From: aquilescanta Date: Mon, 10 Apr 2017 11:06:06 -0700 Subject: [PATCH 077/119] Add pattern information to ExoPlayer's CryptoInfo ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=152708351 --- .../exoplayer2/decoder/CryptoInfo.java | 51 ++++++++++++++++++- 1 file changed, 49 insertions(+), 2 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/decoder/CryptoInfo.java b/library/core/src/main/java/com/google/android/exoplayer2/decoder/CryptoInfo.java index 866e421acc..0d143cdf49 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/decoder/CryptoInfo.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/decoder/CryptoInfo.java @@ -49,11 +49,21 @@ public final class CryptoInfo { * @see android.media.MediaCodec.CryptoInfo#numSubSamples */ public int numSubSamples; + /** + * @see android.media.MediaCodec.CryptoInfo.Pattern + */ + public int patternBlocksToEncrypt; + /** + * @see android.media.MediaCodec.CryptoInfo.Pattern + */ + public int patternBlocksToSkip; private final android.media.MediaCodec.CryptoInfo frameworkCryptoInfo; + private final PatternHolderV24 patternHolder; public CryptoInfo() { frameworkCryptoInfo = Util.SDK_INT >= 16 ? newFrameworkCryptoInfoV16() : null; + patternHolder = Util.SDK_INT >= 24 ? new PatternHolderV24(frameworkCryptoInfo) : null; } /** @@ -67,11 +77,21 @@ public final class CryptoInfo { this.key = key; this.iv = iv; this.mode = mode; + patternBlocksToEncrypt = 0; + patternBlocksToSkip = 0; if (Util.SDK_INT >= 16) { updateFrameworkCryptoInfoV16(); } } + public void setPattern(int patternBlocksToEncrypt, int patternBlocksToSkip) { + this.patternBlocksToEncrypt = patternBlocksToEncrypt; + this.patternBlocksToSkip = patternBlocksToSkip; + if (Util.SDK_INT >= 24) { + patternHolder.set(patternBlocksToEncrypt, patternBlocksToSkip); + } + } + /** * Returns an equivalent {@link android.media.MediaCodec.CryptoInfo} instance. *

    @@ -93,8 +113,35 @@ public final class CryptoInfo { @TargetApi(16) private void updateFrameworkCryptoInfoV16() { - frameworkCryptoInfo.set(numSubSamples, numBytesOfClearData, numBytesOfEncryptedData, key, iv, - mode); + // Update fields directly because the framework's CryptoInfo.set performs an unnecessary object + // allocation on Android N. + frameworkCryptoInfo.numSubSamples = numSubSamples; + frameworkCryptoInfo.numBytesOfClearData = numBytesOfClearData; + frameworkCryptoInfo.numBytesOfEncryptedData = numBytesOfEncryptedData; + frameworkCryptoInfo.key = key; + frameworkCryptoInfo.iv = iv; + frameworkCryptoInfo.mode = mode; + if (Util.SDK_INT >= 24) { + patternHolder.set(patternBlocksToEncrypt, patternBlocksToSkip); + } + } + + @TargetApi(24) + private static final class PatternHolderV24 { + + private final android.media.MediaCodec.CryptoInfo frameworkCryptoInfo; + private final android.media.MediaCodec.CryptoInfo.Pattern pattern; + + private PatternHolderV24(android.media.MediaCodec.CryptoInfo frameworkCryptoInfo) { + this.frameworkCryptoInfo = frameworkCryptoInfo; + pattern = new android.media.MediaCodec.CryptoInfo.Pattern(0, 0); + } + + private void set(int blocksToEncrypt, int blocksToSkip) { + pattern.set(blocksToEncrypt, blocksToSkip); + frameworkCryptoInfo.setPattern(pattern); + } + } } From f36500c20079dd965e4946ada18d936233ef0857 Mon Sep 17 00:00:00 2001 From: aquilescanta Date: Mon, 10 Apr 2017 11:06:32 -0700 Subject: [PATCH 078/119] Add support for audio adaptation When no video tracks or renderers are present, attempt audio adaptation. Issue:#1975 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=152708422 --- .../trackselection/DefaultTrackSelector.java | 205 ++++++++++++++---- 1 file changed, 158 insertions(+), 47 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelector.java b/library/core/src/main/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelector.java index 1fa372ca0a..9db77fd7ad 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelector.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelector.java @@ -372,24 +372,24 @@ public class DefaultTrackSelector extends MappingTrackSelector { private static final int[] NO_TRACKS = new int[0]; private static final int WITHIN_RENDERER_CAPABILITIES_BONUS = 1000; - private final TrackSelection.Factory adaptiveVideoTrackSelectionFactory; + private final TrackSelection.Factory adaptiveTrackSelectionFactory; private final AtomicReference paramsReference; /** - * Constructs an instance that does not support adaptive video. + * Constructs an instance that does not support adaptive tracks. */ public DefaultTrackSelector() { this(null); } /** - * Constructs an instance that uses a factory to create adaptive video track selections. + * Constructs an instance that uses a factory to create adaptive track selections. * - * @param adaptiveVideoTrackSelectionFactory A factory for adaptive video {@link TrackSelection}s, - * or null if the selector should not support adaptive video. + * @param adaptiveTrackSelectionFactory A factory for adaptive {@link TrackSelection}s, or null if + * the selector should not support adaptive tracks. */ - public DefaultTrackSelector(TrackSelection.Factory adaptiveVideoTrackSelectionFactory) { - this.adaptiveVideoTrackSelectionFactory = adaptiveVideoTrackSelectionFactory; + public DefaultTrackSelector(TrackSelection.Factory adaptiveTrackSelectionFactory) { + this.adaptiveTrackSelectionFactory = adaptiveTrackSelectionFactory; paramsReference = new AtomicReference<>(new Parameters()); } @@ -424,6 +424,7 @@ public class DefaultTrackSelector extends MappingTrackSelector { int rendererCount = rendererCapabilities.length; TrackSelection[] rendererTrackSelections = new TrackSelection[rendererCount]; Parameters params = paramsReference.get(); + boolean videoTrackAndRendererPresent = false; for (int i = 0; i < rendererCount; i++) { if (C.TRACK_TYPE_VIDEO == rendererCapabilities[i].getTrackType()) { @@ -431,8 +432,9 @@ public class DefaultTrackSelector extends MappingTrackSelector { rendererTrackGroupArrays[i], rendererFormatSupports[i], params.maxVideoWidth, params.maxVideoHeight, params.maxVideoBitrate, params.allowNonSeamlessAdaptiveness, params.allowMixedMimeAdaptiveness, params.viewportWidth, params.viewportHeight, - params.orientationMayChange, adaptiveVideoTrackSelectionFactory, + params.orientationMayChange, adaptiveTrackSelectionFactory, params.exceedVideoConstraintsIfNecessary, params.exceedRendererCapabilitiesIfNecessary); + videoTrackAndRendererPresent |= rendererTrackGroupArrays[i].length > 0; } } @@ -444,7 +446,8 @@ public class DefaultTrackSelector extends MappingTrackSelector { case C.TRACK_TYPE_AUDIO: rendererTrackSelections[i] = selectAudioTrack(rendererTrackGroupArrays[i], rendererFormatSupports[i], params.preferredAudioLanguage, - params.exceedRendererCapabilitiesIfNecessary); + params.exceedRendererCapabilitiesIfNecessary, params.allowMixedMimeAdaptiveness, + videoTrackAndRendererPresent ? null : adaptiveTrackSelectionFactory); break; case C.TRACK_TYPE_TEXT: rendererTrackSelections[i] = selectTextTrack(rendererTrackGroupArrays[i], @@ -467,15 +470,14 @@ public class DefaultTrackSelector extends MappingTrackSelector { TrackGroupArray groups, int[][] formatSupport, int maxVideoWidth, int maxVideoHeight, int maxVideoBitrate, boolean allowNonSeamlessAdaptiveness, boolean allowMixedMimeAdaptiveness, int viewportWidth, int viewportHeight, boolean orientationMayChange, - TrackSelection.Factory adaptiveVideoTrackSelectionFactory, - boolean exceedConstraintsIfNecessary, boolean exceedRendererCapabilitiesIfNecessary) - throws ExoPlaybackException { + TrackSelection.Factory adaptiveTrackSelectionFactory, boolean exceedConstraintsIfNecessary, + boolean exceedRendererCapabilitiesIfNecessary) throws ExoPlaybackException { TrackSelection selection = null; - if (adaptiveVideoTrackSelectionFactory != null) { + if (adaptiveTrackSelectionFactory != null) { selection = selectAdaptiveVideoTrack(rendererCapabilities, groups, formatSupport, maxVideoWidth, maxVideoHeight, maxVideoBitrate, allowNonSeamlessAdaptiveness, allowMixedMimeAdaptiveness, viewportWidth, viewportHeight, - orientationMayChange, adaptiveVideoTrackSelectionFactory); + orientationMayChange, adaptiveTrackSelectionFactory); } if (selection == null) { selection = selectFixedVideoTrack(groups, formatSupport, maxVideoWidth, maxVideoHeight, @@ -489,7 +491,7 @@ public class DefaultTrackSelector extends MappingTrackSelector { TrackGroupArray groups, int[][] formatSupport, int maxVideoWidth, int maxVideoHeight, int maxVideoBitrate, boolean allowNonSeamlessAdaptiveness, boolean allowMixedMimeAdaptiveness, int viewportWidth, int viewportHeight, boolean orientationMayChange, - TrackSelection.Factory adaptiveVideoTrackSelectionFactory) throws ExoPlaybackException { + TrackSelection.Factory adaptiveTrackSelectionFactory) throws ExoPlaybackException { int requiredAdaptiveSupport = allowNonSeamlessAdaptiveness ? (RendererCapabilities.ADAPTIVE_NOT_SEAMLESS | RendererCapabilities.ADAPTIVE_SEAMLESS) : RendererCapabilities.ADAPTIVE_SEAMLESS; @@ -497,17 +499,17 @@ public class DefaultTrackSelector extends MappingTrackSelector { && (rendererCapabilities.supportsMixedMimeTypeAdaptation() & requiredAdaptiveSupport) != 0; for (int i = 0; i < groups.length; i++) { TrackGroup group = groups.get(i); - int[] adaptiveTracks = getAdaptiveTracksForGroup(group, formatSupport[i], + int[] adaptiveTracks = getAdaptiveVideoTracksForGroup(group, formatSupport[i], allowMixedMimeTypes, requiredAdaptiveSupport, maxVideoWidth, maxVideoHeight, maxVideoBitrate, viewportWidth, viewportHeight, orientationMayChange); if (adaptiveTracks.length > 0) { - return adaptiveVideoTrackSelectionFactory.createTrackSelection(group, adaptiveTracks); + return adaptiveTrackSelectionFactory.createTrackSelection(group, adaptiveTracks); } } return null; } - private static int[] getAdaptiveTracksForGroup(TrackGroup group, int[] formatSupport, + private static int[] getAdaptiveVideoTracksForGroup(TrackGroup group, int[] formatSupport, boolean allowMixedMimeTypes, int requiredAdaptiveSupport, int maxVideoWidth, int maxVideoHeight, int maxVideoBitrate, int viewportWidth, int viewportHeight, boolean orientationMayChange) { @@ -530,7 +532,7 @@ public class DefaultTrackSelector extends MappingTrackSelector { int trackIndex = selectedTrackIndices.get(i); String sampleMimeType = group.getFormat(trackIndex).sampleMimeType; if (seenMimeTypes.add(sampleMimeType)) { - int countForMimeType = getAdaptiveTrackCountForMimeType(group, formatSupport, + int countForMimeType = getAdaptiveVideoTrackCountForMimeType(group, formatSupport, requiredAdaptiveSupport, sampleMimeType, maxVideoWidth, maxVideoHeight, maxVideoBitrate, selectedTrackIndices); if (countForMimeType > selectedMimeTypeTrackCount) { @@ -542,13 +544,13 @@ public class DefaultTrackSelector extends MappingTrackSelector { } // Filter by the selected mime type. - filterAdaptiveTrackCountForMimeType(group, formatSupport, requiredAdaptiveSupport, + filterAdaptiveVideoTrackCountForMimeType(group, formatSupport, requiredAdaptiveSupport, selectedMimeType, maxVideoWidth, maxVideoHeight, maxVideoBitrate, selectedTrackIndices); return selectedTrackIndices.size() < 2 ? NO_TRACKS : Util.toArray(selectedTrackIndices); } - private static int getAdaptiveTrackCountForMimeType(TrackGroup group, int[] formatSupport, + private static int getAdaptiveVideoTrackCountForMimeType(TrackGroup group, int[] formatSupport, int requiredAdaptiveSupport, String mimeType, int maxVideoWidth, int maxVideoHeight, int maxVideoBitrate, List selectedTrackIndices) { int adaptiveTrackCount = 0; @@ -563,9 +565,9 @@ public class DefaultTrackSelector extends MappingTrackSelector { return adaptiveTrackCount; } - private static void filterAdaptiveTrackCountForMimeType(TrackGroup group, int[] formatSupport, - int requiredAdaptiveSupport, String mimeType, int maxVideoWidth, int maxVideoHeight, - int maxVideoBitrate, List selectedTrackIndices) { + private static void filterAdaptiveVideoTrackCountForMimeType(TrackGroup group, + int[] formatSupport, int requiredAdaptiveSupport, String mimeType, int maxVideoWidth, + int maxVideoHeight, int maxVideoBitrate, List selectedTrackIndices) { for (int i = selectedTrackIndices.size() - 1; i >= 0; i--) { int trackIndex = selectedTrackIndices.get(i); if (!isSupportedAdaptiveVideoTrack(group.getFormat(trackIndex), mimeType, @@ -661,9 +663,10 @@ public class DefaultTrackSelector extends MappingTrackSelector { // Audio track selection implementation. protected TrackSelection selectAudioTrack(TrackGroupArray groups, int[][] formatSupport, - String preferredAudioLanguage, boolean exceedRendererCapabilitiesIfNecessary) { - TrackGroup selectedGroup = null; - int selectedTrackIndex = 0; + String preferredAudioLanguage, boolean exceedRendererCapabilitiesIfNecessary, + boolean allowMixedMimeAdaptiveness, TrackSelection.Factory adaptiveTrackSelectionFactory) { + int selectedGroupIndex = C.INDEX_UNSET; + int selectedTrackIndex = C.INDEX_UNSET; int selectedTrackScore = 0; for (int groupIndex = 0; groupIndex < groups.length; groupIndex++) { TrackGroup trackGroup = groups.get(groupIndex); @@ -671,32 +674,105 @@ public class DefaultTrackSelector extends MappingTrackSelector { for (int trackIndex = 0; trackIndex < trackGroup.length; trackIndex++) { if (isSupported(trackFormatSupport[trackIndex], exceedRendererCapabilitiesIfNecessary)) { Format format = trackGroup.getFormat(trackIndex); - boolean isDefault = (format.selectionFlags & C.SELECTION_FLAG_DEFAULT) != 0; - int trackScore; - if (formatHasLanguage(format, preferredAudioLanguage)) { - if (isDefault) { - trackScore = 4; - } else { - trackScore = 3; - } - } else if (isDefault) { - trackScore = 2; - } else { - trackScore = 1; - } - if (isSupported(trackFormatSupport[trackIndex], false)) { - trackScore += WITHIN_RENDERER_CAPABILITIES_BONUS; - } + int trackScore = getAudioTrackScore(trackFormatSupport[trackIndex], + preferredAudioLanguage, format); if (trackScore > selectedTrackScore) { - selectedGroup = trackGroup; + selectedGroupIndex = groupIndex; selectedTrackIndex = trackIndex; selectedTrackScore = trackScore; } } } } - return selectedGroup == null ? null - : new FixedTrackSelection(selectedGroup, selectedTrackIndex); + + if (selectedGroupIndex == C.INDEX_UNSET) { + return null; + } + + TrackGroup selectedGroup = groups.get(selectedGroupIndex); + if (adaptiveTrackSelectionFactory != null) { + // If the group of the track with the highest score allows it, try to enable adaptation. + int[] adaptiveTracks = getAdaptiveAudioTracks(selectedGroup, + formatSupport[selectedGroupIndex], allowMixedMimeAdaptiveness); + if (adaptiveTracks.length > 0) { + return adaptiveTrackSelectionFactory.createTrackSelection(selectedGroup, + adaptiveTracks); + } + } + return new FixedTrackSelection(selectedGroup, selectedTrackIndex); + } + + private static int getAudioTrackScore(int formatSupport, String preferredLanguage, + Format format) { + boolean isDefault = (format.selectionFlags & C.SELECTION_FLAG_DEFAULT) != 0; + int trackScore; + if (formatHasLanguage(format, preferredLanguage)) { + if (isDefault) { + trackScore = 4; + } else { + trackScore = 3; + } + } else if (isDefault) { + trackScore = 2; + } else { + trackScore = 1; + } + if (isSupported(formatSupport, false)) { + trackScore += WITHIN_RENDERER_CAPABILITIES_BONUS; + } + return trackScore; + } + + private static int[] getAdaptiveAudioTracks(TrackGroup group, int[] formatSupport, + boolean allowMixedMimeTypes) { + int selectedConfigurationTrackCount = 0; + AudioConfigurationTuple selectedConfiguration = null; + HashSet seenConfigurationTuples = new HashSet<>(); + for (int i = 0; i < group.length; i++) { + Format format = group.getFormat(i); + AudioConfigurationTuple configuration = new AudioConfigurationTuple( + format.channelCount, format.sampleRate, + allowMixedMimeTypes ? null : format.sampleMimeType); + if (seenConfigurationTuples.add(configuration)) { + int configurationCount = getAdaptiveAudioTrackCount(group, formatSupport, configuration); + if (configurationCount > selectedConfigurationTrackCount) { + selectedConfiguration = configuration; + selectedConfigurationTrackCount = configurationCount; + } + } + } + + if (selectedConfigurationTrackCount > 1) { + int[] adaptiveIndices = new int[selectedConfigurationTrackCount]; + int index = 0; + for (int i = 0; i < group.length; i++) { + if (isSupportedAdaptiveAudioTrack(group.getFormat(i), formatSupport[i], + selectedConfiguration)) { + adaptiveIndices[index++] = i; + } + } + return adaptiveIndices; + } + return NO_TRACKS; + } + + private static int getAdaptiveAudioTrackCount(TrackGroup group, int[] formatSupport, + AudioConfigurationTuple configuration) { + int count = 0; + for (int i = 0; i < group.length; i++) { + if (isSupportedAdaptiveAudioTrack(group.getFormat(i), formatSupport[i], configuration)) { + count++; + } + } + return count; + } + + private static boolean isSupportedAdaptiveAudioTrack(Format format, int formatSupport, + AudioConfigurationTuple configuration) { + return isSupported(formatSupport, false) && format.channelCount == configuration.channelCount + && format.sampleRate == configuration.sampleRate + && (configuration.mimeType == null + || TextUtils.equals(configuration.mimeType, format.sampleMimeType)); } // Text track selection implementation. @@ -791,7 +867,7 @@ public class DefaultTrackSelector extends MappingTrackSelector { } protected static boolean formatHasLanguage(Format format, String language) { - return language != null && language.equals(Util.normalizeLanguageCode(format.language)); + return TextUtils.equals(language, Util.normalizeLanguageCode(format.language)); } // Viewport size util methods. @@ -865,4 +941,39 @@ public class DefaultTrackSelector extends MappingTrackSelector { } } + private static final class AudioConfigurationTuple { + + public final int channelCount; + public final int sampleRate; + public final String mimeType; + + public AudioConfigurationTuple(int channelCount, int sampleRate, String mimeType) { + this.channelCount = channelCount; + this.sampleRate = sampleRate; + this.mimeType = mimeType; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + AudioConfigurationTuple other = (AudioConfigurationTuple) obj; + return channelCount == other.channelCount && sampleRate == other.sampleRate + && TextUtils.equals(mimeType, other.mimeType); + } + + @Override + public int hashCode() { + int result = channelCount; + result = 31 * result + sampleRate; + result = 31 * result + (mimeType != null ? mimeType.hashCode() : 0); + return result; + } + + } + } From 59c44af4cb391a7bfd223f36b357cf74274fbd9d Mon Sep 17 00:00:00 2001 From: Tyler Roach Date: Thu, 13 Apr 2017 14:56:22 -0400 Subject: [PATCH 079/119] Added mp3 support to FLV extractor --- .../extractor/flv/AudioTagPayloadReader.java | 55 ++++++++++++++----- 1 file changed, 40 insertions(+), 15 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/flv/AudioTagPayloadReader.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/flv/AudioTagPayloadReader.java index 3ee87b47ea..2fc586ce22 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/flv/AudioTagPayloadReader.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/flv/AudioTagPayloadReader.java @@ -15,6 +15,7 @@ */ package com.google.android.exoplayer2.extractor.flv; +import android.util.Log; import android.util.Pair; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.Format; @@ -29,6 +30,9 @@ import java.util.Collections; */ /* package */ final class AudioTagPayloadReader extends TagPayloadReader { + private static final String TAG = "AudioTagPayloadReader"; + + private static final int AUDIO_FORMAT_MP3 = 2; private static final int AUDIO_FORMAT_ALAW = 7; private static final int AUDIO_FORMAT_ULAW = 8; private static final int AUDIO_FORMAT_AAC = 10; @@ -36,6 +40,11 @@ import java.util.Collections; private static final int AAC_PACKET_TYPE_SEQUENCE_HEADER = 0; private static final int AAC_PACKET_TYPE_AAC_RAW = 1; + // SAMPLING RATES USED FOR MP3 + private static final int[] AUDIO_SAMPLING_RATE_TABLE = new int[] { + 5512, 11025, 22050, 44100 + }; + // State variables private boolean hasParsedAudioDataHeader; private boolean hasOutputFormat; @@ -55,8 +64,19 @@ import java.util.Collections; if (!hasParsedAudioDataHeader) { int header = data.readUnsignedByte(); audioFormat = (header >> 4) & 0x0F; - // TODO: Add support for MP3. - if (audioFormat == AUDIO_FORMAT_ALAW || audioFormat == AUDIO_FORMAT_ULAW) { + if (audioFormat == AUDIO_FORMAT_MP3) { + int sampleRateIndex = (header >> 2) & 0x03; + if (sampleRateIndex < 0 || sampleRateIndex >= AUDIO_SAMPLING_RATE_TABLE.length) { + Log.d(TAG, "Invalid sample rate, attempting to read at 44.1k"); + //rather than throw an exception, lets just attempt to play at 44.1k + sampleRateIndex = 3; + } + int sampleRate = AUDIO_SAMPLING_RATE_TABLE[sampleRateIndex]; + Format format = Format.createAudioSampleFormat(null, MimeTypes.AUDIO_MPEG, null, + Format.NO_VALUE, Format.NO_VALUE, 1, sampleRate, null, null, 0, null); + output.format(format); + hasOutputFormat = true; + } else if (audioFormat == AUDIO_FORMAT_ALAW || audioFormat == AUDIO_FORMAT_ULAW) { String type = audioFormat == AUDIO_FORMAT_ALAW ? MimeTypes.AUDIO_ALAW : MimeTypes.AUDIO_ULAW; int pcmEncoding = (header & 0x01) == 1 ? C.ENCODING_PCM_16BIT : C.ENCODING_PCM_8BIT; @@ -77,22 +97,27 @@ import java.util.Collections; @Override protected void parsePayload(ParsableByteArray data, long timeUs) { - int packetType = data.readUnsignedByte(); - if (packetType == AAC_PACKET_TYPE_SEQUENCE_HEADER && !hasOutputFormat) { - // Parse the sequence header. - byte[] audioSpecificConfig = new byte[data.bytesLeft()]; - data.readBytes(audioSpecificConfig, 0, audioSpecificConfig.length); - Pair audioParams = CodecSpecificDataUtil.parseAacAudioSpecificConfig( - audioSpecificConfig); - Format format = Format.createAudioSampleFormat(null, MimeTypes.AUDIO_AAC, null, - Format.NO_VALUE, Format.NO_VALUE, audioParams.second, audioParams.first, - Collections.singletonList(audioSpecificConfig), null, 0, null); - output.format(format); - hasOutputFormat = true; - } else if (audioFormat != AUDIO_FORMAT_AAC || packetType == AAC_PACKET_TYPE_AAC_RAW) { + if (audioFormat == AUDIO_FORMAT_MP3) { int sampleSize = data.bytesLeft(); output.sampleData(data, sampleSize); output.sampleMetadata(timeUs, C.BUFFER_FLAG_KEY_FRAME, sampleSize, 0, null); + } else { + int packetType = data.readUnsignedByte(); + if (packetType == AAC_PACKET_TYPE_SEQUENCE_HEADER && !hasOutputFormat) { + // Parse the sequence header. + byte[] audioSpecificConfig = new byte[data.bytesLeft()]; + data.readBytes(audioSpecificConfig, 0, audioSpecificConfig.length); + Pair audioParams = CodecSpecificDataUtil.parseAacAudioSpecificConfig(audioSpecificConfig); + Format format = + Format.createAudioSampleFormat(null, MimeTypes.AUDIO_AAC, null, Format.NO_VALUE, Format.NO_VALUE, audioParams.second, audioParams.first, + Collections.singletonList(audioSpecificConfig), null, 0, null); + output.format(format); + hasOutputFormat = true; + } else if (audioFormat != AUDIO_FORMAT_AAC || packetType == AAC_PACKET_TYPE_AAC_RAW) { + int sampleSize = data.bytesLeft(); + output.sampleData(data, sampleSize); + output.sampleMetadata(timeUs, C.BUFFER_FLAG_KEY_FRAME, sampleSize, 0, null); + } } } From d27d6426c472ad296a45b5217024ab8f48833cc7 Mon Sep 17 00:00:00 2001 From: Gleb Pinigin Date: Fri, 14 Apr 2017 14:23:23 +0700 Subject: [PATCH 080/119] Simplify cross-compiling on Mac OS X host machine by copy-paste commands --- extensions/ffmpeg/README.md | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/extensions/ffmpeg/README.md b/extensions/ffmpeg/README.md index c34b1b68e9..a14e51a894 100644 --- a/extensions/ffmpeg/README.md +++ b/extensions/ffmpeg/README.md @@ -31,6 +31,13 @@ FFMPEG_EXT_PATH="${EXOPLAYER_ROOT}/extensions/ffmpeg/src/main" NDK_PATH="" ``` +* Set up host platform ("darwin-x86_64" for Mac OS X): + +``` +HOST_PLATFORM="linux-x86_64" +``` + + * Fetch and build FFmpeg. For example, to fetch and build for armeabi-v7a, arm64-v8a and x86 on Linux x86_64: @@ -60,7 +67,7 @@ git clone git://source.ffmpeg.org/ffmpeg ffmpeg && cd ffmpeg && \ --libdir=android-libs/armeabi-v7a \ --arch=arm \ --cpu=armv7-a \ - --cross-prefix="${NDK_PATH}/toolchains/arm-linux-androideabi-4.9/prebuilt/linux-x86_64/bin/arm-linux-androideabi-" \ + --cross-prefix="${NDK_PATH}/toolchains/arm-linux-androideabi-4.9/prebuilt/${HOST_PLATFORM}/bin/arm-linux-androideabi-" \ --sysroot="${NDK_PATH}/platforms/android-9/arch-arm/" \ --extra-cflags="-march=armv7-a -mfloat-abi=softfp" \ --extra-ldflags="-Wl,--fix-cortex-a8" \ @@ -72,7 +79,7 @@ make clean && ./configure \ --libdir=android-libs/arm64-v8a \ --arch=aarch64 \ --cpu=armv8-a \ - --cross-prefix="${NDK_PATH}/toolchains/aarch64-linux-android-4.9/prebuilt/linux-x86_64/bin/aarch64-linux-android-" \ + --cross-prefix="${NDK_PATH}/toolchains/aarch64-linux-android-4.9/prebuilt/${HOST_PLATFORM}/bin/aarch64-linux-android-" \ --sysroot="${NDK_PATH}/platforms/android-21/arch-arm64/" \ --extra-ldexeflags=-pie \ ${COMMON_OPTIONS} \ @@ -82,7 +89,7 @@ make clean && ./configure \ --libdir=android-libs/x86 \ --arch=x86 \ --cpu=i686 \ - --cross-prefix="${NDK_PATH}/toolchains/x86-4.9/prebuilt/linux-x86_64/bin/i686-linux-android-" \ + --cross-prefix="${NDK_PATH}/toolchains/x86-4.9/prebuilt/${HOST_PLATFORM}/bin/i686-linux-android-" \ --sysroot="${NDK_PATH}/platforms/android-9/arch-x86/" \ --extra-ldexeflags=-pie \ --disable-asm \ From 896f63a3c38f92b34c0df0fb6eb9379eb83580e8 Mon Sep 17 00:00:00 2001 From: Rohit Krishnan Date: Fri, 14 Apr 2017 12:11:04 -0400 Subject: [PATCH 081/119] Fixes OOM that can occur from reading first NAL packet before sequence header --- .../exoplayer2/extractor/flv/VideoTagPayloadReader.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/flv/VideoTagPayloadReader.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/flv/VideoTagPayloadReader.java index 3ff7dd608e..a86836d56f 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/flv/VideoTagPayloadReader.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/flv/VideoTagPayloadReader.java @@ -93,7 +93,7 @@ import com.google.android.exoplayer2.video.AvcConfig; avcConfig.initializationData, Format.NO_VALUE, avcConfig.pixelWidthAspectRatio, null); output.format(format); hasOutputFormat = true; - } else if (packetType == AVC_PACKET_TYPE_AVC_NALU) { + } else if (packetType == AVC_PACKET_TYPE_AVC_NALU && hasOutputFormat) { // TODO: Deduplicate with Mp4Extractor. // Zero the top three bytes of the array that we'll use to decode nal unit lengths, in case // they're only 1 or 2 bytes long. @@ -101,6 +101,7 @@ import com.google.android.exoplayer2.video.AvcConfig; nalLengthData[0] = 0; nalLengthData[1] = 0; nalLengthData[2] = 0; + if(nalUnitLengthFieldLength == 0) nalUnitLengthFieldLength = 4; int nalUnitLengthFieldLengthDiff = 4 - nalUnitLengthFieldLength; // NAL units are length delimited, but the decoder requires start code delimited units. // Loop until we've written the sample to the track output, replacing length delimiters with From 329b8910cac6684ea4f67327fe434b182bff5e30 Mon Sep 17 00:00:00 2001 From: Rohit Krishnan Date: Mon, 17 Apr 2017 22:07:33 -0400 Subject: [PATCH 082/119] Remove unnecessary check. sampleRateIndex is masked with 0x03 so it is constrained to be between 0 and 3. --- .../exoplayer2/extractor/flv/AudioTagPayloadReader.java | 7 ------- 1 file changed, 7 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/flv/AudioTagPayloadReader.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/flv/AudioTagPayloadReader.java index 2fc586ce22..d87802eca6 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/flv/AudioTagPayloadReader.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/flv/AudioTagPayloadReader.java @@ -30,8 +30,6 @@ import java.util.Collections; */ /* package */ final class AudioTagPayloadReader extends TagPayloadReader { - private static final String TAG = "AudioTagPayloadReader"; - private static final int AUDIO_FORMAT_MP3 = 2; private static final int AUDIO_FORMAT_ALAW = 7; private static final int AUDIO_FORMAT_ULAW = 8; @@ -66,11 +64,6 @@ import java.util.Collections; audioFormat = (header >> 4) & 0x0F; if (audioFormat == AUDIO_FORMAT_MP3) { int sampleRateIndex = (header >> 2) & 0x03; - if (sampleRateIndex < 0 || sampleRateIndex >= AUDIO_SAMPLING_RATE_TABLE.length) { - Log.d(TAG, "Invalid sample rate, attempting to read at 44.1k"); - //rather than throw an exception, lets just attempt to play at 44.1k - sampleRateIndex = 3; - } int sampleRate = AUDIO_SAMPLING_RATE_TABLE[sampleRateIndex]; Format format = Format.createAudioSampleFormat(null, MimeTypes.AUDIO_MPEG, null, Format.NO_VALUE, Format.NO_VALUE, 1, sampleRate, null, null, 0, null); From 3b4d981891ca23b16a9d46666ef1b84256456d4a Mon Sep 17 00:00:00 2001 From: Tyler Roach Date: Tue, 18 Apr 2017 09:32:40 -0400 Subject: [PATCH 083/119] If hasOutputFormat is true, nalUnitLengthFieldLengthDiff will never be 0 --- .../android/exoplayer2/extractor/flv/VideoTagPayloadReader.java | 1 - 1 file changed, 1 deletion(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/flv/VideoTagPayloadReader.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/flv/VideoTagPayloadReader.java index a86836d56f..8a4d314ee0 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/flv/VideoTagPayloadReader.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/flv/VideoTagPayloadReader.java @@ -101,7 +101,6 @@ import com.google.android.exoplayer2.video.AvcConfig; nalLengthData[0] = 0; nalLengthData[1] = 0; nalLengthData[2] = 0; - if(nalUnitLengthFieldLength == 0) nalUnitLengthFieldLength = 4; int nalUnitLengthFieldLengthDiff = 4 - nalUnitLengthFieldLength; // NAL units are length delimited, but the decoder requires start code delimited units. // Loop until we've written the sample to the track output, replacing length delimiters with From 1dc8bb5bb17f1a627546eb7e8b4af8fb0cf31697 Mon Sep 17 00:00:00 2001 From: tonihei Date: Tue, 11 Apr 2017 03:35:33 -0700 Subject: [PATCH 084/119] Support 'styl' in Tx3g decoder. Extended Tx3gDecoder to read additional information after subtitle text. Currently parses font face, font size, and foreground colour. Font identifier and other information provided in subtitle sample description not yet supported. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=152793774 --- .../src/androidTest/assets/tx3g/no_subtitle | Bin 0 -> 2 bytes .../assets/tx3g/sample_utf16_be_no_styl | Bin 0 -> 8 bytes .../assets/tx3g/sample_utf16_le_no_styl | Bin 0 -> 8 bytes .../assets/tx3g/sample_with_multiple_styl | Bin 0 -> 61 bytes .../assets/tx3g/sample_with_other_extension | Bin 0 -> 39 bytes .../androidTest/assets/tx3g/sample_with_styl | Bin 0 -> 31 bytes .../exoplayer2/text/tx3g/Tx3gDecoderTest.java | 123 ++++++++++++++++++ .../java/com/google/android/exoplayer2/C.java | 5 + .../exoplayer2/text/tx3g/Tx3gDecoder.java | 115 +++++++++++++++- .../exoplayer2/util/ParsableByteArray.java | 8 ++ 10 files changed, 244 insertions(+), 7 deletions(-) create mode 100644 library/core/src/androidTest/assets/tx3g/no_subtitle create mode 100644 library/core/src/androidTest/assets/tx3g/sample_utf16_be_no_styl create mode 100644 library/core/src/androidTest/assets/tx3g/sample_utf16_le_no_styl create mode 100644 library/core/src/androidTest/assets/tx3g/sample_with_multiple_styl create mode 100644 library/core/src/androidTest/assets/tx3g/sample_with_other_extension create mode 100644 library/core/src/androidTest/assets/tx3g/sample_with_styl create mode 100644 library/core/src/androidTest/java/com/google/android/exoplayer2/text/tx3g/Tx3gDecoderTest.java diff --git a/library/core/src/androidTest/assets/tx3g/no_subtitle b/library/core/src/androidTest/assets/tx3g/no_subtitle new file mode 100644 index 0000000000000000000000000000000000000000..09f370e38f498a462e1ca0faa724559b6630c04f GIT binary patch literal 2 JcmZQz0000200961 literal 0 HcmV?d00001 diff --git a/library/core/src/androidTest/assets/tx3g/sample_utf16_be_no_styl b/library/core/src/androidTest/assets/tx3g/sample_utf16_be_no_styl new file mode 100644 index 0000000000000000000000000000000000000000..9c3fed2b7d4ac24ba78ba32f0084f63eb68ee28c GIT binary patch literal 8 PcmZQz`}f~JA+i<#4y*%- literal 0 HcmV?d00001 diff --git a/library/core/src/androidTest/assets/tx3g/sample_utf16_le_no_styl b/library/core/src/androidTest/assets/tx3g/sample_utf16_le_no_styl new file mode 100644 index 0000000000000000000000000000000000000000..03a2cb23505459a02654e345b8dbb21807e88ca0 GIT binary patch literal 8 PcmZQz`~NS&zcvy84&noe literal 0 HcmV?d00001 diff --git a/library/core/src/androidTest/assets/tx3g/sample_with_multiple_styl b/library/core/src/androidTest/assets/tx3g/sample_with_multiple_styl new file mode 100644 index 0000000000000000000000000000000000000000..48b01c3438e861713348e131e02206831379d6b4 GIT binary patch literal 61 zcmZSJ^~uajRWRZLQ^pJo40^>Sl{pN|Ko% T findSpan(SpannedString testObject, int expectedStart, int expectedEnd, + Class expectedType) { + T[] spans = testObject.getSpans(0, testObject.length(), expectedType); + for (T span : spans) { + if (testObject.getSpanStart(span) == expectedStart + && testObject.getSpanEnd(span) == expectedEnd) { + return span; + } + } + fail("Span not found."); + return null; + } + +} diff --git a/library/core/src/main/java/com/google/android/exoplayer2/C.java b/library/core/src/main/java/com/google/android/exoplayer2/C.java index 02e5939b86..56092f17a9 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/C.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/C.java @@ -76,6 +76,11 @@ public final class C { */ public static final String UTF8_NAME = "UTF-8"; + /** + * The name of the UTF-16 charset. + */ + public static final String UTF16_NAME = "UTF-16"; + /** * Crypto modes for a codec. */ diff --git a/library/core/src/main/java/com/google/android/exoplayer2/text/tx3g/Tx3gDecoder.java b/library/core/src/main/java/com/google/android/exoplayer2/text/tx3g/Tx3gDecoder.java index dccb64caec..79ec89838a 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/text/tx3g/Tx3gDecoder.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/text/tx3g/Tx3gDecoder.java @@ -15,10 +15,21 @@ */ package com.google.android.exoplayer2.text.tx3g; +import android.graphics.Typeface; +import android.text.SpannableStringBuilder; +import android.text.Spanned; +import android.text.style.ForegroundColorSpan; +import android.text.style.StyleSpan; +import android.text.style.UnderlineSpan; +import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.text.Cue; import com.google.android.exoplayer2.text.SimpleSubtitleDecoder; import com.google.android.exoplayer2.text.Subtitle; +import com.google.android.exoplayer2.text.SubtitleDecoderException; +import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.ParsableByteArray; +import com.google.android.exoplayer2.util.Util; +import java.nio.charset.Charset; /** * A {@link SimpleSubtitleDecoder} for tx3g. @@ -27,6 +38,20 @@ import com.google.android.exoplayer2.util.ParsableByteArray; */ public final class Tx3gDecoder extends SimpleSubtitleDecoder { + private static final char BOM_UTF16_BE = '\uFEFF'; + private static final char BOM_UTF16_LE = '\uFFFE'; + + private static final int TYPE_STYL = Util.getIntegerCodeForString("styl"); + + private static final int SIZE_ATOM_HEADER = 8; + private static final int SIZE_SHORT = 2; + private static final int SIZE_BOM_UTF16 = 2; + private static final int SIZE_STYLE_RECORD = 12; + + private static final int FONT_FACE_BOLD = 0x0001; + private static final int FONT_FACE_ITALIC = 0x0002; + private static final int FONT_FACE_UNDERLINE = 0x0004; + private final ParsableByteArray parsableByteArray; public Tx3gDecoder() { @@ -35,14 +60,90 @@ public final class Tx3gDecoder extends SimpleSubtitleDecoder { } @Override - protected Subtitle decode(byte[] bytes, int length, boolean reset) { - parsableByteArray.reset(bytes, length); - int textLength = parsableByteArray.readUnsignedShort(); - if (textLength == 0) { - return Tx3gSubtitle.EMPTY; + protected Subtitle decode(byte[] bytes, int length, boolean reset) + throws SubtitleDecoderException { + try { + parsableByteArray.reset(bytes, length); + String cueTextString = readSubtitleText(parsableByteArray); + if (cueTextString.isEmpty()) { + return Tx3gSubtitle.EMPTY; + } + SpannableStringBuilder cueText = new SpannableStringBuilder(cueTextString); + while (parsableByteArray.bytesLeft() >= SIZE_ATOM_HEADER) { + int atomSize = parsableByteArray.readInt(); + int atomType = parsableByteArray.readInt(); + if (atomType == TYPE_STYL) { + Assertions.checkArgument(parsableByteArray.bytesLeft() >= SIZE_SHORT); + int styleRecordCount = parsableByteArray.readUnsignedShort(); + for (int i = 0; i < styleRecordCount; i++) { + applyStyleRecord(parsableByteArray, cueText); + } + } else { + parsableByteArray.skipBytes(atomSize - SIZE_ATOM_HEADER); + } + } + return new Tx3gSubtitle(new Cue(cueText)); + } catch (IllegalArgumentException e) { + throw new SubtitleDecoderException("Unexpected subtitle format.", e); } - String cueText = parsableByteArray.readString(textLength); - return new Tx3gSubtitle(new Cue(cueText)); } + private static String readSubtitleText(ParsableByteArray parsableByteArray) { + Assertions.checkArgument(parsableByteArray.bytesLeft() >= SIZE_SHORT); + int textLength = parsableByteArray.readUnsignedShort(); + if (textLength == 0) { + return ""; + } + if (parsableByteArray.bytesLeft() >= SIZE_BOM_UTF16) { + char firstChar = parsableByteArray.peekChar(); + if (firstChar == BOM_UTF16_BE || firstChar == BOM_UTF16_LE) { + return parsableByteArray.readString(textLength, Charset.forName(C.UTF16_NAME)); + } + } + return parsableByteArray.readString(textLength, Charset.forName(C.UTF8_NAME)); + } + + private static void applyStyleRecord(ParsableByteArray parsableByteArray, + SpannableStringBuilder cueText) { + Assertions.checkArgument(parsableByteArray.bytesLeft() >= SIZE_STYLE_RECORD); + int start = parsableByteArray.readUnsignedShort(); + int end = parsableByteArray.readUnsignedShort(); + parsableByteArray.skipBytes(2); // font identifier + int fontFace = parsableByteArray.readUnsignedByte(); + parsableByteArray.skipBytes(1); // font size + int colorRgba = parsableByteArray.readInt(); + + if (fontFace != 0) { + attachFontFace(cueText, fontFace, start, end); + } + attachColor(cueText, colorRgba, start, end); + } + + private static void attachFontFace(SpannableStringBuilder cueText, int fontFace, int start, + int end) { + boolean isBold = (fontFace & FONT_FACE_BOLD) != 0; + boolean isItalic = (fontFace & FONT_FACE_ITALIC) != 0; + if (isBold) { + if (isItalic) { + cueText.setSpan(new StyleSpan(Typeface.BOLD_ITALIC), start, end, + Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + } else { + cueText.setSpan(new StyleSpan(Typeface.BOLD), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + } + } else if (isItalic) { + cueText.setSpan(new StyleSpan(Typeface.ITALIC), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + } + + boolean isUnderlined = (fontFace & FONT_FACE_UNDERLINE) != 0; + if (isUnderlined) { + cueText.setSpan(new UnderlineSpan(), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + } + } + + private static void attachColor(SpannableStringBuilder cueText, int colorRgba, int start, + int end) { + int colorArgb = ((colorRgba & 0xFF) << 24) | (colorRgba >>> 8); + cueText.setSpan(new ForegroundColorSpan(colorArgb), start, end, + Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + } } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/util/ParsableByteArray.java b/library/core/src/main/java/com/google/android/exoplayer2/util/ParsableByteArray.java index ef4aa05cfe..2a907e5955 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/util/ParsableByteArray.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/util/ParsableByteArray.java @@ -201,6 +201,14 @@ public final class ParsableByteArray { return (data[position] & 0xFF); } + /** + * Peeks at the next char. + */ + public char peekChar() { + return (char) ((data[position] & 0xFF) << 8 + | (data[position + 1] & 0xFF)); + } + /** * Reads the next byte as an unsigned value. */ From 85adecf948a7c30c149e2cf0de8267c30c73f8d4 Mon Sep 17 00:00:00 2001 From: olly Date: Tue, 11 Apr 2017 07:36:03 -0700 Subject: [PATCH 085/119] Remove reflection + make it easy to set extractor flags The idea of using reflection was so that a developer could delete a package they didn't want and have everything else still compile. However, a developer doing this is likely building from source, in which case editing the factories too is pretty trivial. Removing the reflection makes specifying extractor flags via the default factory easy, and removes the need for special proguard config. Issue: #2657 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=152810423 --- library/core/build.gradle | 1 - library/core/proguard-rules.txt | 10 - .../extractor/DefaultExtractorsFactory.java | 209 ++++++++---------- .../ts/DefaultTsPayloadReaderFactory.java | 15 +- .../exoplayer2/extractor/ts/TsExtractor.java | 12 +- .../metadata/MetadataDecoderFactory.java | 40 +--- .../text/SubtitleDecoderFactory.java | 83 +++---- 7 files changed, 158 insertions(+), 212 deletions(-) delete mode 100644 library/core/proguard-rules.txt diff --git a/library/core/build.gradle b/library/core/build.gradle index 49ed791a78..8ab63af26f 100644 --- a/library/core/build.gradle +++ b/library/core/build.gradle @@ -20,7 +20,6 @@ android { defaultConfig { minSdkVersion project.ext.minSdkVersion targetSdkVersion project.ext.targetSdkVersion - consumerProguardFiles 'proguard-rules.txt' } sourceSets { diff --git a/library/core/proguard-rules.txt b/library/core/proguard-rules.txt deleted file mode 100644 index c5d752f4c6..0000000000 --- a/library/core/proguard-rules.txt +++ /dev/null @@ -1,10 +0,0 @@ -# Accessed via reflection in SubtitleDecoderFactory.DEFAULT --keepclassmembers class com.google.android.exoplayer2.text.cea.Cea608Decoder { - public (java.lang.String, int); -} --keepclassmembers class com.google.android.exoplayer2.text.cea.Cea708Decoder { - public (int); -} --keepclassmembers class com.google.android.exoplayer2.text.dvb.DvbDecoder { - public (java.util.List); -} diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/DefaultExtractorsFactory.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/DefaultExtractorsFactory.java index 29fad1fbde..4ea8452956 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/DefaultExtractorsFactory.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/DefaultExtractorsFactory.java @@ -15,141 +15,118 @@ */ package com.google.android.exoplayer2.extractor; -import java.util.ArrayList; -import java.util.List; +import com.google.android.exoplayer2.extractor.flv.FlvExtractor; +import com.google.android.exoplayer2.extractor.mkv.MatroskaExtractor; +import com.google.android.exoplayer2.extractor.mp3.Mp3Extractor; +import com.google.android.exoplayer2.extractor.mp4.FragmentedMp4Extractor; +import com.google.android.exoplayer2.extractor.mp4.Mp4Extractor; +import com.google.android.exoplayer2.extractor.ogg.OggExtractor; +import com.google.android.exoplayer2.extractor.ts.Ac3Extractor; +import com.google.android.exoplayer2.extractor.ts.AdtsExtractor; +import com.google.android.exoplayer2.extractor.ts.DefaultTsPayloadReaderFactory; +import com.google.android.exoplayer2.extractor.ts.PsExtractor; +import com.google.android.exoplayer2.extractor.ts.TsExtractor; +import com.google.android.exoplayer2.extractor.wav.WavExtractor; +import java.lang.reflect.Constructor; /** * An {@link ExtractorsFactory} that provides an array of extractors for the following formats: * *

      - *
    • MP4, including M4A ({@link com.google.android.exoplayer2.extractor.mp4.Mp4Extractor})
    • - *
    • fMP4 ({@link com.google.android.exoplayer2.extractor.mp4.FragmentedMp4Extractor})
    • - *
    • Matroska and WebM ({@link com.google.android.exoplayer2.extractor.mkv.MatroskaExtractor}) - *
    • - *
    • Ogg Vorbis/FLAC ({@link com.google.android.exoplayer2.extractor.ogg.OggExtractor}
    • - *
    • MP3 ({@link com.google.android.exoplayer2.extractor.mp3.Mp3Extractor})
    • - *
    • AAC ({@link com.google.android.exoplayer2.extractor.ts.AdtsExtractor})
    • - *
    • MPEG TS ({@link com.google.android.exoplayer2.extractor.ts.TsExtractor})
    • - *
    • MPEG PS ({@link com.google.android.exoplayer2.extractor.ts.PsExtractor})
    • - *
    • FLV ({@link com.google.android.exoplayer2.extractor.flv.FlvExtractor})
    • - *
    • WAV ({@link com.google.android.exoplayer2.extractor.wav.WavExtractor})
    • + *
    • MP4, including M4A ({@link Mp4Extractor})
    • + *
    • fMP4 ({@link FragmentedMp4Extractor})
    • + *
    • Matroska and WebM ({@link MatroskaExtractor})
    • + *
    • Ogg Vorbis/FLAC ({@link OggExtractor}
    • + *
    • MP3 ({@link Mp3Extractor})
    • + *
    • AAC ({@link AdtsExtractor})
    • + *
    • MPEG TS ({@link TsExtractor})
    • + *
    • MPEG PS ({@link PsExtractor})
    • + *
    • FLV ({@link FlvExtractor})
    • + *
    • WAV ({@link WavExtractor})
    • + *
    • AC3 ({@link Ac3Extractor})
    • *
    • FLAC (only available if the FLAC extension is built and included)
    • *
    */ public final class DefaultExtractorsFactory implements ExtractorsFactory { - // Lazily initialized default extractor classes in priority order. - private static List> defaultExtractorClasses; + private static final Constructor FLAC_EXTRACTOR_CONSTRUCTOR; + static { + Constructor flacExtractorConstructor = null; + try { + flacExtractorConstructor = + Class.forName("com.google.android.exoplayer2.ext.flac.FlacExtractor") + .asSubclass(Extractor.class).getConstructor(); + } catch (ClassNotFoundException e) { + // Extractor not found. + } catch (NoSuchMethodException e) { + // Constructor not found. + } + FLAC_EXTRACTOR_CONSTRUCTOR = flacExtractorConstructor; + } + + private @FragmentedMp4Extractor.Flags int fragmentedMp4Flags; + private @Mp3Extractor.Flags int mp3Flags; + private @DefaultTsPayloadReaderFactory.Flags int tsFlags; /** - * Creates a new factory for the default extractors. + * Sets flags for {@link FragmentedMp4Extractor} instances created by the factory. + * + * @see FragmentedMp4Extractor#FragmentedMp4Extractor(int) + * @param flags The flags to use. + * @return The factory, for convenience. */ - public DefaultExtractorsFactory() { - synchronized (DefaultExtractorsFactory.class) { - if (defaultExtractorClasses == null) { - // Lazily initialize defaultExtractorClasses. - List> extractorClasses = new ArrayList<>(); - // We reference extractors using reflection so that they can be deleted cleanly. - // Class.forName is used so that automated tools like proguard can detect the use of - // reflection (see http://proguard.sourceforge.net/FAQ.html#forname). - try { - extractorClasses.add( - Class.forName("com.google.android.exoplayer2.extractor.mkv.MatroskaExtractor") - .asSubclass(Extractor.class)); - } catch (ClassNotFoundException e) { - // Extractor not found. - } - try { - extractorClasses.add( - Class.forName("com.google.android.exoplayer2.extractor.mp4.FragmentedMp4Extractor") - .asSubclass(Extractor.class)); - } catch (ClassNotFoundException e) { - // Extractor not found. - } - try { - extractorClasses.add( - Class.forName("com.google.android.exoplayer2.extractor.mp4.Mp4Extractor") - .asSubclass(Extractor.class)); - } catch (ClassNotFoundException e) { - // Extractor not found. - } - try { - extractorClasses.add( - Class.forName("com.google.android.exoplayer2.extractor.mp3.Mp3Extractor") - .asSubclass(Extractor.class)); - } catch (ClassNotFoundException e) { - // Extractor not found. - } - try { - extractorClasses.add( - Class.forName("com.google.android.exoplayer2.extractor.ts.AdtsExtractor") - .asSubclass(Extractor.class)); - } catch (ClassNotFoundException e) { - // Extractor not found. - } - try { - extractorClasses.add( - Class.forName("com.google.android.exoplayer2.extractor.ts.Ac3Extractor") - .asSubclass(Extractor.class)); - } catch (ClassNotFoundException e) { - // Extractor not found. - } - try { - extractorClasses.add( - Class.forName("com.google.android.exoplayer2.extractor.ts.TsExtractor") - .asSubclass(Extractor.class)); - } catch (ClassNotFoundException e) { - // Extractor not found. - } - try { - extractorClasses.add( - Class.forName("com.google.android.exoplayer2.extractor.flv.FlvExtractor") - .asSubclass(Extractor.class)); - } catch (ClassNotFoundException e) { - // Extractor not found. - } - try { - extractorClasses.add( - Class.forName("com.google.android.exoplayer2.extractor.ogg.OggExtractor") - .asSubclass(Extractor.class)); - } catch (ClassNotFoundException e) { - // Extractor not found. - } - try { - extractorClasses.add( - Class.forName("com.google.android.exoplayer2.extractor.ts.PsExtractor") - .asSubclass(Extractor.class)); - } catch (ClassNotFoundException e) { - // Extractor not found. - } - try { - extractorClasses.add( - Class.forName("com.google.android.exoplayer2.extractor.wav.WavExtractor") - .asSubclass(Extractor.class)); - } catch (ClassNotFoundException e) { - // Extractor not found. - } - try { - extractorClasses.add( - Class.forName("com.google.android.exoplayer2.ext.flac.FlacExtractor") - .asSubclass(Extractor.class)); - } catch (ClassNotFoundException e) { - // Extractor not found. - } - defaultExtractorClasses = extractorClasses; - } - } + public synchronized DefaultExtractorsFactory setFragmentedMp4ExtractorFlags( + @FragmentedMp4Extractor.Flags int flags) { + this.fragmentedMp4Flags = flags; + return this; + } + + /** + * Sets flags for {@link Mp3Extractor} instances created by the factory. + * + * @see Mp3Extractor#Mp3Extractor(int) + * @param flags The flags to use. + * @return The factory, for convenience. + */ + public synchronized DefaultExtractorsFactory setMp3ExtractorFlags(@Mp3Extractor.Flags int flags) { + mp3Flags = flags; + return this; + } + + /** + * Sets flags for {@link DefaultTsPayloadReaderFactory}s used by {@link TsExtractor} instances + * created by the factory. + * + * @see TsExtractor#TsExtractor(int) + * @param flags The flags to use. + * @return The factory, for convenience. + */ + public synchronized DefaultExtractorsFactory setTsExtractorFlags( + @DefaultTsPayloadReaderFactory.Flags int flags) { + tsFlags = flags; + return this; } @Override - public Extractor[] createExtractors() { - Extractor[] extractors = new Extractor[defaultExtractorClasses.size()]; - for (int i = 0; i < extractors.length; i++) { + public synchronized Extractor[] createExtractors() { + Extractor[] extractors = new Extractor[FLAC_EXTRACTOR_CONSTRUCTOR == null ? 11 : 12]; + extractors[0] = new MatroskaExtractor(); + extractors[1] = new FragmentedMp4Extractor(fragmentedMp4Flags); + extractors[2] = new Mp4Extractor(); + extractors[3] = new Mp3Extractor(mp3Flags); + extractors[4] = new AdtsExtractor(); + extractors[5] = new Ac3Extractor(); + extractors[6] = new TsExtractor(tsFlags); + extractors[7] = new FlvExtractor(); + extractors[8] = new OggExtractor(); + extractors[9] = new PsExtractor(); + extractors[10] = new WavExtractor(); + if (FLAC_EXTRACTOR_CONSTRUCTOR != null) { try { - extractors[i] = defaultExtractorClasses.get(i).getConstructor().newInstance(); + extractors[11] = FLAC_EXTRACTOR_CONSTRUCTOR.newInstance(); } catch (Exception e) { // Should never happen. - throw new IllegalStateException("Unexpected error creating default extractor", e); + throw new IllegalStateException("Unexpected error creating FLAC extractor", e); } } return extractors; diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/DefaultTsPayloadReaderFactory.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/DefaultTsPayloadReaderFactory.java index 1e391c3eca..3808d18b9a 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/DefaultTsPayloadReaderFactory.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/DefaultTsPayloadReaderFactory.java @@ -39,8 +39,7 @@ public final class DefaultTsPayloadReaderFactory implements TsPayloadReader.Fact @IntDef(flag = true, value = {FLAG_ALLOW_NON_IDR_KEYFRAMES, FLAG_IGNORE_AAC_STREAM, FLAG_IGNORE_H264_STREAM, FLAG_DETECT_ACCESS_UNITS, FLAG_IGNORE_SPLICE_INFO_STREAM, FLAG_OVERRIDE_CAPTION_DESCRIPTORS}) - public @interface Flags { - } + public @interface Flags {} public static final int FLAG_ALLOW_NON_IDR_KEYFRAMES = 1; public static final int FLAG_IGNORE_AAC_STREAM = 1 << 1; public static final int FLAG_IGNORE_H264_STREAM = 1 << 2; @@ -54,11 +53,19 @@ public final class DefaultTsPayloadReaderFactory implements TsPayloadReader.Fact private final List closedCaptionFormats; public DefaultTsPayloadReaderFactory() { - this(0, Collections.emptyList()); + this(0); } /** - * @param flags A combination of {@code FLAG_*} values, which control the behavior of the created + * @param flags A combination of {@code FLAG_*} values that control the behavior of the created + * readers. + */ + public DefaultTsPayloadReaderFactory(@Flags int flags) { + this(flags, Collections.emptyList()); + } + + /** + * @param flags A combination of {@code FLAG_*} values that control the behavior of the created * readers. * @param closedCaptionFormats {@link Format}s to be exposed by payload readers for streams with * embedded closed captions when no caption service descriptors are provided. If 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 e242414ff2..b1c1220c45 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 @@ -27,6 +27,7 @@ import com.google.android.exoplayer2.extractor.ExtractorsFactory; import com.google.android.exoplayer2.extractor.PositionHolder; import com.google.android.exoplayer2.extractor.SeekMap; import com.google.android.exoplayer2.extractor.TrackOutput; +import com.google.android.exoplayer2.extractor.ts.DefaultTsPayloadReaderFactory.Flags; import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.EsInfo; import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.TrackIdGenerator; import com.google.android.exoplayer2.util.Assertions; @@ -122,7 +123,16 @@ public final class TsExtractor implements Extractor { private TsPayloadReader id3Reader; public TsExtractor() { - this(MODE_NORMAL, new TimestampAdjuster(0), new DefaultTsPayloadReaderFactory()); + this(0); + } + + /** + * @param defaultTsPayloadReaderFlags A combination of {@link DefaultTsPayloadReaderFactory} + * {@code FLAG_*} values that control the behavior of the payload readers. + */ + public TsExtractor(@Flags int defaultTsPayloadReaderFlags) { + this(MODE_NORMAL, new TimestampAdjuster(0), + new DefaultTsPayloadReaderFactory(defaultTsPayloadReaderFlags)); } /** diff --git a/library/core/src/main/java/com/google/android/exoplayer2/metadata/MetadataDecoderFactory.java b/library/core/src/main/java/com/google/android/exoplayer2/metadata/MetadataDecoderFactory.java index 414a8269d7..028a8eb893 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/metadata/MetadataDecoderFactory.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/metadata/MetadataDecoderFactory.java @@ -58,39 +58,23 @@ public interface MetadataDecoderFactory { @Override public boolean supportsFormat(Format format) { - return getDecoderClass(format.sampleMimeType) != null; + String mimeType = format.sampleMimeType; + return MimeTypes.APPLICATION_ID3.equals(mimeType) + || MimeTypes.APPLICATION_EMSG.equals(mimeType) + || MimeTypes.APPLICATION_SCTE35.equals(mimeType); } @Override public MetadataDecoder createDecoder(Format format) { - try { - Class clazz = getDecoderClass(format.sampleMimeType); - if (clazz == null) { + switch (format.sampleMimeType) { + case MimeTypes.APPLICATION_ID3: + return new Id3Decoder(); + case MimeTypes.APPLICATION_EMSG: + return new EventMessageDecoder(); + case MimeTypes.APPLICATION_SCTE35: + return new SpliceInfoDecoder(); + default: throw new IllegalArgumentException("Attempted to create decoder for unsupported format"); - } - return clazz.asSubclass(MetadataDecoder.class).getConstructor().newInstance(); - } catch (Exception e) { - throw new IllegalStateException("Unexpected error instantiating decoder", e); - } - } - - private Class getDecoderClass(String mimeType) { - if (mimeType == null) { - return null; - } - try { - switch (mimeType) { - case MimeTypes.APPLICATION_ID3: - return Class.forName("com.google.android.exoplayer2.metadata.id3.Id3Decoder"); - case MimeTypes.APPLICATION_EMSG: - return Class.forName("com.google.android.exoplayer2.metadata.emsg.EventMessageDecoder"); - case MimeTypes.APPLICATION_SCTE35: - return Class.forName("com.google.android.exoplayer2.metadata.scte35.SpliceInfoDecoder"); - default: - return null; - } - } catch (ClassNotFoundException e) { - return null; } } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/text/SubtitleDecoderFactory.java b/library/core/src/main/java/com/google/android/exoplayer2/text/SubtitleDecoderFactory.java index 5f318916b5..f65d5a6e55 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/text/SubtitleDecoderFactory.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/text/SubtitleDecoderFactory.java @@ -18,13 +18,13 @@ package com.google.android.exoplayer2.text; import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.text.cea.Cea608Decoder; import com.google.android.exoplayer2.text.cea.Cea708Decoder; +import com.google.android.exoplayer2.text.dvb.DvbDecoder; import com.google.android.exoplayer2.text.subrip.SubripDecoder; import com.google.android.exoplayer2.text.ttml.TtmlDecoder; import com.google.android.exoplayer2.text.tx3g.Tx3gDecoder; import com.google.android.exoplayer2.text.webvtt.Mp4WebvttDecoder; import com.google.android.exoplayer2.text.webvtt.WebvttDecoder; import com.google.android.exoplayer2.util.MimeTypes; -import java.util.List; /** * A factory for {@link SubtitleDecoder} instances. @@ -61,68 +61,47 @@ public interface SubtitleDecoderFactory { *
  • TX3G ({@link Tx3gDecoder})
  • *
  • Cea608 ({@link Cea608Decoder})
  • *
  • Cea708 ({@link Cea708Decoder})
  • + *
  • DVB ({@link DvbDecoder})
  • * */ SubtitleDecoderFactory DEFAULT = new SubtitleDecoderFactory() { @Override public boolean supportsFormat(Format format) { - return getDecoderClass(format.sampleMimeType) != null; + String mimeType = format.sampleMimeType; + return MimeTypes.TEXT_VTT.equals(mimeType) + || MimeTypes.APPLICATION_TTML.equals(mimeType) + || MimeTypes.APPLICATION_MP4VTT.equals(mimeType) + || MimeTypes.APPLICATION_SUBRIP.equals(mimeType) + || MimeTypes.APPLICATION_TX3G.equals(mimeType) + || MimeTypes.APPLICATION_CEA608.equals(mimeType) + || MimeTypes.APPLICATION_MP4CEA608.equals(mimeType) + || MimeTypes.APPLICATION_CEA708.equals(mimeType) + || MimeTypes.APPLICATION_DVBSUBS.equals(mimeType); } @Override public SubtitleDecoder createDecoder(Format format) { - try { - Class clazz = getDecoderClass(format.sampleMimeType); - if (clazz == null) { + switch (format.sampleMimeType) { + case MimeTypes.TEXT_VTT: + return new WebvttDecoder(); + case MimeTypes.APPLICATION_MP4VTT: + return new Mp4WebvttDecoder(); + case MimeTypes.APPLICATION_TTML: + return new TtmlDecoder(); + case MimeTypes.APPLICATION_SUBRIP: + return new SubripDecoder(); + case MimeTypes.APPLICATION_TX3G: + return new Tx3gDecoder(); + case MimeTypes.APPLICATION_CEA608: + case MimeTypes.APPLICATION_MP4CEA608: + return new Cea608Decoder(format.sampleMimeType, format.accessibilityChannel); + case MimeTypes.APPLICATION_CEA708: + return new Cea708Decoder(format.accessibilityChannel); + case MimeTypes.APPLICATION_DVBSUBS: + return new DvbDecoder(format.initializationData); + default: throw new IllegalArgumentException("Attempted to create decoder for unsupported format"); - } - if (format.sampleMimeType.equals(MimeTypes.APPLICATION_CEA608) - || format.sampleMimeType.equals(MimeTypes.APPLICATION_MP4CEA608)) { - return clazz.asSubclass(SubtitleDecoder.class).getConstructor(String.class, Integer.TYPE) - .newInstance(format.sampleMimeType, format.accessibilityChannel); - } else if (format.sampleMimeType.equals(MimeTypes.APPLICATION_CEA708)) { - return clazz.asSubclass(SubtitleDecoder.class).getConstructor(Integer.TYPE) - .newInstance(format.accessibilityChannel); - } else if (format.sampleMimeType.equals(MimeTypes.APPLICATION_DVBSUBS)) { - return clazz.asSubclass(SubtitleDecoder.class).getConstructor(List.class) - .newInstance(format.initializationData); - } else { - return clazz.asSubclass(SubtitleDecoder.class).getConstructor().newInstance(); - } - } catch (Exception e) { - throw new IllegalStateException("Unexpected error instantiating decoder", e); - } - } - - private Class getDecoderClass(String mimeType) { - if (mimeType == null) { - return null; - } - try { - switch (mimeType) { - case MimeTypes.TEXT_VTT: - return Class.forName("com.google.android.exoplayer2.text.webvtt.WebvttDecoder"); - case MimeTypes.APPLICATION_TTML: - return Class.forName("com.google.android.exoplayer2.text.ttml.TtmlDecoder"); - case MimeTypes.APPLICATION_MP4VTT: - return Class.forName("com.google.android.exoplayer2.text.webvtt.Mp4WebvttDecoder"); - case MimeTypes.APPLICATION_SUBRIP: - return Class.forName("com.google.android.exoplayer2.text.subrip.SubripDecoder"); - case MimeTypes.APPLICATION_TX3G: - return Class.forName("com.google.android.exoplayer2.text.tx3g.Tx3gDecoder"); - case MimeTypes.APPLICATION_CEA608: - case MimeTypes.APPLICATION_MP4CEA608: - return Class.forName("com.google.android.exoplayer2.text.cea.Cea608Decoder"); - case MimeTypes.APPLICATION_CEA708: - return Class.forName("com.google.android.exoplayer2.text.cea.Cea708Decoder"); - case MimeTypes.APPLICATION_DVBSUBS: - return Class.forName("com.google.android.exoplayer2.text.dvb.DvbDecoder"); - default: - return null; - } - } catch (ClassNotFoundException e) { - return null; } } From 5a6a1af4412c3c48ac05a5498a2b1b53a29b1e0c Mon Sep 17 00:00:00 2001 From: eguven Date: Tue, 11 Apr 2017 08:30:22 -0700 Subject: [PATCH 086/119] Add DashDownloaderService which downloads DASH streams at the background ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=152815185 --- library/dash/build.gradle | 1 + 1 file changed, 1 insertion(+) diff --git a/library/dash/build.gradle b/library/dash/build.gradle index a9a5550219..e608181fab 100644 --- a/library/dash/build.gradle +++ b/library/dash/build.gradle @@ -38,6 +38,7 @@ android { dependencies { compile project(':library-core') compile 'com.android.support:support-annotations:25.2.0' + compile 'com.android.support:support-core-utils:25.2.0' androidTestCompile 'com.google.dexmaker:dexmaker:1.2' androidTestCompile 'com.google.dexmaker:dexmaker-mockito:1.2' androidTestCompile 'org.mockito:mockito-core:1.9.5' From 1a22a4be5f9f77f0eb8bdc40f5ffb9da23ad1b63 Mon Sep 17 00:00:00 2001 From: sxp Date: Tue, 11 Apr 2017 16:37:18 -0700 Subject: [PATCH 087/119] Automated g4 rollback of changelist 152815185. *** Reason for rollback *** The force submit broke GVR: [] *** Original change description *** Add DashDownloaderService which downloads DASH streams at the background *** ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=152877808 --- library/dash/build.gradle | 1 - 1 file changed, 1 deletion(-) diff --git a/library/dash/build.gradle b/library/dash/build.gradle index e608181fab..a9a5550219 100644 --- a/library/dash/build.gradle +++ b/library/dash/build.gradle @@ -38,7 +38,6 @@ android { dependencies { compile project(':library-core') compile 'com.android.support:support-annotations:25.2.0' - compile 'com.android.support:support-core-utils:25.2.0' androidTestCompile 'com.google.dexmaker:dexmaker:1.2' androidTestCompile 'com.google.dexmaker:dexmaker-mockito:1.2' androidTestCompile 'org.mockito:mockito-core:1.9.5' From 0a7011b3904abb2cb8d4d8834d1086da45b8d687 Mon Sep 17 00:00:00 2001 From: tonihei Date: Wed, 12 Apr 2017 06:11:27 -0700 Subject: [PATCH 088/119] Support default style in Tx3g decoder. The track initialization data of Tx3g includes default style values for font styles, colour, and font family. Additionally the decoder now supports vertical subtitle placements other than the Tx3g default of 85% video height. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=152930057 --- .../androidTest/assets/tx3g/initialization | Bin 0 -> 48 bytes .../assets/tx3g/initialization_all_defaults | Bin 0 -> 53 bytes .../androidTest/assets/tx3g/sample_just_text | Bin 0 -> 9 bytes .../assets/tx3g/sample_with_styl_all_defaults | Bin 0 -> 31 bytes .../androidTest/assets/tx3g/sample_with_tbox | Bin 0 -> 25 bytes .../exoplayer2/text/tx3g/Tx3gDecoderTest.java | 107 ++++++++- .../java/com/google/android/exoplayer2/C.java | 10 + .../com/google/android/exoplayer2/Format.java | 14 +- .../exoplayer2/extractor/mp4/AtomParsers.java | 71 ++++-- .../text/SubtitleDecoderFactory.java | 2 +- .../exoplayer2/text/tx3g/Tx3gDecoder.java | 203 +++++++++++++----- 11 files changed, 312 insertions(+), 95 deletions(-) create mode 100644 library/core/src/androidTest/assets/tx3g/initialization create mode 100644 library/core/src/androidTest/assets/tx3g/initialization_all_defaults create mode 100644 library/core/src/androidTest/assets/tx3g/sample_just_text create mode 100644 library/core/src/androidTest/assets/tx3g/sample_with_styl_all_defaults create mode 100644 library/core/src/androidTest/assets/tx3g/sample_with_tbox diff --git a/library/core/src/androidTest/assets/tx3g/initialization b/library/core/src/androidTest/assets/tx3g/initialization new file mode 100644 index 0000000000000000000000000000000000000000..def42b9aded8d1dde23d219c7364edec639b4ffb GIT binary patch literal 48 tcmY#jU|?YU4yN)ST{JeC^HQJ)Y%Hs literal 0 HcmV?d00001 diff --git a/library/core/src/androidTest/assets/tx3g/sample_just_text b/library/core/src/androidTest/assets/tx3g/sample_just_text new file mode 100644 index 0000000000000000000000000000000000000000..68561eca7e22ab4f59ed0d7916e6315318720a15 GIT binary patch literal 9 QcmZQzcXn0?Ni8k`00emptyList()); byte[] bytes = TestUtil.getByteArray(getInstrumentation(), NO_SUBTITLE); Subtitle subtitle = decoder.decode(bytes, bytes.length, false); assertTrue(subtitle.getCues(0).isEmpty()); } + public void testDecodeJustText() throws IOException, SubtitleDecoderException { + Tx3gDecoder decoder = new Tx3gDecoder(Collections.emptyList()); + byte[] bytes = TestUtil.getByteArray(getInstrumentation(), SAMPLE_JUST_TEXT); + Subtitle subtitle = decoder.decode(bytes, bytes.length, false); + SpannedString text = new SpannedString(subtitle.getCues(0).get(0).text); + assertEquals("CC Test", text.toString()); + assertEquals(0, text.getSpans(0, text.length(), Object.class).length); + assertFractionalLinePosition(subtitle.getCues(0).get(0), 0.85f); + } + public void testDecodeWithStyl() throws IOException, SubtitleDecoderException { - Tx3gDecoder decoder = new Tx3gDecoder(); + Tx3gDecoder decoder = new Tx3gDecoder(Collections.emptyList()); byte[] bytes = TestUtil.getByteArray(getInstrumentation(), SAMPLE_WITH_STYL); Subtitle subtitle = decoder.decode(bytes, bytes.length, false); SpannedString text = new SpannedString(subtitle.getCues(0).get(0).text); @@ -58,28 +77,41 @@ public final class Tx3gDecoderTest extends InstrumentationTestCase { findSpan(text, 0, 6, UnderlineSpan.class); ForegroundColorSpan colorSpan = findSpan(text, 0, 6, ForegroundColorSpan.class); assertEquals(Color.GREEN, colorSpan.getForegroundColor()); + assertFractionalLinePosition(subtitle.getCues(0).get(0), 0.85f); + } + + public void testDecodeWithStylAllDefaults() throws IOException, SubtitleDecoderException { + Tx3gDecoder decoder = new Tx3gDecoder(Collections.emptyList()); + byte[] bytes = TestUtil.getByteArray(getInstrumentation(), SAMPLE_WITH_STYL_ALL_DEFAULTS); + Subtitle subtitle = decoder.decode(bytes, bytes.length, false); + SpannedString text = new SpannedString(subtitle.getCues(0).get(0).text); + assertEquals("CC Test", text.toString()); + assertEquals(0, text.getSpans(0, text.length(), Object.class).length); + assertFractionalLinePosition(subtitle.getCues(0).get(0), 0.85f); } public void testDecodeUtf16BeNoStyl() throws IOException, SubtitleDecoderException { - Tx3gDecoder decoder = new Tx3gDecoder(); + Tx3gDecoder decoder = new Tx3gDecoder(Collections.emptyList()); byte[] bytes = TestUtil.getByteArray(getInstrumentation(), SAMPLE_UTF16_BE_NO_STYL); Subtitle subtitle = decoder.decode(bytes, bytes.length, false); SpannedString text = new SpannedString(subtitle.getCues(0).get(0).text); assertEquals("你好", text.toString()); assertEquals(0, text.getSpans(0, text.length(), Object.class).length); + assertFractionalLinePosition(subtitle.getCues(0).get(0), 0.85f); } public void testDecodeUtf16LeNoStyl() throws IOException, SubtitleDecoderException { - Tx3gDecoder decoder = new Tx3gDecoder(); + Tx3gDecoder decoder = new Tx3gDecoder(Collections.emptyList()); byte[] bytes = TestUtil.getByteArray(getInstrumentation(), SAMPLE_UTF16_LE_NO_STYL); Subtitle subtitle = decoder.decode(bytes, bytes.length, false); SpannedString text = new SpannedString(subtitle.getCues(0).get(0).text); assertEquals("你好", text.toString()); assertEquals(0, text.getSpans(0, text.length(), Object.class).length); + assertFractionalLinePosition(subtitle.getCues(0).get(0), 0.85f); } public void testDecodeWithMultipleStyl() throws IOException, SubtitleDecoderException { - Tx3gDecoder decoder = new Tx3gDecoder(); + Tx3gDecoder decoder = new Tx3gDecoder(Collections.emptyList()); byte[] bytes = TestUtil.getByteArray(getInstrumentation(), SAMPLE_WITH_MULTIPLE_STYL); Subtitle subtitle = decoder.decode(bytes, bytes.length, false); SpannedString text = new SpannedString(subtitle.getCues(0).get(0).text); @@ -92,10 +124,11 @@ public final class Tx3gDecoderTest extends InstrumentationTestCase { assertEquals(Color.GREEN, colorSpan.getForegroundColor()); colorSpan = findSpan(text, 7, 12, ForegroundColorSpan.class); assertEquals(Color.GREEN, colorSpan.getForegroundColor()); + assertFractionalLinePosition(subtitle.getCues(0).get(0), 0.85f); } public void testDecodeWithOtherExtension() throws IOException, SubtitleDecoderException { - Tx3gDecoder decoder = new Tx3gDecoder(); + Tx3gDecoder decoder = new Tx3gDecoder(Collections.emptyList()); byte[] bytes = TestUtil.getByteArray(getInstrumentation(), SAMPLE_WITH_OTHER_EXTENSION); Subtitle subtitle = decoder.decode(bytes, bytes.length, false); SpannedString text = new SpannedString(subtitle.getCues(0).get(0).text); @@ -105,6 +138,62 @@ public final class Tx3gDecoderTest extends InstrumentationTestCase { assertEquals(Typeface.BOLD, styleSpan.getStyle()); ForegroundColorSpan colorSpan = findSpan(text, 0, 6, ForegroundColorSpan.class); assertEquals(Color.GREEN, colorSpan.getForegroundColor()); + assertFractionalLinePosition(subtitle.getCues(0).get(0), 0.85f); + } + + public void testInitializationDecodeWithStyl() throws IOException, SubtitleDecoderException { + byte[] initBytes = TestUtil.getByteArray(getInstrumentation(), INITIALIZATION); + Tx3gDecoder decoder = new Tx3gDecoder(Collections.singletonList(initBytes)); + byte[] bytes = TestUtil.getByteArray(getInstrumentation(), SAMPLE_WITH_STYL); + Subtitle subtitle = decoder.decode(bytes, bytes.length, false); + SpannedString text = new SpannedString(subtitle.getCues(0).get(0).text); + assertEquals("CC Test", text.toString()); + assertEquals(5, text.getSpans(0, text.length(), Object.class).length); + StyleSpan styleSpan = findSpan(text, 0, text.length(), StyleSpan.class); + assertEquals(Typeface.BOLD_ITALIC, styleSpan.getStyle()); + findSpan(text, 0, text.length(), UnderlineSpan.class); + TypefaceSpan typefaceSpan = findSpan(text, 0, text.length(), TypefaceSpan.class); + assertEquals(C.SERIF_NAME, typefaceSpan.getFamily()); + ForegroundColorSpan colorSpan = findSpan(text, 0, text.length(), ForegroundColorSpan.class); + assertEquals(Color.RED, colorSpan.getForegroundColor()); + colorSpan = findSpan(text, 0, 6, ForegroundColorSpan.class); + assertEquals(Color.GREEN, colorSpan.getForegroundColor()); + assertFractionalLinePosition(subtitle.getCues(0).get(0), 0.1f); + } + + public void testInitializationDecodeWithTbox() throws IOException, SubtitleDecoderException { + byte[] initBytes = TestUtil.getByteArray(getInstrumentation(), INITIALIZATION); + Tx3gDecoder decoder = new Tx3gDecoder(Collections.singletonList(initBytes)); + byte[] bytes = TestUtil.getByteArray(getInstrumentation(), SAMPLE_WITH_TBOX); + Subtitle subtitle = decoder.decode(bytes, bytes.length, false); + SpannedString text = new SpannedString(subtitle.getCues(0).get(0).text); + assertEquals("CC Test", text.toString()); + assertEquals(4, text.getSpans(0, text.length(), Object.class).length); + StyleSpan styleSpan = findSpan(text, 0, text.length(), StyleSpan.class); + assertEquals(Typeface.BOLD_ITALIC, styleSpan.getStyle()); + findSpan(text, 0, text.length(), UnderlineSpan.class); + TypefaceSpan typefaceSpan = findSpan(text, 0, text.length(), TypefaceSpan.class); + assertEquals(C.SERIF_NAME, typefaceSpan.getFamily()); + ForegroundColorSpan colorSpan = findSpan(text, 0, text.length(), ForegroundColorSpan.class); + assertEquals(Color.RED, colorSpan.getForegroundColor()); + assertFractionalLinePosition(subtitle.getCues(0).get(0), 0.1875f); + } + + public void testInitializationAllDefaultsDecodeWithStyl() throws IOException, + SubtitleDecoderException { + byte[] initBytes = TestUtil.getByteArray(getInstrumentation(), INITIALIZATION_ALL_DEFAULTS); + Tx3gDecoder decoder = new Tx3gDecoder(Collections.singletonList(initBytes)); + byte[] bytes = TestUtil.getByteArray(getInstrumentation(), SAMPLE_WITH_STYL); + Subtitle subtitle = decoder.decode(bytes, bytes.length, false); + SpannedString text = new SpannedString(subtitle.getCues(0).get(0).text); + assertEquals("CC Test", text.toString()); + assertEquals(3, text.getSpans(0, text.length(), Object.class).length); + StyleSpan styleSpan = findSpan(text, 0, 6, StyleSpan.class); + assertEquals(Typeface.BOLD_ITALIC, styleSpan.getStyle()); + findSpan(text, 0, 6, UnderlineSpan.class); + ForegroundColorSpan colorSpan = findSpan(text, 0, 6, ForegroundColorSpan.class); + assertEquals(Color.GREEN, colorSpan.getForegroundColor()); + assertFractionalLinePosition(subtitle.getCues(0).get(0), 0.85f); } private static T findSpan(SpannedString testObject, int expectedStart, int expectedEnd, @@ -120,4 +209,10 @@ public final class Tx3gDecoderTest extends InstrumentationTestCase { return null; } + private static void assertFractionalLinePosition(Cue cue, float expectedFraction) { + assertEquals(Cue.LINE_TYPE_FRACTION, cue.lineType); + assertEquals(Cue.ANCHOR_TYPE_START, cue.lineAnchor); + assertTrue(Math.abs(expectedFraction - cue.line) < 1e-6); + } + } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/C.java b/library/core/src/main/java/com/google/android/exoplayer2/C.java index 56092f17a9..6fb3c4500e 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/C.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/C.java @@ -81,6 +81,16 @@ public final class C { */ public static final String UTF16_NAME = "UTF-16"; + /** + * * The name of the serif font family. + */ + public static final String SERIF_NAME = "serif"; + + /** + * * The name of the sans-serif font family. + */ + public static final String SANS_SERIF_NAME = "sans-serif"; + /** * Crypto modes for a codec. */ diff --git a/library/core/src/main/java/com/google/android/exoplayer2/Format.java b/library/core/src/main/java/com/google/android/exoplayer2/Format.java index 866e512288..c29c8fbd83 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/Format.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/Format.java @@ -283,30 +283,31 @@ public final class Format implements Parcelable { public static Format createTextSampleFormat(String id, String sampleMimeType, String codecs, int bitrate, @C.SelectionFlags int selectionFlags, String language, DrmInitData drmInitData) { return createTextSampleFormat(id, sampleMimeType, codecs, bitrate, selectionFlags, language, - NO_VALUE, drmInitData, OFFSET_SAMPLE_RELATIVE); + NO_VALUE, drmInitData, OFFSET_SAMPLE_RELATIVE, Collections.emptyList()); } public static Format createTextSampleFormat(String id, String sampleMimeType, String codecs, int bitrate, @C.SelectionFlags int selectionFlags, String language, int accessibilityChannel, DrmInitData drmInitData) { return createTextSampleFormat(id, sampleMimeType, codecs, bitrate, selectionFlags, language, - accessibilityChannel, drmInitData, OFFSET_SAMPLE_RELATIVE); + accessibilityChannel, drmInitData, OFFSET_SAMPLE_RELATIVE, Collections.emptyList()); } public static Format createTextSampleFormat(String id, String sampleMimeType, String codecs, int bitrate, @C.SelectionFlags int selectionFlags, String language, DrmInitData drmInitData, long subsampleOffsetUs) { return createTextSampleFormat(id, sampleMimeType, codecs, bitrate, selectionFlags, language, - NO_VALUE, drmInitData, subsampleOffsetUs); + NO_VALUE, drmInitData, subsampleOffsetUs, Collections.emptyList()); } public static Format createTextSampleFormat(String id, String sampleMimeType, String codecs, int bitrate, @C.SelectionFlags int selectionFlags, String language, - int accessibilityChannel, DrmInitData drmInitData, long subsampleOffsetUs) { + int accessibilityChannel, DrmInitData drmInitData, long subsampleOffsetUs, + List initializationData) { return new Format(id, null, sampleMimeType, codecs, bitrate, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, null, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, - NO_VALUE, selectionFlags, language, accessibilityChannel, subsampleOffsetUs, null, - drmInitData, null); + NO_VALUE, selectionFlags, language, accessibilityChannel, subsampleOffsetUs, + initializationData, drmInitData, null); } // Image. @@ -438,6 +439,7 @@ public final class Format implements Parcelable { drmInitData, metadata); } + @SuppressWarnings("ReferenceEquality") public Format copyWithManifestFormatInfo(Format manifestFormat) { if (this == manifestFormat) { // No need to copy from ourselves. diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/AtomParsers.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/AtomParsers.java index 54141f2545..4b30b383ec 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/AtomParsers.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/AtomParsers.java @@ -611,24 +611,11 @@ import java.util.List; || childAtomType == Atom.TYPE__mp3 || childAtomType == Atom.TYPE_alac) { parseAudioSampleEntry(stsd, childAtomType, childStartPosition, childAtomSize, trackId, language, isQuickTime, drmInitData, out, i); - } else if (childAtomType == Atom.TYPE_TTML) { - out.format = Format.createTextSampleFormat(Integer.toString(trackId), - MimeTypes.APPLICATION_TTML, null, Format.NO_VALUE, 0, language, drmInitData); - } else if (childAtomType == Atom.TYPE_tx3g) { - out.format = Format.createTextSampleFormat(Integer.toString(trackId), - MimeTypes.APPLICATION_TX3G, null, Format.NO_VALUE, 0, language, drmInitData); - } else if (childAtomType == Atom.TYPE_wvtt) { - out.format = Format.createTextSampleFormat(Integer.toString(trackId), - MimeTypes.APPLICATION_MP4VTT, null, Format.NO_VALUE, 0, language, drmInitData); - } else if (childAtomType == Atom.TYPE_stpp) { - out.format = Format.createTextSampleFormat(Integer.toString(trackId), - MimeTypes.APPLICATION_TTML, null, Format.NO_VALUE, 0, language, drmInitData, - 0 /* subsample timing is absolute */); - } else if (childAtomType == Atom.TYPE_c608) { - // Defined by the QuickTime File Format specification. - out.format = Format.createTextSampleFormat(Integer.toString(trackId), - MimeTypes.APPLICATION_MP4CEA608, null, Format.NO_VALUE, 0, language, drmInitData); - out.requiredSampleTransformation = Track.TRANSFORMATION_CEA608_CDAT; + } else if (childAtomType == Atom.TYPE_TTML || childAtomType == Atom.TYPE_tx3g + || childAtomType == Atom.TYPE_wvtt || childAtomType == Atom.TYPE_stpp + || childAtomType == Atom.TYPE_c608) { + parseTextSampleEntry(stsd, childAtomType, childStartPosition, childAtomSize, trackId, + language, drmInitData, out); } else if (childAtomType == Atom.TYPE_camm) { out.format = Format.createSampleFormat(Integer.toString(trackId), MimeTypes.APPLICATION_CAMERA_MOTION, null, Format.NO_VALUE, drmInitData); @@ -638,12 +625,49 @@ import java.util.List; return out; } + private static void parseTextSampleEntry(ParsableByteArray parent, int atomType, int position, + int atomSize, int trackId, String language, DrmInitData drmInitData, StsdData out) + throws ParserException { + parent.setPosition(position + Atom.HEADER_SIZE + StsdData.STSD_HEADER_SIZE); + + // Default values. + List initializationData = null; + long subsampleOffsetUs = Format.OFFSET_SAMPLE_RELATIVE; + + String mimeType; + if (atomType == Atom.TYPE_TTML) { + mimeType = MimeTypes.APPLICATION_TTML; + } else if (atomType == Atom.TYPE_tx3g) { + mimeType = MimeTypes.APPLICATION_TX3G; + int sampleDescriptionLength = atomSize - Atom.HEADER_SIZE - 8; + byte[] sampleDescriptionData = new byte[sampleDescriptionLength]; + parent.readBytes(sampleDescriptionData, 0, sampleDescriptionLength); + initializationData = Collections.singletonList(sampleDescriptionData); + } else if (atomType == Atom.TYPE_wvtt) { + mimeType = MimeTypes.APPLICATION_MP4VTT; + } else if (atomType == Atom.TYPE_stpp) { + mimeType = MimeTypes.APPLICATION_TTML; + subsampleOffsetUs = 0; // Subsample timing is absolute. + } else if (atomType == Atom.TYPE_c608) { + // Defined by the QuickTime File Format specification. + mimeType = MimeTypes.APPLICATION_MP4CEA608; + out.requiredSampleTransformation = Track.TRANSFORMATION_CEA608_CDAT; + } else { + // Never happens. + throw new IllegalStateException(); + } + + out.format = Format.createTextSampleFormat(Integer.toString(trackId), mimeType, null, + Format.NO_VALUE, 0, language, Format.NO_VALUE, drmInitData, subsampleOffsetUs, + initializationData); + } + private static void parseVideoSampleEntry(ParsableByteArray parent, int atomType, int position, int size, int trackId, int rotationDegrees, DrmInitData drmInitData, StsdData out, int entryIndex) throws ParserException { - parent.setPosition(position + Atom.HEADER_SIZE); + parent.setPosition(position + Atom.HEADER_SIZE + StsdData.STSD_HEADER_SIZE); - parent.skipBytes(24); + parent.skipBytes(16); int width = parent.readUnsignedShort(); int height = parent.readUnsignedShort(); boolean pixelWidthHeightRatioFromPasp = false; @@ -784,15 +808,14 @@ import java.util.List; private static void parseAudioSampleEntry(ParsableByteArray parent, int atomType, int position, int size, int trackId, String language, boolean isQuickTime, DrmInitData drmInitData, StsdData out, int entryIndex) { - parent.setPosition(position + Atom.HEADER_SIZE); + parent.setPosition(position + Atom.HEADER_SIZE + StsdData.STSD_HEADER_SIZE); int quickTimeSoundDescriptionVersion = 0; if (isQuickTime) { - parent.skipBytes(8); quickTimeSoundDescriptionVersion = parent.readUnsignedShort(); parent.skipBytes(6); } else { - parent.skipBytes(16); + parent.skipBytes(8); } int channelCount; @@ -1177,6 +1200,8 @@ import java.util.List; */ private static final class StsdData { + public static final int STSD_HEADER_SIZE = 8; + public final TrackEncryptionBox[] trackEncryptionBoxes; public Format format; diff --git a/library/core/src/main/java/com/google/android/exoplayer2/text/SubtitleDecoderFactory.java b/library/core/src/main/java/com/google/android/exoplayer2/text/SubtitleDecoderFactory.java index f65d5a6e55..795189e1a6 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/text/SubtitleDecoderFactory.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/text/SubtitleDecoderFactory.java @@ -92,7 +92,7 @@ public interface SubtitleDecoderFactory { case MimeTypes.APPLICATION_SUBRIP: return new SubripDecoder(); case MimeTypes.APPLICATION_TX3G: - return new Tx3gDecoder(); + return new Tx3gDecoder(format.initializationData); case MimeTypes.APPLICATION_CEA608: case MimeTypes.APPLICATION_MP4CEA608: return new Cea608Decoder(format.sampleMimeType, format.accessibilityChannel); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/text/tx3g/Tx3gDecoder.java b/library/core/src/main/java/com/google/android/exoplayer2/text/tx3g/Tx3gDecoder.java index 79ec89838a..2270ccc632 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/text/tx3g/Tx3gDecoder.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/text/tx3g/Tx3gDecoder.java @@ -15,26 +15,28 @@ */ package com.google.android.exoplayer2.text.tx3g; +import android.graphics.Color; import android.graphics.Typeface; import android.text.SpannableStringBuilder; import android.text.Spanned; import android.text.style.ForegroundColorSpan; import android.text.style.StyleSpan; +import android.text.style.TypefaceSpan; import android.text.style.UnderlineSpan; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.text.Cue; import com.google.android.exoplayer2.text.SimpleSubtitleDecoder; import com.google.android.exoplayer2.text.Subtitle; import com.google.android.exoplayer2.text.SubtitleDecoderException; -import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.ParsableByteArray; import com.google.android.exoplayer2.util.Util; import java.nio.charset.Charset; +import java.util.List; /** * A {@link SimpleSubtitleDecoder} for tx3g. *

    - * Currently only supports parsing of a single text track. + * Currently supports parsing of a single text track with embedded styles. */ public final class Tx3gDecoder extends SimpleSubtitleDecoder { @@ -42,6 +44,8 @@ public final class Tx3gDecoder extends SimpleSubtitleDecoder { private static final char BOM_UTF16_LE = '\uFFFE'; private static final int TYPE_STYL = Util.getIntegerCodeForString("styl"); + private static final int TYPE_TBOX = Util.getIntegerCodeForString("tbox"); + private static final String TX3G_SERIF = "Serif"; private static final int SIZE_ATOM_HEADER = 8; private static final int SIZE_SHORT = 2; @@ -52,44 +56,107 @@ public final class Tx3gDecoder extends SimpleSubtitleDecoder { private static final int FONT_FACE_ITALIC = 0x0002; private static final int FONT_FACE_UNDERLINE = 0x0004; - private final ParsableByteArray parsableByteArray; + private static final int SPAN_PRIORITY_LOW = (0xFF << Spanned.SPAN_PRIORITY_SHIFT); + private static final int SPAN_PRIORITY_HIGH = (0x00 << Spanned.SPAN_PRIORITY_SHIFT); - public Tx3gDecoder() { + private static final int DEFAULT_FONT_FACE = 0; + private static final int DEFAULT_COLOR = Color.WHITE; + private static final String DEFAULT_FONT_FAMILY = C.SANS_SERIF_NAME; + private static final float DEFAULT_VERTICAL_PLACEMENT = 0.85f; + + private final ParsableByteArray parsableByteArray; + private boolean customVerticalPlacement; + private int defaultFontFace; + private int defaultColorRgba; + private String defaultFontFamily; + private float defaultVerticalPlacement; + private int calculatedVideoTrackHeight; + + /** + * Sets up a new {@link Tx3gDecoder} with default values. + * + * @param initializationData Sample description atom ('stsd') data with default subtitle styles. + */ + public Tx3gDecoder(List initializationData) { super("Tx3gDecoder"); parsableByteArray = new ParsableByteArray(); + decodeInitializationData(initializationData); + } + + private void decodeInitializationData(List initializationData) { + if (initializationData != null && initializationData.size() == 1 + && (initializationData.get(0).length == 48 || initializationData.get(0).length == 53)) { + byte[] initializationBytes = initializationData.get(0); + defaultFontFace = initializationBytes[24]; + defaultColorRgba = ((initializationBytes[26] & 0xFF) << 24) + | ((initializationBytes[27] & 0xFF) << 16) + | ((initializationBytes[28] & 0xFF) << 8) + | (initializationBytes[29] & 0xFF); + String fontFamily = new String(initializationBytes, 43, initializationBytes.length - 43); + defaultFontFamily = TX3G_SERIF.equals(fontFamily) ? C.SERIF_NAME : C.SANS_SERIF_NAME; + //font size (initializationBytes[25]) is 5% of video height + calculatedVideoTrackHeight = 20 * initializationBytes[25]; + customVerticalPlacement = (initializationBytes[0] & 0x20) != 0; + if (customVerticalPlacement) { + int requestedVerticalPlacement = ((initializationBytes[10] & 0xFF) << 8) + | (initializationBytes[11] & 0xFF); + defaultVerticalPlacement = (float) requestedVerticalPlacement / calculatedVideoTrackHeight; + defaultVerticalPlacement = Util.constrainValue(defaultVerticalPlacement, 0.0f, 0.95f); + } else { + defaultVerticalPlacement = DEFAULT_VERTICAL_PLACEMENT; + } + } else { + defaultFontFace = DEFAULT_FONT_FACE; + defaultColorRgba = DEFAULT_COLOR; + defaultFontFamily = DEFAULT_FONT_FAMILY; + customVerticalPlacement = false; + defaultVerticalPlacement = DEFAULT_VERTICAL_PLACEMENT; + } } @Override protected Subtitle decode(byte[] bytes, int length, boolean reset) throws SubtitleDecoderException { - try { - parsableByteArray.reset(bytes, length); - String cueTextString = readSubtitleText(parsableByteArray); - if (cueTextString.isEmpty()) { - return Tx3gSubtitle.EMPTY; - } - SpannableStringBuilder cueText = new SpannableStringBuilder(cueTextString); - while (parsableByteArray.bytesLeft() >= SIZE_ATOM_HEADER) { - int atomSize = parsableByteArray.readInt(); - int atomType = parsableByteArray.readInt(); - if (atomType == TYPE_STYL) { - Assertions.checkArgument(parsableByteArray.bytesLeft() >= SIZE_SHORT); - int styleRecordCount = parsableByteArray.readUnsignedShort(); - for (int i = 0; i < styleRecordCount; i++) { - applyStyleRecord(parsableByteArray, cueText); - } - } else { - parsableByteArray.skipBytes(atomSize - SIZE_ATOM_HEADER); - } - } - return new Tx3gSubtitle(new Cue(cueText)); - } catch (IllegalArgumentException e) { - throw new SubtitleDecoderException("Unexpected subtitle format.", e); + parsableByteArray.reset(bytes, length); + String cueTextString = readSubtitleText(parsableByteArray); + if (cueTextString.isEmpty()) { + return Tx3gSubtitle.EMPTY; } + // Attach default styles. + SpannableStringBuilder cueText = new SpannableStringBuilder(cueTextString); + attachFontFace(cueText, defaultFontFace, DEFAULT_FONT_FACE, 0, cueText.length(), + SPAN_PRIORITY_LOW); + attachColor(cueText, defaultColorRgba, DEFAULT_COLOR, 0, cueText.length(), + SPAN_PRIORITY_LOW); + attachFontFamily(cueText, defaultFontFamily, DEFAULT_FONT_FAMILY, 0, cueText.length(), + SPAN_PRIORITY_LOW); + float verticalPlacement = defaultVerticalPlacement; + // Find and attach additional styles. + while (parsableByteArray.bytesLeft() >= SIZE_ATOM_HEADER) { + int position = parsableByteArray.getPosition(); + int atomSize = parsableByteArray.readInt(); + int atomType = parsableByteArray.readInt(); + if (atomType == TYPE_STYL) { + assertTrue(parsableByteArray.bytesLeft() >= SIZE_SHORT); + int styleRecordCount = parsableByteArray.readUnsignedShort(); + for (int i = 0; i < styleRecordCount; i++) { + applyStyleRecord(parsableByteArray, cueText); + } + } else if (atomType == TYPE_TBOX && customVerticalPlacement) { + assertTrue(parsableByteArray.bytesLeft() >= SIZE_SHORT); + int requestedVerticalPlacement = parsableByteArray.readUnsignedShort(); + verticalPlacement = (float) requestedVerticalPlacement / calculatedVideoTrackHeight; + verticalPlacement = Util.constrainValue(verticalPlacement, 0.0f, 0.95f); + } + parsableByteArray.setPosition(position + atomSize); + } + return new Tx3gSubtitle(new Cue(cueText, null, verticalPlacement, Cue.LINE_TYPE_FRACTION, + Cue.ANCHOR_TYPE_START, Cue.DIMEN_UNSET, Cue.TYPE_UNSET, Cue.DIMEN_UNSET)); } - private static String readSubtitleText(ParsableByteArray parsableByteArray) { - Assertions.checkArgument(parsableByteArray.bytesLeft() >= SIZE_SHORT); + private static String readSubtitleText(ParsableByteArray parsableByteArray) + throws SubtitleDecoderException { + assertTrue(parsableByteArray.bytesLeft() >= SIZE_SHORT); int textLength = parsableByteArray.readUnsignedShort(); if (textLength == 0) { return ""; @@ -103,47 +170,65 @@ public final class Tx3gDecoder extends SimpleSubtitleDecoder { return parsableByteArray.readString(textLength, Charset.forName(C.UTF8_NAME)); } - private static void applyStyleRecord(ParsableByteArray parsableByteArray, - SpannableStringBuilder cueText) { - Assertions.checkArgument(parsableByteArray.bytesLeft() >= SIZE_STYLE_RECORD); + private void applyStyleRecord(ParsableByteArray parsableByteArray, + SpannableStringBuilder cueText) throws SubtitleDecoderException { + assertTrue(parsableByteArray.bytesLeft() >= SIZE_STYLE_RECORD); int start = parsableByteArray.readUnsignedShort(); int end = parsableByteArray.readUnsignedShort(); parsableByteArray.skipBytes(2); // font identifier int fontFace = parsableByteArray.readUnsignedByte(); parsableByteArray.skipBytes(1); // font size int colorRgba = parsableByteArray.readInt(); - - if (fontFace != 0) { - attachFontFace(cueText, fontFace, start, end); - } - attachColor(cueText, colorRgba, start, end); + attachFontFace(cueText, fontFace, defaultFontFace, start, end, SPAN_PRIORITY_HIGH); + attachColor(cueText, colorRgba, defaultColorRgba, start, end, SPAN_PRIORITY_HIGH); } - private static void attachFontFace(SpannableStringBuilder cueText, int fontFace, int start, - int end) { - boolean isBold = (fontFace & FONT_FACE_BOLD) != 0; - boolean isItalic = (fontFace & FONT_FACE_ITALIC) != 0; - if (isBold) { - if (isItalic) { - cueText.setSpan(new StyleSpan(Typeface.BOLD_ITALIC), start, end, - Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); - } else { - cueText.setSpan(new StyleSpan(Typeface.BOLD), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + private static void attachFontFace(SpannableStringBuilder cueText, int fontFace, + int defaultFontFace, int start, int end, int spanPriority) { + if (fontFace != defaultFontFace) { + final int flags = Spanned.SPAN_EXCLUSIVE_EXCLUSIVE | spanPriority; + boolean isBold = (fontFace & FONT_FACE_BOLD) != 0; + boolean isItalic = (fontFace & FONT_FACE_ITALIC) != 0; + if (isBold) { + if (isItalic) { + cueText.setSpan(new StyleSpan(Typeface.BOLD_ITALIC), start, end, flags); + } else { + cueText.setSpan(new StyleSpan(Typeface.BOLD), start, end, flags); + } + } else if (isItalic) { + cueText.setSpan(new StyleSpan(Typeface.ITALIC), start, end, flags); + } + boolean isUnderlined = (fontFace & FONT_FACE_UNDERLINE) != 0; + if (isUnderlined) { + cueText.setSpan(new UnderlineSpan(), start, end, flags); + } + if (!isUnderlined && !isBold && !isItalic) { + cueText.setSpan(new StyleSpan(Typeface.NORMAL), start, end, flags); } - } else if (isItalic) { - cueText.setSpan(new StyleSpan(Typeface.ITALIC), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); - } - - boolean isUnderlined = (fontFace & FONT_FACE_UNDERLINE) != 0; - if (isUnderlined) { - cueText.setSpan(new UnderlineSpan(), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); } } - private static void attachColor(SpannableStringBuilder cueText, int colorRgba, int start, - int end) { - int colorArgb = ((colorRgba & 0xFF) << 24) | (colorRgba >>> 8); - cueText.setSpan(new ForegroundColorSpan(colorArgb), start, end, - Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + private static void attachColor(SpannableStringBuilder cueText, int colorRgba, + int defaultColorRgba, int start, int end, int spanPriority) { + if (colorRgba != defaultColorRgba) { + int colorArgb = ((colorRgba & 0xFF) << 24) | (colorRgba >>> 8); + cueText.setSpan(new ForegroundColorSpan(colorArgb), start, end, + Spanned.SPAN_EXCLUSIVE_EXCLUSIVE | spanPriority); + } + } + + @SuppressWarnings("ReferenceEquality") + private static void attachFontFamily(SpannableStringBuilder cueText, String fontFamily, + String defaultFontFamily, int start, int end, int spanPriority) { + if (fontFamily != defaultFontFamily) { + cueText.setSpan(new TypefaceSpan(fontFamily), start, end, + Spanned.SPAN_EXCLUSIVE_EXCLUSIVE | spanPriority); + } + } + + private static void assertTrue(boolean checkValue) throws SubtitleDecoderException { + if (!checkValue) { + throw new SubtitleDecoderException("Unexpected subtitle format."); + } } } From 12433ad146693b3d0816a1f9bd88437196bb0c73 Mon Sep 17 00:00:00 2001 From: olly Date: Wed, 12 Apr 2017 08:06:31 -0700 Subject: [PATCH 089/119] Support intercepting setPlayWhenReady calls Issue: #2454 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=152938743 --- .../exoplayer2/ui/PlaybackControlView.java | 65 +++++++++++++------ .../exoplayer2/ui/SimpleExoPlayerView.java | 12 ++-- 2 files changed, 50 insertions(+), 27 deletions(-) diff --git a/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlaybackControlView.java b/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlaybackControlView.java index ab5a8d4c97..ff0e92add5 100644 --- a/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlaybackControlView.java +++ b/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlaybackControlView.java @@ -161,28 +161,50 @@ public class PlaybackControlView extends FrameLayout { } /** - * Dispatches seek operations to the player. + * Dispatches operations to the player. + *

    + * Implementations may choose to suppress (e.g. prevent playback from resuming if audio focus is + * denied) or modify (e.g. change the seek position to prevent a user from seeking past a + * non-skippable advert) operations. */ - public interface SeekDispatcher { + public interface ControlDispatcher { /** - * @param player The player to seek. + * Dispatches a {@link ExoPlayer#setPlayWhenReady(boolean)} operation. + * + * @param player The player to which the operation should be dispatched. + * @param playWhenReady Whether playback should proceed when ready. + * @return True if the operation was dispatched. False if suppressed. + */ + boolean dispatchSetPlayWhenReady(ExoPlayer player, boolean playWhenReady); + + /** + * Dispatches a {@link ExoPlayer#seekTo(int, long)} operation. + * + * @param player The player to which the operation should be dispatched. * @param windowIndex The index of the window. * @param positionMs The seek position in the specified window, or {@link C#TIME_UNSET} to seek * to the window's default position. - * @return True if the seek was dispatched. False otherwise. + * @return True if the operation was dispatched. False if suppressed. */ - boolean dispatchSeek(ExoPlayer player, int windowIndex, long positionMs); + boolean dispatchSeekTo(ExoPlayer player, int windowIndex, long positionMs); } /** - * Default {@link SeekDispatcher} that dispatches seeks to the player without modification. + * Default {@link ControlDispatcher} that dispatches operations to the player without + * modification. */ - public static final SeekDispatcher DEFAULT_SEEK_DISPATCHER = new SeekDispatcher() { + public static final ControlDispatcher DEFAULT_CONTROL_DISPATCHER = new ControlDispatcher() { @Override - public boolean dispatchSeek(ExoPlayer player, int windowIndex, long positionMs) { + public boolean dispatchSetPlayWhenReady(ExoPlayer player, boolean playWhenReady) { + player.setPlayWhenReady(playWhenReady); + return true; + } + + @Override + public boolean dispatchSeekTo(ExoPlayer player, int windowIndex, long positionMs) { player.seekTo(windowIndex, positionMs); return true; } @@ -216,7 +238,7 @@ public class PlaybackControlView extends FrameLayout { private final Timeline.Window window; private ExoPlayer player; - private SeekDispatcher seekDispatcher; + private ControlDispatcher controlDispatcher; private VisibilityListener visibilityListener; private boolean isAttachedToWindow; @@ -278,7 +300,7 @@ public class PlaybackControlView extends FrameLayout { formatter = new Formatter(formatBuilder, Locale.getDefault()); adBreakTimesMs = new long[0]; componentListener = new ComponentListener(); - seekDispatcher = DEFAULT_SEEK_DISPATCHER; + controlDispatcher = DEFAULT_CONTROL_DISPATCHER; LayoutInflater.from(context).inflate(controllerLayoutId, this); setDescendantFocusability(FOCUS_AFTER_DESCENDANTS); @@ -364,13 +386,14 @@ public class PlaybackControlView extends FrameLayout { } /** - * Sets the {@link SeekDispatcher}. + * Sets the {@link ControlDispatcher}. * - * @param seekDispatcher The {@link SeekDispatcher}, or null to use - * {@link #DEFAULT_SEEK_DISPATCHER}. + * @param controlDispatcher The {@link ControlDispatcher}, or null to use + * {@link #DEFAULT_CONTROL_DISPATCHER}. */ - public void setSeekDispatcher(SeekDispatcher seekDispatcher) { - this.seekDispatcher = seekDispatcher == null ? DEFAULT_SEEK_DISPATCHER : seekDispatcher; + public void setControlDispatcher(ControlDispatcher controlDispatcher) { + this.controlDispatcher = controlDispatcher == null ? DEFAULT_CONTROL_DISPATCHER + : controlDispatcher; } /** @@ -697,7 +720,7 @@ public class PlaybackControlView extends FrameLayout { } private void seekTo(int windowIndex, long positionMs) { - boolean dispatched = seekDispatcher.dispatchSeek(player, windowIndex, positionMs); + boolean dispatched = controlDispatcher.dispatchSeekTo(player, windowIndex, positionMs); if (!dispatched) { // The seek wasn't dispatched. If the progress bar was dragged by the user to perform the // seek then it'll now be in the wrong position. Trigger a progress update to snap it back. @@ -758,13 +781,13 @@ public class PlaybackControlView extends FrameLayout { rewind(); break; case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE: - player.setPlayWhenReady(!player.getPlayWhenReady()); + controlDispatcher.dispatchSetPlayWhenReady(player, !player.getPlayWhenReady()); break; case KeyEvent.KEYCODE_MEDIA_PLAY: - player.setPlayWhenReady(true); + controlDispatcher.dispatchSetPlayWhenReady(player, true); break; case KeyEvent.KEYCODE_MEDIA_PAUSE: - player.setPlayWhenReady(false); + controlDispatcher.dispatchSetPlayWhenReady(player, false); break; case KeyEvent.KEYCODE_MEDIA_NEXT: next(); @@ -913,9 +936,9 @@ public class PlaybackControlView extends FrameLayout { } else if (rewindButton == view) { rewind(); } else if (playButton == view) { - player.setPlayWhenReady(true); + controlDispatcher.dispatchSetPlayWhenReady(player, true); } else if (pauseButton == view) { - player.setPlayWhenReady(false); + controlDispatcher.dispatchSetPlayWhenReady(player, false); } } hideAfterTimeout(); diff --git a/library/ui/src/main/java/com/google/android/exoplayer2/ui/SimpleExoPlayerView.java b/library/ui/src/main/java/com/google/android/exoplayer2/ui/SimpleExoPlayerView.java index c2ad8ddeb5..bdb7539563 100644 --- a/library/ui/src/main/java/com/google/android/exoplayer2/ui/SimpleExoPlayerView.java +++ b/library/ui/src/main/java/com/google/android/exoplayer2/ui/SimpleExoPlayerView.java @@ -45,7 +45,7 @@ import com.google.android.exoplayer2.text.TextRenderer; import com.google.android.exoplayer2.trackselection.TrackSelection; import com.google.android.exoplayer2.trackselection.TrackSelectionArray; import com.google.android.exoplayer2.ui.AspectRatioFrameLayout.ResizeMode; -import com.google.android.exoplayer2.ui.PlaybackControlView.SeekDispatcher; +import com.google.android.exoplayer2.ui.PlaybackControlView.ControlDispatcher; import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Util; import java.util.List; @@ -497,14 +497,14 @@ public final class SimpleExoPlayerView extends FrameLayout { } /** - * Sets the {@link SeekDispatcher}. + * Sets the {@link ControlDispatcher}. * - * @param seekDispatcher The {@link SeekDispatcher}, or null to use - * {@link PlaybackControlView#DEFAULT_SEEK_DISPATCHER}. + * @param controlDispatcher The {@link ControlDispatcher}, or null to use + * {@link PlaybackControlView#DEFAULT_CONTROL_DISPATCHER}. */ - public void setSeekDispatcher(SeekDispatcher seekDispatcher) { + public void setControlDispatcher(ControlDispatcher controlDispatcher) { Assertions.checkState(controller != null); - controller.setSeekDispatcher(seekDispatcher); + controller.setControlDispatcher(controlDispatcher); } /** From 579d57b445400a6c32d2db31f2b315c494f10769 Mon Sep 17 00:00:00 2001 From: tonihei Date: Thu, 13 Apr 2017 08:01:20 -0700 Subject: [PATCH 090/119] Less memory allocations for repeated calls to SubtitleView.draw() The draw method checks if all inputs are the same as in the previous call. The options to strip the subtitle of its styles were applied each time, in order to check if the final CharSequence is still the same. This additional computation (and memory allocations) can be prevented by checking if the original CharSequence (including all styles) and the flags to remove those styles are the same. The actual style removal is now part of setupTextlayout(). ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=153061064 --- .../exoplayer2/ui/SubtitlePainter.java | 50 +++++++++---------- 1 file changed, 24 insertions(+), 26 deletions(-) diff --git a/library/ui/src/main/java/com/google/android/exoplayer2/ui/SubtitlePainter.java b/library/ui/src/main/java/com/google/android/exoplayer2/ui/SubtitlePainter.java index b75ac2b990..b6cfc9a6f3 100644 --- a/library/ui/src/main/java/com/google/android/exoplayer2/ui/SubtitlePainter.java +++ b/library/ui/src/main/java/com/google/android/exoplayer2/ui/SubtitlePainter.java @@ -153,8 +153,6 @@ import com.google.android.exoplayer2.util.Util; CaptionStyleCompat style, float textSizePx, float bottomPaddingFraction, Canvas canvas, int cueBoxLeft, int cueBoxTop, int cueBoxRight, int cueBoxBottom) { boolean isTextCue = cue.bitmap == null; - CharSequence cueText = null; - Bitmap cueBitmap = null; int windowColor = Color.BLACK; if (isTextCue) { if (TextUtils.isEmpty(cue.text)) { @@ -163,30 +161,10 @@ import com.google.android.exoplayer2.util.Util; } windowColor = (cue.windowColorSet && applyEmbeddedStyles) ? cue.windowColor : style.windowColor; - // Remove embedded styling or font size if requested. - if (applyEmbeddedFontSizes && applyEmbeddedStyles) { - cueText = cue.text; - } else if (!applyEmbeddedStyles) { - cueText = cue.text.toString(); // Equivalent to erasing all spans. - } else { - SpannableStringBuilder newCueText = new SpannableStringBuilder(cue.text); - int cueLength = newCueText.length(); - AbsoluteSizeSpan[] absSpans = newCueText.getSpans(0, cueLength, AbsoluteSizeSpan.class); - RelativeSizeSpan[] relSpans = newCueText.getSpans(0, cueLength, RelativeSizeSpan.class); - for (AbsoluteSizeSpan absSpan : absSpans) { - newCueText.removeSpan(absSpan); - } - for (RelativeSizeSpan relSpan : relSpans) { - newCueText.removeSpan(relSpan); - } - cueText = newCueText; - } - } else { - cueBitmap = cue.bitmap; } - if (areCharSequencesEqual(this.cueText, cueText) + if (areCharSequencesEqual(this.cueText, cue.text) && Util.areEqual(this.cueTextAlignment, cue.textAlignment) - && this.cueBitmap == cueBitmap + && this.cueBitmap == cue.bitmap && this.cueLine == cue.line && this.cueLineType == cue.lineType && Util.areEqual(this.cueLineAnchor, cue.lineAnchor) @@ -213,9 +191,9 @@ import com.google.android.exoplayer2.util.Util; return; } - this.cueText = cueText; + this.cueText = cue.text; this.cueTextAlignment = cue.textAlignment; - this.cueBitmap = cueBitmap; + this.cueBitmap = cue.bitmap; this.cueLine = cue.line; this.cueLineType = cue.lineType; this.cueLineAnchor = cue.lineAnchor; @@ -262,6 +240,26 @@ import com.google.android.exoplayer2.util.Util; return; } + // Remove embedded styling or font size if requested. + CharSequence cueText; + if (applyEmbeddedFontSizes && applyEmbeddedStyles) { + cueText = this.cueText; + } else if (!applyEmbeddedStyles) { + cueText = this.cueText.toString(); // Equivalent to erasing all spans. + } else { + SpannableStringBuilder newCueText = new SpannableStringBuilder(this.cueText); + int cueLength = newCueText.length(); + AbsoluteSizeSpan[] absSpans = newCueText.getSpans(0, cueLength, AbsoluteSizeSpan.class); + RelativeSizeSpan[] relSpans = newCueText.getSpans(0, cueLength, RelativeSizeSpan.class); + for (AbsoluteSizeSpan absSpan : absSpans) { + newCueText.removeSpan(absSpan); + } + for (RelativeSizeSpan relSpan : relSpans) { + newCueText.removeSpan(relSpan); + } + cueText = newCueText; + } + Alignment textAlignment = cueTextAlignment == null ? Alignment.ALIGN_CENTER : cueTextAlignment; textLayout = new StaticLayout(cueText, textPaint, availableWidth, textAlignment, spacingMult, spacingAdd, true); From 835839456ff7792afb6d7a768914696993eecf80 Mon Sep 17 00:00:00 2001 From: andrewlewis Date: Thu, 13 Apr 2017 10:53:02 -0700 Subject: [PATCH 091/119] Apply playback parameters when uninitialized If AudioTrack.setPlaybackParameters was called before initialization (for example, when an audio renderer is enabled) the parameters would actually be dropped, because configure calls reset, which didn't apply draining playback parameters if the track was not initialized. It would then overwrite the draining parameters with the current parameters. Set the playback parameters directly (without draining) for uninitialized tracks so that the call to setPlaybackParameters in configure is a no-op. Also, reset the stored channel count and sample rate when the audio processor is released so that configure returns true when it is next used, which makes sure that it gets flushed. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=153078759 --- .../google/android/exoplayer2/audio/AudioTrack.java | 11 ++++++++--- .../android/exoplayer2/audio/SonicAudioProcessor.java | 4 +++- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioTrack.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioTrack.java index d376ffee14..b3700695e5 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioTrack.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioTrack.java @@ -1002,9 +1002,13 @@ public final class AudioTrack { ? playbackParametersCheckpoints.getLast().playbackParameters : this.playbackParameters; if (!playbackParameters.equals(lastSetPlaybackParameters)) { - // We need to change the playback parameters. Drain the audio processors so we can determine - // the frame position at which the new parameters apply. - drainingPlaybackParameters = playbackParameters; + if (isInitialized()) { + // Drain the audio processors so we can determine the frame position at which the new + // parameters apply. + drainingPlaybackParameters = playbackParameters; + } else { + this.playbackParameters = playbackParameters; + } } return this.playbackParameters; } @@ -1132,6 +1136,7 @@ public final class AudioTrack { framesPerEncodedSample = 0; if (drainingPlaybackParameters != null) { playbackParameters = drainingPlaybackParameters; + drainingPlaybackParameters = null; } else if (!playbackParametersCheckpoints.isEmpty()) { playbackParameters = playbackParametersCheckpoints.getLast().playbackParameters; } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/audio/SonicAudioProcessor.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/SonicAudioProcessor.java index 2dc14a094d..ba18ebd65a 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/audio/SonicAudioProcessor.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/audio/SonicAudioProcessor.java @@ -201,9 +201,11 @@ import java.nio.ShortBuffer; @Override public void release() { sonic = null; + channelCount = Format.NO_VALUE; + sampleRateHz = Format.NO_VALUE; buffer = EMPTY_BUFFER; + shortBuffer = buffer.asShortBuffer(); outputBuffer = EMPTY_BUFFER; - shortBuffer = null; } } From e87e2318d8cad794b0342a8e80217c9b3047ddf7 Mon Sep 17 00:00:00 2001 From: andrewlewis Date: Tue, 18 Apr 2017 03:22:42 -0700 Subject: [PATCH 092/119] Fully reset AudioProcessors on releasing AudioTrack Issue: #2675 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=153445372 --- .../exoplayer2/ext/gvr/GvrAudioProcessor.java | 11 ++++++++--- .../android/exoplayer2/audio/AudioProcessor.java | 4 ++-- .../google/android/exoplayer2/audio/AudioTrack.java | 2 +- .../audio/ChannelMappingAudioProcessor.java | 9 ++++++++- .../exoplayer2/audio/ResamplingAudioProcessor.java | 5 ++++- .../exoplayer2/audio/SonicAudioProcessor.java | 12 +++++++----- 6 files changed, 30 insertions(+), 13 deletions(-) diff --git a/extensions/gvr/src/main/java/com/google/android/exoplayer2/ext/gvr/GvrAudioProcessor.java b/extensions/gvr/src/main/java/com/google/android/exoplayer2/ext/gvr/GvrAudioProcessor.java index 2117985da0..980424904d 100644 --- a/extensions/gvr/src/main/java/com/google/android/exoplayer2/ext/gvr/GvrAudioProcessor.java +++ b/extensions/gvr/src/main/java/com/google/android/exoplayer2/ext/gvr/GvrAudioProcessor.java @@ -152,14 +152,19 @@ public final class GvrAudioProcessor implements AudioProcessor { @Override public void flush() { - gvrAudioSurround.flush(); + if (gvrAudioSurround != null) { + gvrAudioSurround.flush(); + } inputEnded = false; } @Override - public synchronized void release() { - buffer = null; + public synchronized void reset() { maybeReleaseGvrAudioSurround(); + inputEnded = false; + buffer = null; + sampleRateHz = Format.NO_VALUE; + channelCount = Format.NO_VALUE; } private void maybeReleaseGvrAudioSurround() { diff --git a/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioProcessor.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioProcessor.java index 2e0d1f98d9..eced040812 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioProcessor.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioProcessor.java @@ -116,8 +116,8 @@ public interface AudioProcessor { void flush(); /** - * Releases any resources associated with this instance. + * Resets the processor to its initial state. */ - void release(); + void reset(); } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioTrack.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioTrack.java index b3700695e5..44a96373f3 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioTrack.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioTrack.java @@ -1187,7 +1187,7 @@ public final class AudioTrack { reset(); releaseKeepSessionIdAudioTrack(); for (AudioProcessor audioProcessor : availableAudioProcessors) { - audioProcessor.release(); + audioProcessor.reset(); } audioSessionId = C.AUDIO_SESSION_ID_UNSET; playing = false; diff --git a/library/core/src/main/java/com/google/android/exoplayer2/audio/ChannelMappingAudioProcessor.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/ChannelMappingAudioProcessor.java index e81d7e218a..b755776f1e 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/audio/ChannelMappingAudioProcessor.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/audio/ChannelMappingAudioProcessor.java @@ -17,6 +17,7 @@ package com.google.android.exoplayer2.audio; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C.Encoding; +import com.google.android.exoplayer2.Format; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.util.Arrays; @@ -43,6 +44,8 @@ import java.util.Arrays; public ChannelMappingAudioProcessor() { buffer = EMPTY_BUFFER; outputBuffer = EMPTY_BUFFER; + channelCount = Format.NO_VALUE; + sampleRateHz = Format.NO_VALUE; } /** @@ -147,9 +150,13 @@ import java.util.Arrays; } @Override - public void release() { + public void reset() { flush(); buffer = EMPTY_BUFFER; + channelCount = Format.NO_VALUE; + sampleRateHz = Format.NO_VALUE; + outputChannels = null; + active = false; } } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/audio/ResamplingAudioProcessor.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/ResamplingAudioProcessor.java index 752f55a0ca..0dd062150d 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/audio/ResamplingAudioProcessor.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/audio/ResamplingAudioProcessor.java @@ -168,9 +168,12 @@ import java.nio.ByteOrder; } @Override - public void release() { + public void reset() { flush(); buffer = EMPTY_BUFFER; + sampleRateHz = Format.NO_VALUE; + channelCount = Format.NO_VALUE; + encoding = C.ENCODING_INVALID; } } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/audio/SonicAudioProcessor.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/SonicAudioProcessor.java index ba18ebd65a..4fffdf2deb 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/audio/SonicAudioProcessor.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/audio/SonicAudioProcessor.java @@ -58,9 +58,8 @@ import java.nio.ShortBuffer; private float speed; private float pitch; - private ShortBuffer shortBuffer; - private ByteBuffer buffer; + private ShortBuffer shortBuffer; private ByteBuffer outputBuffer; private long inputBytes; private long outputBytes; @@ -199,13 +198,16 @@ import java.nio.ShortBuffer; } @Override - public void release() { + public void reset() { sonic = null; - channelCount = Format.NO_VALUE; - sampleRateHz = Format.NO_VALUE; buffer = EMPTY_BUFFER; shortBuffer = buffer.asShortBuffer(); outputBuffer = EMPTY_BUFFER; + channelCount = Format.NO_VALUE; + sampleRateHz = Format.NO_VALUE; + inputBytes = 0; + outputBytes = 0; + inputEnded = false; } } From 42e4100c0511b8dbd405f143e0d8aca84e2ea50b Mon Sep 17 00:00:00 2001 From: eguven Date: Tue, 18 Apr 2017 03:46:46 -0700 Subject: [PATCH 093/119] Add DashDownloaderService which downloads DASH streams at the background ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=153446615 --- library/dash/build.gradle | 1 + 1 file changed, 1 insertion(+) diff --git a/library/dash/build.gradle b/library/dash/build.gradle index a9a5550219..e608181fab 100644 --- a/library/dash/build.gradle +++ b/library/dash/build.gradle @@ -38,6 +38,7 @@ android { dependencies { compile project(':library-core') compile 'com.android.support:support-annotations:25.2.0' + compile 'com.android.support:support-core-utils:25.2.0' androidTestCompile 'com.google.dexmaker:dexmaker:1.2' androidTestCompile 'com.google.dexmaker:dexmaker-mockito:1.2' androidTestCompile 'org.mockito:mockito-core:1.9.5' From 8be85d4d2fa1e35281abf7bce2108719b3d72d21 Mon Sep 17 00:00:00 2001 From: olly Date: Tue, 18 Apr 2017 05:56:43 -0700 Subject: [PATCH 094/119] Consolidate version codes in root gradle file ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=153453768 --- build.gradle | 12 ++++++++---- extensions/cronet/build.gradle | 8 ++++---- library/all/build.gradle | 2 -- library/core/build.gradle | 8 ++++---- library/dash/build.gradle | 10 +++++----- library/hls/build.gradle | 8 ++++---- library/smoothstreaming/build.gradle | 8 ++++---- library/ui/build.gradle | 2 +- testutils/build.gradle | 2 +- 9 files changed, 31 insertions(+), 29 deletions(-) diff --git a/build.gradle b/build.gradle index 63a5f5eaaa..9c83e045db 100644 --- a/build.gradle +++ b/build.gradle @@ -37,10 +37,14 @@ allprojects { // components provided by the library may be of use on older devices. // However, please note that the core media playback functionality // provided by the library requires API level 16 or greater. - minSdkVersion=9 - compileSdkVersion=25 - targetSdkVersion=25 - buildToolsVersion='25' + minSdkVersion = 9 + compileSdkVersion = 25 + targetSdkVersion = 25 + buildToolsVersion = '25' + testSupportLibraryVersion = '0.5' + supportLibraryVersion = '25.3.1' + dexmakerVersion = '1.2' + mockitoVersion = '1.9.5' releaseRepoName = getBintrayRepo() releaseUserOrg = 'google' releaseGroupId = 'com.google.android.exoplayer' diff --git a/extensions/cronet/build.gradle b/extensions/cronet/build.gradle index 5e3c2ab482..5611817b2e 100644 --- a/extensions/cronet/build.gradle +++ b/extensions/cronet/build.gradle @@ -33,11 +33,11 @@ dependencies { compile files('libs/cronet_api.jar') compile files('libs/cronet_impl_common_java.jar') compile files('libs/cronet_impl_native_java.jar') - androidTestCompile 'com.google.dexmaker:dexmaker:1.2' - androidTestCompile 'com.google.dexmaker:dexmaker-mockito:1.2' - androidTestCompile 'org.mockito:mockito-core:1.9.5' androidTestCompile project(':library') - androidTestCompile 'com.android.support.test:runner:0.5' + androidTestCompile 'com.google.dexmaker:dexmaker:' + dexmakerVersion + androidTestCompile 'com.google.dexmaker:dexmaker-mockito:' + dexmakerVersion + androidTestCompile 'org.mockito:mockito-core:' + mockitoVersion + androidTestCompile 'com.android.support.test:runner:' + testSupportLibraryVersion } ext { diff --git a/library/all/build.gradle b/library/all/build.gradle index e88ee2721d..63943ada77 100644 --- a/library/all/build.gradle +++ b/library/all/build.gradle @@ -11,8 +11,6 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. -import com.android.builder.core.BuilderConstants - apply plugin: 'com.android.library' android { diff --git a/library/core/build.gradle b/library/core/build.gradle index 8ab63af26f..bb0adaa4c7 100644 --- a/library/core/build.gradle +++ b/library/core/build.gradle @@ -36,10 +36,10 @@ android { } dependencies { - compile 'com.android.support:support-annotations:25.2.0' - androidTestCompile 'com.google.dexmaker:dexmaker:1.2' - androidTestCompile 'com.google.dexmaker:dexmaker-mockito:1.2' - androidTestCompile 'org.mockito:mockito-core:1.9.5' + compile 'com.android.support:support-annotations:' + supportLibraryVersion + androidTestCompile 'com.google.dexmaker:dexmaker:' + dexmakerVersion + androidTestCompile 'com.google.dexmaker:dexmaker-mockito:' + dexmakerVersion + androidTestCompile 'org.mockito:mockito-core:' + mockitoVersion } ext { diff --git a/library/dash/build.gradle b/library/dash/build.gradle index e608181fab..ebad5a8603 100644 --- a/library/dash/build.gradle +++ b/library/dash/build.gradle @@ -37,11 +37,11 @@ android { dependencies { compile project(':library-core') - compile 'com.android.support:support-annotations:25.2.0' - compile 'com.android.support:support-core-utils:25.2.0' - androidTestCompile 'com.google.dexmaker:dexmaker:1.2' - androidTestCompile 'com.google.dexmaker:dexmaker-mockito:1.2' - androidTestCompile 'org.mockito:mockito-core:1.9.5' + compile 'com.android.support:support-annotations:' + supportLibraryVersion + compile 'com.android.support:support-core-utils:' + supportLibraryVersion + androidTestCompile 'com.google.dexmaker:dexmaker:' + dexmakerVersion + androidTestCompile 'com.google.dexmaker:dexmaker-mockito:' + dexmakerVersion + androidTestCompile 'org.mockito:mockito-core:' + mockitoVersion } ext { diff --git a/library/hls/build.gradle b/library/hls/build.gradle index e7375fbc8e..47b5758b1d 100644 --- a/library/hls/build.gradle +++ b/library/hls/build.gradle @@ -31,10 +31,10 @@ android { dependencies { compile project(':library-core') - compile 'com.android.support:support-annotations:25.2.0' - androidTestCompile 'com.google.dexmaker:dexmaker:1.2' - androidTestCompile 'com.google.dexmaker:dexmaker-mockito:1.2' - androidTestCompile 'org.mockito:mockito-core:1.9.5' + compile 'com.android.support:support-annotations:' + supportLibraryVersion + androidTestCompile 'com.google.dexmaker:dexmaker:' + dexmakerVersion + androidTestCompile 'com.google.dexmaker:dexmaker-mockito:' + dexmakerVersion + androidTestCompile 'org.mockito:mockito-core:' + mockitoVersion } ext { diff --git a/library/smoothstreaming/build.gradle b/library/smoothstreaming/build.gradle index 1f76f1043d..81f8234672 100644 --- a/library/smoothstreaming/build.gradle +++ b/library/smoothstreaming/build.gradle @@ -37,10 +37,10 @@ android { dependencies { compile project(':library-core') - compile 'com.android.support:support-annotations:25.2.0' - androidTestCompile 'com.google.dexmaker:dexmaker:1.2' - androidTestCompile 'com.google.dexmaker:dexmaker-mockito:1.2' - androidTestCompile 'org.mockito:mockito-core:1.9.5' + compile 'com.android.support:support-annotations:' + supportLibraryVersion + androidTestCompile 'com.google.dexmaker:dexmaker:' + dexmakerVersion + androidTestCompile 'com.google.dexmaker:dexmaker-mockito:' + dexmakerVersion + androidTestCompile 'org.mockito:mockito-core:' + mockitoVersion } ext { diff --git a/library/ui/build.gradle b/library/ui/build.gradle index 9e1e0ba85a..96dcd52655 100644 --- a/library/ui/build.gradle +++ b/library/ui/build.gradle @@ -31,7 +31,7 @@ android { dependencies { compile project(':library-core') - compile 'com.android.support:support-annotations:25.2.0' + compile 'com.android.support:support-annotations:' + supportLibraryVersion } ext { diff --git a/testutils/build.gradle b/testutils/build.gradle index a97c743384..f1d557bbf5 100644 --- a/testutils/build.gradle +++ b/testutils/build.gradle @@ -25,5 +25,5 @@ android { dependencies { compile project(':library') - compile 'org.mockito:mockito-core:1.9.5' + compile 'org.mockito:mockito-core:' + mockitoVersion } From 3c49044fb43060b28afe3da3d89544cabd28750a Mon Sep 17 00:00:00 2001 From: olly Date: Tue, 18 Apr 2017 06:12:08 -0700 Subject: [PATCH 095/119] Updates for ExoPlayer modularization - Update relevant documentation - Use individual modules for the demo app. Given it's preferable to use them over the full library, and that many people probably use the demo app as a starting point, we should set the right example even though we currently need to include all of them. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=153454898 --- README.md | 34 ++++++++++++++++++++++++++++------ demo/build.gradle | 6 +++++- testutils/build.gradle | 2 +- 3 files changed, 34 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index f74cda84ee..3de86d21a3 100644 --- a/README.md +++ b/README.md @@ -30,17 +30,39 @@ repositories { } ``` -Next, include the following in your module's `build.gradle` file: +Next add a gradle compile dependency to the `build.gradle` file of your app +module. The following will add a dependency to the full ExoPlayer library: ```gradle -compile 'com.google.android.exoplayer:exoplayer:rX.X.X' +compile 'com.google.android.exoplayer:exoplayer:r2.X.X' ``` -where `rX.X.X` is the your preferred version. For the latest version, see the -project's [Releases][]. For more details, see the project on [Bintray][]. +where `r2.X.X` is your preferred version. Alternatively, you can depend on only +the library modules that you actually need. For example the following will add +dependencies on the Core, DASH and UI library modules, as might be required for +an app that plays DASH content: -[Releases]: https://github.com/google/ExoPlayer/releases -[Bintray]: https://bintray.com/google/exoplayer/exoplayer/view +```gradle +compile 'com.google.android.exoplayer:exoplayer-core:r2.X.X' +compile 'com.google.android.exoplayer:exoplayer-dash:r2.X.X' +compile 'com.google.android.exoplayer:exoplayer-ui:r2.X.X' +``` + +The available modules are listed below. Adding a dependency to the full +ExoPlayer library is equivalent to adding dependencies on all of the modules +individually. + +* `exoplayer-core`: Core functionality (required). +* `exoplayer-dash`: Support for DASH content. +* `exoplayer-hls`: Support for HLS content. +* `exoplayer-smoothstreaming`: Support for SmoothStreaming content. +* `exoplayer-ui`: UI components and resources for use with ExoPlayer. + +For more details, see the project on [Bintray][]. For information about the +latest versions, see the [Release notes][]. + +[Bintray]: https://bintray.com/google/exoplayer +[Release notes]: https://github.com/google/ExoPlayer/blob/release-v2/RELEASENOTES.md ## Developing ExoPlayer ## diff --git a/demo/build.gradle b/demo/build.gradle index 01946c8504..be5e52a25c 100644 --- a/demo/build.gradle +++ b/demo/build.gradle @@ -45,7 +45,11 @@ android { } dependencies { - compile project(':library') + compile project(':library-core') + compile project(':library-dash') + compile project(':library-hls') + compile project(':library-smoothstreaming') + compile project(':library-ui') withExtensionsCompile project(path: ':extension-ffmpeg') withExtensionsCompile project(path: ':extension-flac') withExtensionsCompile project(path: ':extension-opus') diff --git a/testutils/build.gradle b/testutils/build.gradle index f1d557bbf5..5fea76f9c3 100644 --- a/testutils/build.gradle +++ b/testutils/build.gradle @@ -24,6 +24,6 @@ android { } dependencies { - compile project(':library') + compile project(':library-core') compile 'org.mockito:mockito-core:' + mockitoVersion } From ecb62cccd50d891338253faf768d400df0ecfa66 Mon Sep 17 00:00:00 2001 From: olly Date: Tue, 18 Apr 2017 07:43:56 -0700 Subject: [PATCH 096/119] Delete broken PriorityHandlerThread class This class is unnecessary; you can simply pass a thread priority to HandlerThread's own constructor. It's also broken, since HandlerThread.run() immediately overrides the priority being set! ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=153461225 --- .../exoplayer2/ExoPlayerImplInternal.java | 3 +- .../util/PriorityHandlerThread.java | 43 ------------------- 2 files changed, 1 insertion(+), 45 deletions(-) delete mode 100644 library/core/src/main/java/com/google/android/exoplayer2/util/PriorityHandlerThread.java diff --git a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java index 916f5f6657..bf5b3f6482 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java @@ -32,7 +32,6 @@ import com.google.android.exoplayer2.trackselection.TrackSelector; import com.google.android.exoplayer2.trackselection.TrackSelectorResult; import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.MediaClock; -import com.google.android.exoplayer2.util.PriorityHandlerThread; import com.google.android.exoplayer2.util.StandaloneMediaClock; import com.google.android.exoplayer2.util.TraceUtil; import java.io.IOException; @@ -196,7 +195,7 @@ import java.io.IOException; // Note: The documentation for Process.THREAD_PRIORITY_AUDIO that states "Applications can // not normally change to this priority" is incorrect. - internalPlaybackThread = new PriorityHandlerThread("ExoPlayerImplInternal:Handler", + internalPlaybackThread = new HandlerThread("ExoPlayerImplInternal:Handler", Process.THREAD_PRIORITY_AUDIO); internalPlaybackThread.start(); handler = new Handler(internalPlaybackThread.getLooper(), this); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/util/PriorityHandlerThread.java b/library/core/src/main/java/com/google/android/exoplayer2/util/PriorityHandlerThread.java deleted file mode 100644 index d89bb6bca3..0000000000 --- a/library/core/src/main/java/com/google/android/exoplayer2/util/PriorityHandlerThread.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.google.android.exoplayer2.util; - -import android.os.HandlerThread; -import android.os.Process; - -/** - * A {@link HandlerThread} with a specified process priority. - */ -public final class PriorityHandlerThread extends HandlerThread { - - private final int priority; - - /** - * @param name The name of the thread. - * @param priority The priority level. See {@link Process#setThreadPriority(int)} for details. - */ - public PriorityHandlerThread(String name, int priority) { - super(name); - this.priority = priority; - } - - @Override - public void run() { - Process.setThreadPriority(priority); - super.run(); - } - -} From da1b55ed4c2621f8a4ec49cf35a0a9f435188a70 Mon Sep 17 00:00:00 2001 From: tonihei Date: Tue, 18 Apr 2017 09:28:03 -0700 Subject: [PATCH 097/119] NullPointerException in CronetDataSource constructor When calling CronetDataSourceFactory.createDataSourceInteral, a null pointer is passed as clock to the CronetDataSource constructor which in turn throws a NullPointerException. Can be prevented by using other constructor without clock parameter. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=153470874 --- .../android/exoplayer2/ext/cronet/CronetDataSourceFactory.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/cronet/src/main/java/com/google/android/exoplayer2/ext/cronet/CronetDataSourceFactory.java b/extensions/cronet/src/main/java/com/google/android/exoplayer2/ext/cronet/CronetDataSourceFactory.java index db560305a7..2ad6da6a54 100644 --- a/extensions/cronet/src/main/java/com/google/android/exoplayer2/ext/cronet/CronetDataSourceFactory.java +++ b/extensions/cronet/src/main/java/com/google/android/exoplayer2/ext/cronet/CronetDataSourceFactory.java @@ -72,7 +72,7 @@ public final class CronetDataSourceFactory extends BaseFactory { protected CronetDataSource createDataSourceInternal(HttpDataSource.RequestProperties defaultRequestProperties) { return new CronetDataSource(cronetEngine, executor, contentTypePredicate, transferListener, - connectTimeoutMs, readTimeoutMs, resetTimeoutOnRedirects, null, defaultRequestProperties); + connectTimeoutMs, readTimeoutMs, resetTimeoutOnRedirects, defaultRequestProperties); } } From 3b7f47551c72a690e83e19042c7fa2002fd4390d Mon Sep 17 00:00:00 2001 From: Sergio Moreno Mozota Date: Thu, 20 Apr 2017 21:32:59 +0200 Subject: [PATCH 098/119] Add support for multiple subtitle tracks per PID and subtitle type identification --- .../extractor/ts/TsExtractorTest.java | 2 +- .../ts/DefaultTsPayloadReaderFactory.java | 10 ++-- .../extractor/ts/DvbSubtitleReader.java | 54 ++++++++++++++----- .../exoplayer2/extractor/ts/TsExtractor.java | 36 +++++++++---- .../extractor/ts/TsPayloadReader.java | 27 ++++++---- 5 files changed, 89 insertions(+), 40 deletions(-) diff --git a/library/core/src/androidTest/java/com/google/android/exoplayer2/extractor/ts/TsExtractorTest.java b/library/core/src/androidTest/java/com/google/android/exoplayer2/extractor/ts/TsExtractorTest.java index 7bf722cd8f..e732a4c657 100644 --- a/library/core/src/androidTest/java/com/google/android/exoplayer2/extractor/ts/TsExtractorTest.java +++ b/library/core/src/androidTest/java/com/google/android/exoplayer2/extractor/ts/TsExtractorTest.java @@ -156,7 +156,7 @@ public final class TsExtractorTest extends InstrumentationTestCase { @Override public TsPayloadReader createPayloadReader(int streamType, EsInfo esInfo) { if (provideCustomEsReader && streamType == 3) { - esReader = new CustomEsReader(esInfo.language); + esReader = new CustomEsReader(esInfo.languagesInfo.get(0).languageCode); return new PesReader(esReader); } else { return defaultFactory.createPayloadReader(streamType, esInfo); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/DefaultTsPayloadReaderFactory.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/DefaultTsPayloadReaderFactory.java index 3808d18b9a..c36cbb1a07 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/DefaultTsPayloadReaderFactory.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/DefaultTsPayloadReaderFactory.java @@ -93,16 +93,16 @@ public final class DefaultTsPayloadReaderFactory implements TsPayloadReader.Fact switch (streamType) { case TsExtractor.TS_STREAM_TYPE_MPA: case TsExtractor.TS_STREAM_TYPE_MPA_LSF: - return new PesReader(new MpegAudioReader(esInfo.language)); + return new PesReader(new MpegAudioReader(esInfo.languagesInfo.get(0).languageCode)); case TsExtractor.TS_STREAM_TYPE_AAC: return isSet(FLAG_IGNORE_AAC_STREAM) - ? null : new PesReader(new AdtsReader(false, esInfo.language)); + ? null : new PesReader(new AdtsReader(false, esInfo.languagesInfo.get(0).languageCode)); case TsExtractor.TS_STREAM_TYPE_AC3: case TsExtractor.TS_STREAM_TYPE_E_AC3: - return new PesReader(new Ac3Reader(esInfo.language)); + return new PesReader(new Ac3Reader(esInfo.languagesInfo.get(0).languageCode)); case TsExtractor.TS_STREAM_TYPE_DTS: case TsExtractor.TS_STREAM_TYPE_HDMV_DTS: - return new PesReader(new DtsReader(esInfo.language)); + return new PesReader(new DtsReader(esInfo.languagesInfo.get(0).languageCode)); case TsExtractor.TS_STREAM_TYPE_H262: return new PesReader(new H262Reader()); case TsExtractor.TS_STREAM_TYPE_H264: @@ -118,7 +118,7 @@ public final class DefaultTsPayloadReaderFactory implements TsPayloadReader.Fact return new PesReader(new Id3Reader()); case TsExtractor.TS_STREAM_TYPE_DVBSUBS: return new PesReader( - new DvbSubtitleReader(esInfo.language, esInfo.dvbSubtitleInitializationData)); + new DvbSubtitleReader(esInfo)); default: return null; } 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 f228432a77..1f6c885984 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 @@ -20,9 +20,12 @@ import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.extractor.ExtractorOutput; import com.google.android.exoplayer2.extractor.TrackOutput; import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.TrackIdGenerator; +import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.EsInfo; +import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.LanguageInfo; import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.ParsableByteArray; -import java.util.Collections; + +import java.util.ArrayList; import java.util.List; /** @@ -30,22 +33,19 @@ import java.util.List; */ public final class DvbSubtitleReader implements ElementaryStreamReader { - private final String language; - private final List initializationData; + private final List languages; + private List outputTracks = new ArrayList<>(); - private TrackOutput output; private boolean writingSample; private int bytesToCheck; private int sampleBytesWritten; private long sampleTimeUs; /** - * @param language The subtitle language code. - * @param initializationData Initialization data to be included in the track {@link Format}. + * @param esInfo Information associated to the elementary stream. */ - public DvbSubtitleReader(String language, byte[] initializationData) { - this.language = language; - this.initializationData = Collections.singletonList(initializationData); + public DvbSubtitleReader(EsInfo esInfo) { + this.languages = esInfo.languagesInfo; } @Override @@ -56,9 +56,24 @@ public final class DvbSubtitleReader implements ElementaryStreamReader { @Override public void createTracks(ExtractorOutput extractorOutput, TrackIdGenerator idGenerator) { idGenerator.generateNewId(); - this.output = extractorOutput.track(idGenerator.getTrackId(), C.TRACK_TYPE_TEXT); - output.format(Format.createImageSampleFormat(idGenerator.getFormatId(), - MimeTypes.APPLICATION_DVBSUBS, null, Format.NO_VALUE, initializationData, language, null)); + + TrackOutput output; + LanguageInfo language; + + for (int i = 0; i < languages.size(); i++) { + language = languages.get(i); + idGenerator.generateNewId(); + + if (((language.programElementType & 0xF0 ) >> 4 ) == 2 ) { + language.languageCode += " for hard of hearing"; + } + + output = extractorOutput.track(idGenerator.getTrackId(), C.TRACK_TYPE_TEXT); + output.format(Format.createImageSampleFormat(idGenerator.getFormatId(), + MimeTypes.APPLICATION_DVBSUBS, null, Format.NO_VALUE, + language.initializationData, language.languageCode, null)); + outputTracks.add(output); + } } @Override @@ -75,7 +90,12 @@ public final class DvbSubtitleReader implements ElementaryStreamReader { @Override public void packetFinished() { if (writingSample) { - output.sampleMetadata(sampleTimeUs, C.BUFFER_FLAG_KEY_FRAME, sampleBytesWritten, 0, null); + TrackOutput output; + + for (int i = 0; i < outputTracks.size(); i++) { + output = outputTracks.get(i); + output.sampleMetadata(sampleTimeUs, C.BUFFER_FLAG_KEY_FRAME, sampleBytesWritten, 0, null); + } writingSample = false; } } @@ -92,7 +112,13 @@ public final class DvbSubtitleReader implements ElementaryStreamReader { return; } int bytesAvailable = data.bytesLeft(); - output.sampleData(data, bytesAvailable); + TrackOutput output; + int dataPosition = data.getPosition(); + for (int i = 0; i < outputTracks.size(); i++) { + data.setPosition(dataPosition); + output = outputTracks.get(i); + output.sampleData(data, bytesAvailable); + } sampleBytesWritten += bytesAvailable; } } 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 b1c1220c45..b9c794c1f8 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 @@ -29,6 +29,7 @@ import com.google.android.exoplayer2.extractor.SeekMap; import com.google.android.exoplayer2.extractor.TrackOutput; import com.google.android.exoplayer2.extractor.ts.DefaultTsPayloadReaderFactory.Flags; import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.EsInfo; +import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.LanguageInfo; import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.TrackIdGenerator; import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.ParsableBitArray; @@ -418,7 +419,7 @@ public final class TsExtractor implements Extractor { if (mode == MODE_HLS && id3Reader == null) { // Setup an ID3 track regardless of whether there's a corresponding entry, in case one // appears intermittently during playback. See [Internal: b/20261500]. - EsInfo dummyEsInfo = new EsInfo(TS_STREAM_TYPE_ID3, null, null, new byte[0]); + EsInfo dummyEsInfo = new EsInfo(TS_STREAM_TYPE_ID3, null, new byte[0]); id3Reader = payloadReaderFactory.createPayloadReader(TS_STREAM_TYPE_ID3, dummyEsInfo); id3Reader.init(timestampAdjuster, output, new TrackIdGenerator(programNumber, TS_STREAM_TYPE_ID3, MAX_PID_PLUS_ONE)); @@ -487,8 +488,7 @@ public final class TsExtractor implements Extractor { int descriptorsStartPosition = data.getPosition(); int descriptorsEndPosition = descriptorsStartPosition + length; int streamType = -1; - String language = null; - byte[] dvbSubtitleInitializationData = null; + List languages = null; while (data.getPosition() < descriptorsEndPosition) { int descriptorTag = data.readUnsignedByte(); int descriptorLength = data.readUnsignedByte(); @@ -509,21 +509,35 @@ public final class TsExtractor implements Extractor { } else if (descriptorTag == TS_PMT_DESC_DTS) { // DTS_descriptor streamType = TS_STREAM_TYPE_DTS; } else if (descriptorTag == TS_PMT_DESC_ISO639_LANG) { - language = new String(data.data, data.getPosition(), 3).trim(); - // Audio type is ignored. + int position = data.getPosition(); + languages = Collections.singletonList( + new LanguageInfo(new String(new byte[] + {data.data[position++], data.data[position++], data.data[position++]}).trim(), + data.data[position], null)); } else if (descriptorTag == TS_PMT_DESC_DVBSUBS) { streamType = TS_STREAM_TYPE_DVBSUBS; - language = new String(data.data, data.getPosition(), 3).trim(); - data.skipBytes(4); // Skip language (3) + subtitling_type (1) - // Init data: composition_page (2), ancillary_page (2) - dvbSubtitleInitializationData = new byte[4]; - data.readBytes(dvbSubtitleInitializationData, 0, 4); + int position = data.getPosition(); + String language; + byte programElementType; + byte[] buffer; + languages = new ArrayList<>(); + while (position < positionOfNextDescriptor) { + buffer = new byte[4]; + language = new String(new byte[] + {data.data[position++], data.data[position++], data.data[position++]}).trim(); + programElementType = data.data[position++]; + data.setPosition(position); + data.readBytes(buffer, 0,4); + languages.add( + new LanguageInfo(language, programElementType, Collections.singletonList(buffer))); + position += 4; + } } // Skip unused bytes of current descriptor. data.skipBytes(positionOfNextDescriptor - data.getPosition()); } data.setPosition(descriptorsEndPosition); - return new EsInfo(streamType, language, dvbSubtitleInitializationData, + return new EsInfo(streamType, languages, Arrays.copyOfRange(data.data, descriptorsStartPosition, descriptorsEndPosition)); } 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 a6ebf770b4..d5fe06ded7 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 @@ -21,6 +21,8 @@ import com.google.android.exoplayer2.extractor.TrackOutput; import com.google.android.exoplayer2.util.ParsableByteArray; import com.google.android.exoplayer2.util.TimestampAdjuster; +import java.util.List; + /** * Parses TS packet payload data. */ @@ -59,26 +61,33 @@ public interface TsPayloadReader { final class EsInfo { public final int streamType; - public final String language; - public final byte[] dvbSubtitleInitializationData; + public final List languagesInfo; public final byte[] descriptorBytes; /** * @param streamType The type of the stream as defined by the * {@link TsExtractor}{@code .TS_STREAM_TYPE_*}. - * @param language The language of the stream, as defined by ISO/IEC 13818-1, section 2.6.18. - * @param dvbSubtitleInitializationData If the descriptors include a DVB subtitle tag, this is - * the corresponding decoder initialization data. Null otherwise. + * @param languagesInfo Language or languages info of the associated program element * @param descriptorBytes The descriptor bytes associated to the stream. */ - public EsInfo(int streamType, String language, byte[] dvbSubtitleInitializationData, - byte[] descriptorBytes) { + public EsInfo(int streamType, List languagesInfo, byte[] descriptorBytes) { this.streamType = streamType; - this.language = language; - this.dvbSubtitleInitializationData = dvbSubtitleInitializationData; + this.languagesInfo = languagesInfo; this.descriptorBytes = descriptorBytes; } + } + final class LanguageInfo { + + public String languageCode; + public final byte programElementType; + public final List initializationData; + + LanguageInfo (String languageCode, byte programElementType, List initializationData) { + this.languageCode = languageCode; + this.programElementType = programElementType; + this.initializationData = initializationData; + } } /** From 41f4f24f539b8c647e7b8bb1f48bbad5383ef47a Mon Sep 17 00:00:00 2001 From: Damien Merenne Date: Fri, 21 Apr 2017 11:32:00 +0200 Subject: [PATCH 099/119] Allow out of tree builds. Before modularisation of the code, it was possible to do an out of tree build giving an -PbuildDir=someDir argument to gradle. With the modularisation, it's broken as using -PbuildDir=someDir will force the same directory for each projects, which breaks the gradle build system. This commit adds a new externalBuildDir project property to allow out of tree builds again. When set, it updates the buildDir property for each project to point to ${externalBuildDir}/${project.name}. That way, the build artifacts are written in the out of tree directory in a project specific folder. To do an out of tree build, use gradle -PexternalBuildDir=someDir ... It supports absolute and relative path. Relative path are interpreted against the ExoPlayer root directory. --- build.gradle | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/build.gradle b/build.gradle index 9c83e045db..5b3c955df4 100644 --- a/build.gradle +++ b/build.gradle @@ -51,6 +51,11 @@ allprojects { releaseVersion = 'r2.3.1' releaseWebsite = 'https://github.com/google/ExoPlayer' } + if (it.hasProperty('externalBuildDir')) { + if (!new File(externalBuildDir).isAbsolute()) + externalBuildDir = new File(rootDir, externalBuildDir) + buildDir = "${externalBuildDir}/${project.name}" + } } def getBintrayRepo() { From 3280dc8777287e851c7b56dc541a64ef8a9c522a Mon Sep 17 00:00:00 2001 From: cblay Date: Thu, 20 Apr 2017 15:07:53 -0700 Subject: [PATCH 100/119] Make SonicAudioProcessor public ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=153762476 --- .../google/android/exoplayer2/audio/SonicAudioProcessor.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/audio/SonicAudioProcessor.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/SonicAudioProcessor.java index 4fffdf2deb..df20139255 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/audio/SonicAudioProcessor.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/audio/SonicAudioProcessor.java @@ -26,8 +26,7 @@ import java.nio.ShortBuffer; /** * An {@link AudioProcessor} that uses the Sonic library to modify the speed/pitch of audio. */ -// TODO: Make public once it is possible to override AudioTrack's position calculations. -/* package */ final class SonicAudioProcessor implements AudioProcessor { +public final class SonicAudioProcessor implements AudioProcessor { /** * The maximum allowed playback speed in {@link #setSpeed(float)}. From 1506b6d2b72ae51fd0215e5acddb42e2466266ef Mon Sep 17 00:00:00 2001 From: olly Date: Fri, 21 Apr 2017 07:57:37 -0700 Subject: [PATCH 101/119] Pragmatic fix for Concat(source1, Looping(source2)) Issue: #2680 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=153827243 --- .../source/ConcatenatingMediaSource.java | 7 +++++-- .../exoplayer2/source/LoopingMediaSource.java | 16 ++++++++++++---- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/ConcatenatingMediaSource.java b/library/core/src/main/java/com/google/android/exoplayer2/source/ConcatenatingMediaSource.java index 68552c99ed..9fc499f251 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/ConcatenatingMediaSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/ConcatenatingMediaSource.java @@ -20,6 +20,7 @@ import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.ExoPlayer; import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.upstream.Allocator; +import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Util; import java.io.IOException; import java.util.HashMap; @@ -152,12 +153,14 @@ public final class ConcatenatingMediaSource implements MediaSource { public ConcatenatedTimeline(Timeline[] timelines) { int[] sourcePeriodOffsets = new int[timelines.length]; int[] sourceWindowOffsets = new int[timelines.length]; - int periodCount = 0; + long periodCount = 0; int windowCount = 0; for (int i = 0; i < timelines.length; i++) { Timeline timeline = timelines[i]; periodCount += timeline.getPeriodCount(); - sourcePeriodOffsets[i] = periodCount; + Assertions.checkState(periodCount <= Integer.MAX_VALUE, + "ConcatenatingMediaSource children contain too many periods"); + sourcePeriodOffsets[i] = (int) periodCount; windowCount += timeline.getWindowCount(); sourceWindowOffsets[i] = windowCount; } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/LoopingMediaSource.java b/library/core/src/main/java/com/google/android/exoplayer2/source/LoopingMediaSource.java index b26ae3a6ac..8b14c78234 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/LoopingMediaSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/LoopingMediaSource.java @@ -29,6 +29,13 @@ import java.io.IOException; */ public final class LoopingMediaSource implements MediaSource { + /** + * The maximum number of periods that can be exposed by the source. The value of this constant is + * large enough to cause indefinite looping in practice (the total duration of the looping source + * will be approximately five years if the duration of each period is one second). + */ + public static final int MAX_EXPOSED_PERIODS = 157680000; + private static final String TAG = "LoopingMediaSource"; private final MediaSource childSource; @@ -50,8 +57,8 @@ public final class LoopingMediaSource implements MediaSource { * * @param childSource The {@link MediaSource} to loop. * @param loopCount The desired number of loops. Must be strictly positive. The actual number of - * loops will be capped at the maximum value that can achieved without causing the number of - * periods exposed by the source to exceed {@link Integer#MAX_VALUE}. + * loops will be capped at the maximum that can achieved without causing the number of + * periods exposed by the source to exceed {@link #MAX_EXPOSED_PERIODS}. */ public LoopingMediaSource(MediaSource childSource, int loopCount) { Assertions.checkArgument(loopCount > 0); @@ -101,8 +108,9 @@ public final class LoopingMediaSource implements MediaSource { this.childTimeline = childTimeline; childPeriodCount = childTimeline.getPeriodCount(); childWindowCount = childTimeline.getWindowCount(); - // This is the maximum number of loops that can be performed without overflow. - int maxLoopCount = Integer.MAX_VALUE / childPeriodCount; + // This is the maximum number of loops that can be performed without exceeding + // MAX_EXPOSED_PERIODS periods. + int maxLoopCount = MAX_EXPOSED_PERIODS / childPeriodCount; if (loopCount > maxLoopCount) { if (loopCount != Integer.MAX_VALUE) { Log.w(TAG, "Capped loops to avoid overflow: " + loopCount + " -> " + maxLoopCount); From 86ed73e74416f5609f27347c55e7fa61d4fde386 Mon Sep 17 00:00:00 2001 From: olly Date: Fri, 21 Apr 2017 08:34:16 -0700 Subject: [PATCH 102/119] Use method arguments instead of member variables This is a no-op cleanup change. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=153830409 --- .../com/google/android/exoplayer2/SimpleExoPlayer.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) 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 6ce6191905..f799df2c5d 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 @@ -659,7 +659,7 @@ public class SimpleExoPlayer implements ExoPlayer { Constructor constructor = clazz.getConstructor(boolean.class, long.class, Handler.class, VideoRendererEventListener.class, int.class); Renderer renderer = (Renderer) constructor.newInstance(true, allowedVideoJoiningTimeMs, - mainHandler, componentListener, MAX_DROPPED_VIDEO_FRAME_COUNT_TO_NOTIFY); + mainHandler, eventListener, MAX_DROPPED_VIDEO_FRAME_COUNT_TO_NOTIFY); out.add(extensionRendererIndex++, renderer); Log.i(TAG, "Loaded LibvpxVideoRenderer."); } catch (ClassNotFoundException e) { @@ -702,7 +702,7 @@ public class SimpleExoPlayer implements ExoPlayer { Class.forName("com.google.android.exoplayer2.ext.opus.LibopusAudioRenderer"); Constructor constructor = clazz.getConstructor(Handler.class, AudioRendererEventListener.class, AudioProcessor[].class); - Renderer renderer = (Renderer) constructor.newInstance(mainHandler, componentListener, + Renderer renderer = (Renderer) constructor.newInstance(mainHandler, eventListener, audioProcessors); out.add(extensionRendererIndex++, renderer); Log.i(TAG, "Loaded LibopusAudioRenderer."); @@ -717,7 +717,7 @@ public class SimpleExoPlayer implements ExoPlayer { Class.forName("com.google.android.exoplayer2.ext.flac.LibflacAudioRenderer"); Constructor constructor = clazz.getConstructor(Handler.class, AudioRendererEventListener.class, AudioProcessor[].class); - Renderer renderer = (Renderer) constructor.newInstance(mainHandler, componentListener, + Renderer renderer = (Renderer) constructor.newInstance(mainHandler, eventListener, audioProcessors); out.add(extensionRendererIndex++, renderer); Log.i(TAG, "Loaded LibflacAudioRenderer."); @@ -732,7 +732,7 @@ public class SimpleExoPlayer implements ExoPlayer { Class.forName("com.google.android.exoplayer2.ext.ffmpeg.FfmpegAudioRenderer"); Constructor constructor = clazz.getConstructor(Handler.class, AudioRendererEventListener.class, AudioProcessor[].class); - Renderer renderer = (Renderer) constructor.newInstance(mainHandler, componentListener, + Renderer renderer = (Renderer) constructor.newInstance(mainHandler, eventListener, audioProcessors); out.add(extensionRendererIndex++, renderer); Log.i(TAG, "Loaded FfmpegAudioRenderer."); From 860eb26301ca5aea47f8a34a2394dc304fbf8096 Mon Sep 17 00:00:00 2001 From: Oliver Woodman Date: Fri, 21 Apr 2017 18:04:55 +0100 Subject: [PATCH 103/119] Misc cleanup for merged pull requests --- build.gradle | 3 ++- extensions/ffmpeg/README.md | 1 - .../extractor/flv/AudioTagPayloadReader.java | 15 ++++++--------- 3 files changed, 8 insertions(+), 11 deletions(-) diff --git a/build.gradle b/build.gradle index 5b3c955df4..01b1c28bf6 100644 --- a/build.gradle +++ b/build.gradle @@ -52,8 +52,9 @@ allprojects { releaseWebsite = 'https://github.com/google/ExoPlayer' } if (it.hasProperty('externalBuildDir')) { - if (!new File(externalBuildDir).isAbsolute()) + if (!new File(externalBuildDir).isAbsolute()) { externalBuildDir = new File(rootDir, externalBuildDir) + } buildDir = "${externalBuildDir}/${project.name}" } } diff --git a/extensions/ffmpeg/README.md b/extensions/ffmpeg/README.md index a14e51a894..4ce9173ec9 100644 --- a/extensions/ffmpeg/README.md +++ b/extensions/ffmpeg/README.md @@ -37,7 +37,6 @@ NDK_PATH="" HOST_PLATFORM="linux-x86_64" ``` - * Fetch and build FFmpeg. For example, to fetch and build for armeabi-v7a, arm64-v8a and x86 on Linux x86_64: diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/flv/AudioTagPayloadReader.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/flv/AudioTagPayloadReader.java index d87802eca6..8e3bd08375 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/flv/AudioTagPayloadReader.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/flv/AudioTagPayloadReader.java @@ -15,7 +15,6 @@ */ package com.google.android.exoplayer2.extractor.flv; -import android.util.Log; import android.util.Pair; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.Format; @@ -38,10 +37,7 @@ import java.util.Collections; private static final int AAC_PACKET_TYPE_SEQUENCE_HEADER = 0; private static final int AAC_PACKET_TYPE_AAC_RAW = 1; - // SAMPLING RATES USED FOR MP3 - private static final int[] AUDIO_SAMPLING_RATE_TABLE = new int[] { - 5512, 11025, 22050, 44100 - }; + private static final int[] AUDIO_SAMPLING_RATE_TABLE = new int[] {5512, 11025, 22050, 44100}; // State variables private boolean hasParsedAudioDataHeader; @@ -100,10 +96,11 @@ import java.util.Collections; // Parse the sequence header. byte[] audioSpecificConfig = new byte[data.bytesLeft()]; data.readBytes(audioSpecificConfig, 0, audioSpecificConfig.length); - Pair audioParams = CodecSpecificDataUtil.parseAacAudioSpecificConfig(audioSpecificConfig); - Format format = - Format.createAudioSampleFormat(null, MimeTypes.AUDIO_AAC, null, Format.NO_VALUE, Format.NO_VALUE, audioParams.second, audioParams.first, - Collections.singletonList(audioSpecificConfig), null, 0, null); + Pair audioParams = CodecSpecificDataUtil.parseAacAudioSpecificConfig( + audioSpecificConfig); + Format format = Format.createAudioSampleFormat(null, MimeTypes.AUDIO_AAC, null, + Format.NO_VALUE, Format.NO_VALUE, audioParams.second, audioParams.first, + Collections.singletonList(audioSpecificConfig), null, 0, null); output.format(format); hasOutputFormat = true; } else if (audioFormat != AUDIO_FORMAT_AAC || packetType == AAC_PACKET_TYPE_AAC_RAW) { From 51de6e53ebf7188bd6e469165986283f6ef75b3d Mon Sep 17 00:00:00 2001 From: Oliver Woodman Date: Mon, 24 Apr 2017 16:03:19 +0100 Subject: [PATCH 104/119] Clean up multiple DVB subtitles per PID support --- .../extractor/ts/TsExtractorTest.java | 2 +- .../ts/DefaultTsPayloadReaderFactory.java | 10 ++-- .../extractor/ts/DvbSubtitleReader.java | 49 ++++++------------- .../exoplayer2/extractor/ts/TsExtractor.java | 39 ++++++--------- .../extractor/ts/TsPayloadReader.java | 30 ++++++++---- 5 files changed, 58 insertions(+), 72 deletions(-) diff --git a/library/core/src/androidTest/java/com/google/android/exoplayer2/extractor/ts/TsExtractorTest.java b/library/core/src/androidTest/java/com/google/android/exoplayer2/extractor/ts/TsExtractorTest.java index e732a4c657..7bf722cd8f 100644 --- a/library/core/src/androidTest/java/com/google/android/exoplayer2/extractor/ts/TsExtractorTest.java +++ b/library/core/src/androidTest/java/com/google/android/exoplayer2/extractor/ts/TsExtractorTest.java @@ -156,7 +156,7 @@ public final class TsExtractorTest extends InstrumentationTestCase { @Override public TsPayloadReader createPayloadReader(int streamType, EsInfo esInfo) { if (provideCustomEsReader && streamType == 3) { - esReader = new CustomEsReader(esInfo.languagesInfo.get(0).languageCode); + esReader = new CustomEsReader(esInfo.language); return new PesReader(esReader); } else { return defaultFactory.createPayloadReader(streamType, esInfo); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/DefaultTsPayloadReaderFactory.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/DefaultTsPayloadReaderFactory.java index c36cbb1a07..4bc7f11c1a 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/DefaultTsPayloadReaderFactory.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/DefaultTsPayloadReaderFactory.java @@ -93,16 +93,16 @@ public final class DefaultTsPayloadReaderFactory implements TsPayloadReader.Fact switch (streamType) { case TsExtractor.TS_STREAM_TYPE_MPA: case TsExtractor.TS_STREAM_TYPE_MPA_LSF: - return new PesReader(new MpegAudioReader(esInfo.languagesInfo.get(0).languageCode)); + return new PesReader(new MpegAudioReader(esInfo.language)); case TsExtractor.TS_STREAM_TYPE_AAC: return isSet(FLAG_IGNORE_AAC_STREAM) - ? null : new PesReader(new AdtsReader(false, esInfo.languagesInfo.get(0).languageCode)); + ? null : new PesReader(new AdtsReader(false, esInfo.language)); case TsExtractor.TS_STREAM_TYPE_AC3: case TsExtractor.TS_STREAM_TYPE_E_AC3: - return new PesReader(new Ac3Reader(esInfo.languagesInfo.get(0).languageCode)); + return new PesReader(new Ac3Reader(esInfo.language)); case TsExtractor.TS_STREAM_TYPE_DTS: case TsExtractor.TS_STREAM_TYPE_HDMV_DTS: - return new PesReader(new DtsReader(esInfo.languagesInfo.get(0).languageCode)); + return new PesReader(new DtsReader(esInfo.language)); case TsExtractor.TS_STREAM_TYPE_H262: return new PesReader(new H262Reader()); case TsExtractor.TS_STREAM_TYPE_H264: @@ -118,7 +118,7 @@ public final class DefaultTsPayloadReaderFactory implements TsPayloadReader.Fact return new PesReader(new Id3Reader()); case TsExtractor.TS_STREAM_TYPE_DVBSUBS: return new PesReader( - new DvbSubtitleReader(esInfo)); + new DvbSubtitleReader(esInfo.dvbSubtitleInfos)); default: return null; } 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 1f6c885984..87adfcea86 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 @@ -19,13 +19,10 @@ import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.extractor.ExtractorOutput; import com.google.android.exoplayer2.extractor.TrackOutput; +import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.DvbSubtitleInfo; import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.TrackIdGenerator; -import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.EsInfo; -import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.LanguageInfo; import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.ParsableByteArray; - -import java.util.ArrayList; import java.util.List; /** @@ -33,8 +30,8 @@ import java.util.List; */ public final class DvbSubtitleReader implements ElementaryStreamReader { - private final List languages; - private List outputTracks = new ArrayList<>(); + private final List subtitleInfos; + private final TrackOutput[] outputs; private boolean writingSample; private int bytesToCheck; @@ -42,10 +39,11 @@ public final class DvbSubtitleReader implements ElementaryStreamReader { private long sampleTimeUs; /** - * @param esInfo Information associated to the elementary stream. + * @param subtitleInfos Information about the DVB subtitles associated to the stream. */ - public DvbSubtitleReader(EsInfo esInfo) { - this.languages = esInfo.languagesInfo; + public DvbSubtitleReader(List subtitleInfos) { + this.subtitleInfos = subtitleInfos; + outputs = new TrackOutput[subtitleInfos.size()]; } @Override @@ -55,24 +53,14 @@ public final class DvbSubtitleReader implements ElementaryStreamReader { @Override public void createTracks(ExtractorOutput extractorOutput, TrackIdGenerator idGenerator) { - idGenerator.generateNewId(); - - TrackOutput output; - LanguageInfo language; - - for (int i = 0; i < languages.size(); i++) { - language = languages.get(i); + for (int i = 0; i < outputs.length; i++) { + DvbSubtitleInfo subtitleInfo = subtitleInfos.get(i); idGenerator.generateNewId(); - - if (((language.programElementType & 0xF0 ) >> 4 ) == 2 ) { - language.languageCode += " for hard of hearing"; - } - - output = extractorOutput.track(idGenerator.getTrackId(), C.TRACK_TYPE_TEXT); + TrackOutput output = extractorOutput.track(idGenerator.getTrackId(), C.TRACK_TYPE_TEXT); output.format(Format.createImageSampleFormat(idGenerator.getFormatId(), - MimeTypes.APPLICATION_DVBSUBS, null, Format.NO_VALUE, - language.initializationData, language.languageCode, null)); - outputTracks.add(output); + MimeTypes.APPLICATION_DVBSUBS, null, Format.NO_VALUE, subtitleInfo.initializationData, + subtitleInfo.language, null)); + outputs[i] = output; } } @@ -90,10 +78,7 @@ public final class DvbSubtitleReader implements ElementaryStreamReader { @Override public void packetFinished() { if (writingSample) { - TrackOutput output; - - for (int i = 0; i < outputTracks.size(); i++) { - output = outputTracks.get(i); + for (TrackOutput output : outputs) { output.sampleMetadata(sampleTimeUs, C.BUFFER_FLAG_KEY_FRAME, sampleBytesWritten, 0, null); } writingSample = false; @@ -111,12 +96,10 @@ public final class DvbSubtitleReader implements ElementaryStreamReader { // Check and discard the subtitle_stream_id return; } - int bytesAvailable = data.bytesLeft(); - TrackOutput output; int dataPosition = data.getPosition(); - for (int i = 0; i < outputTracks.size(); i++) { + int bytesAvailable = data.bytesLeft(); + for (TrackOutput output : outputs) { data.setPosition(dataPosition); - output = outputTracks.get(i); output.sampleData(data, bytesAvailable); } sampleBytesWritten += bytesAvailable; 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 b9c794c1f8..adcdb7d9d7 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 @@ -28,8 +28,8 @@ import com.google.android.exoplayer2.extractor.PositionHolder; import com.google.android.exoplayer2.extractor.SeekMap; import com.google.android.exoplayer2.extractor.TrackOutput; import com.google.android.exoplayer2.extractor.ts.DefaultTsPayloadReaderFactory.Flags; +import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.DvbSubtitleInfo; import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.EsInfo; -import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.LanguageInfo; import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.TrackIdGenerator; import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.ParsableBitArray; @@ -419,7 +419,7 @@ public final class TsExtractor implements Extractor { if (mode == MODE_HLS && id3Reader == null) { // Setup an ID3 track regardless of whether there's a corresponding entry, in case one // appears intermittently during playback. See [Internal: b/20261500]. - EsInfo dummyEsInfo = new EsInfo(TS_STREAM_TYPE_ID3, null, new byte[0]); + EsInfo dummyEsInfo = new EsInfo(TS_STREAM_TYPE_ID3, null, null, new byte[0]); id3Reader = payloadReaderFactory.createPayloadReader(TS_STREAM_TYPE_ID3, dummyEsInfo); id3Reader.init(timestampAdjuster, output, new TrackIdGenerator(programNumber, TS_STREAM_TYPE_ID3, MAX_PID_PLUS_ONE)); @@ -488,7 +488,8 @@ public final class TsExtractor implements Extractor { int descriptorsStartPosition = data.getPosition(); int descriptorsEndPosition = descriptorsStartPosition + length; int streamType = -1; - List languages = null; + String language = null; + List dvbSubtitleInfos = null; while (data.getPosition() < descriptorsEndPosition) { int descriptorTag = data.readUnsignedByte(); int descriptorLength = data.readUnsignedByte(); @@ -509,35 +510,25 @@ public final class TsExtractor implements Extractor { } else if (descriptorTag == TS_PMT_DESC_DTS) { // DTS_descriptor streamType = TS_STREAM_TYPE_DTS; } else if (descriptorTag == TS_PMT_DESC_ISO639_LANG) { - int position = data.getPosition(); - languages = Collections.singletonList( - new LanguageInfo(new String(new byte[] - {data.data[position++], data.data[position++], data.data[position++]}).trim(), - data.data[position], null)); + language = data.readString(3).trim(); + // Audio type is ignored. } else if (descriptorTag == TS_PMT_DESC_DVBSUBS) { streamType = TS_STREAM_TYPE_DVBSUBS; - int position = data.getPosition(); - String language; - byte programElementType; - byte[] buffer; - languages = new ArrayList<>(); - while (position < positionOfNextDescriptor) { - buffer = new byte[4]; - language = new String(new byte[] - {data.data[position++], data.data[position++], data.data[position++]}).trim(); - programElementType = data.data[position++]; - data.setPosition(position); - data.readBytes(buffer, 0,4); - languages.add( - new LanguageInfo(language, programElementType, Collections.singletonList(buffer))); - position += 4; + dvbSubtitleInfos = new ArrayList<>(); + while (data.getPosition() < positionOfNextDescriptor) { + String dvbLanguage = data.readString(3).trim(); + int dvbProgramElementType = data.readUnsignedByte(); + byte[] initializationData = new byte[4]; + data.readBytes(initializationData, 0, 4); + dvbSubtitleInfos.add(new DvbSubtitleInfo(dvbLanguage, dvbProgramElementType, + Collections.singletonList(initializationData))); } } // Skip unused bytes of current descriptor. data.skipBytes(positionOfNextDescriptor - data.getPosition()); } data.setPosition(descriptorsEndPosition); - return new EsInfo(streamType, languages, + return new EsInfo(streamType, language, dvbSubtitleInfos, Arrays.copyOfRange(data.data, descriptorsStartPosition, descriptorsEndPosition)); } 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 d5fe06ded7..ef3a6af085 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 @@ -21,6 +21,7 @@ import com.google.android.exoplayer2.extractor.TrackOutput; import com.google.android.exoplayer2.util.ParsableByteArray; import com.google.android.exoplayer2.util.TimestampAdjuster; +import java.util.Collections; import java.util.List; /** @@ -61,33 +62,44 @@ public interface TsPayloadReader { final class EsInfo { public final int streamType; - public final List languagesInfo; + public final String language; + public final List dvbSubtitleInfos; public final byte[] descriptorBytes; /** * @param streamType The type of the stream as defined by the * {@link TsExtractor}{@code .TS_STREAM_TYPE_*}. - * @param languagesInfo Language or languages info of the associated program element + * @param language The language of the stream, as defined by ISO/IEC 13818-1, section 2.6.18. + * @param dvbSubtitleInfos Information about DVB subtitles associated to the stream. * @param descriptorBytes The descriptor bytes associated to the stream. */ - public EsInfo(int streamType, List languagesInfo, byte[] descriptorBytes) { + public EsInfo(int streamType, String language, List dvbSubtitleInfos, + byte[] descriptorBytes) { this.streamType = streamType; - this.languagesInfo = languagesInfo; + this.language = language; + this.dvbSubtitleInfos = dvbSubtitleInfos == null ? Collections.emptyList() + : Collections.unmodifiableList(dvbSubtitleInfos); this.descriptorBytes = descriptorBytes; } + } - final class LanguageInfo { + /** + * Holds information about a DVB subtitle. + */ + final class DvbSubtitleInfo { - public String languageCode; - public final byte programElementType; + public final String language; + public final int programElementType; public final List initializationData; - LanguageInfo (String languageCode, byte programElementType, List initializationData) { - this.languageCode = languageCode; + public DvbSubtitleInfo(String language, int programElementType, + List initializationData) { + this.language = language; this.programElementType = programElementType; this.initializationData = initializationData; } + } /** From e1c82fbb1a907809d04ae5ab405d0bd4185beb48 Mon Sep 17 00:00:00 2001 From: tasnimsunny Date: Fri, 21 Apr 2017 12:42:07 -0700 Subject: [PATCH 105/119] Libvpx: configure a way in Medialib to drop frames instead of video lag. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=153858804 --- .../ext/vp9/LibvpxVideoRenderer.java | 29 +++++++++++++++---- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/extensions/vp9/src/main/java/com/google/android/exoplayer2/ext/vp9/LibvpxVideoRenderer.java b/extensions/vp9/src/main/java/com/google/android/exoplayer2/ext/vp9/LibvpxVideoRenderer.java index 627a64a989..2f3517b35a 100644 --- a/extensions/vp9/src/main/java/com/google/android/exoplayer2/ext/vp9/LibvpxVideoRenderer.java +++ b/extensions/vp9/src/main/java/com/google/android/exoplayer2/ext/vp9/LibvpxVideoRenderer.java @@ -262,11 +262,11 @@ public final class LibvpxVideoRenderer extends BaseRenderer { return false; } - // Drop the frame if we're joining and are more than 30ms late, or if we have the next frame - // and that's also late. Else we'll render what we have. - if ((joiningDeadlineMs != C.TIME_UNSET && outputBuffer.timeUs < positionUs - 30000) - || (nextOutputBuffer != null && !nextOutputBuffer.isEndOfStream() - && nextOutputBuffer.timeUs < positionUs)) { + final long nextOutputBufferTimeUs = + nextOutputBuffer != null && !nextOutputBuffer.isEndOfStream() + ? nextOutputBuffer.timeUs : C.TIME_UNSET; + if (shouldDropOutputBuffer( + outputBuffer.timeUs, nextOutputBufferTimeUs, positionUs, joiningDeadlineMs)) { dropBuffer(); return true; } @@ -280,6 +280,25 @@ public final class LibvpxVideoRenderer extends BaseRenderer { return false; } + + /** + * Returns whether the current frame should be dropped. + * + * @param outputBufferTimeUs The timestamp of the current output buffer. + * @param nextOutputBufferTimeUs The timestamp of the next output buffer or + * {@link TIME_UNSET} if the next output buffer is unavailable. + * @param positionUs The current playback position. + * @param joiningDeadlineMs The joining deadline. + * @return Returns whether to drop the current output buffer. + */ + protected boolean shouldDropOutputBuffer(long outputBufferTimeUs, long nextOutputBufferTimeUs, + long positionUs, long joiningDeadlineMs) { + // Drop the frame if we're joining and are more than 30ms late, or if we have the next frame + // and that's also late. Else we'll render what we have. + return (joiningDeadlineMs != C.TIME_UNSET && outputBufferTimeUs < positionUs - 30000) + || (nextOutputBufferTimeUs != C.TIME_UNSET && nextOutputBufferTimeUs < positionUs); + } + private void renderBuffer() { int bufferMode = outputBuffer.mode; boolean renderRgb = bufferMode == VpxDecoder.OUTPUT_MODE_RGB && surface != null; From 4c0b53905402e9c9b9826033b8f58e531c38225d Mon Sep 17 00:00:00 2001 From: olly Date: Mon, 24 Apr 2017 04:35:25 -0700 Subject: [PATCH 106/119] Allow disabling hide on touch for SimpleExoPlayerView Issue: #2501 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=154036726 --- .../exoplayer2/ui/SimpleExoPlayerView.java | 44 +++++++++++++++---- library/ui/src/main/res/values/attrs.xml | 3 +- 2 files changed, 38 insertions(+), 9 deletions(-) diff --git a/library/ui/src/main/java/com/google/android/exoplayer2/ui/SimpleExoPlayerView.java b/library/ui/src/main/java/com/google/android/exoplayer2/ui/SimpleExoPlayerView.java index bdb7539563..bb47226c42 100644 --- a/library/ui/src/main/java/com/google/android/exoplayer2/ui/SimpleExoPlayerView.java +++ b/library/ui/src/main/java/com/google/android/exoplayer2/ui/SimpleExoPlayerView.java @@ -74,12 +74,18 @@ import java.util.List; *

  • Default: {@code null}
  • * * - *
  • {@code use_controller} - Whether playback controls are displayed. + *
  • {@code use_controller} - Whether the playback controls can be shown. *
      *
    • Corresponding method: {@link #setUseController(boolean)}
    • *
    • Default: {@code true}
    • *
    *
  • + *
  • {@code hide_on_touch} - Whether the playback controls are hidden by touch events. + *
      + *
    • Corresponding method: {@link #setControllerHideOnTouch(boolean)}
    • + *
    • Default: {@code true}
    • + *
    + *
  • *
  • {@code resize_mode} - Controls how video and album art is resized within the view. * Valid values are {@code fit}, {@code fixed_width}, {@code fixed_height} and {@code fill}. *
      @@ -190,6 +196,7 @@ public final class SimpleExoPlayerView extends FrameLayout { private boolean useArtwork; private Bitmap defaultArtwork; private int controllerShowTimeoutMs; + private boolean controllerHideOnTouch; public SimpleExoPlayerView(Context context) { this(context, null); @@ -228,6 +235,7 @@ public final class SimpleExoPlayerView extends FrameLayout { int surfaceType = SURFACE_TYPE_SURFACE_VIEW; int resizeMode = AspectRatioFrameLayout.RESIZE_MODE_FIT; int controllerShowTimeoutMs = PlaybackControlView.DEFAULT_SHOW_TIMEOUT_MS; + boolean controllerHideOnTouch = true; if (attrs != null) { TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.SimpleExoPlayerView, 0, 0); @@ -242,6 +250,8 @@ public final class SimpleExoPlayerView extends FrameLayout { resizeMode = a.getInt(R.styleable.SimpleExoPlayerView_resize_mode, resizeMode); controllerShowTimeoutMs = a.getInt(R.styleable.SimpleExoPlayerView_show_timeout, controllerShowTimeoutMs); + controllerHideOnTouch = a.getBoolean(R.styleable.SimpleExoPlayerView_hide_on_touch, + controllerHideOnTouch); } finally { a.recycle(); } @@ -304,6 +314,7 @@ public final class SimpleExoPlayerView extends FrameLayout { this.controller = null; } this.controllerShowTimeoutMs = controller != null ? controllerShowTimeoutMs : 0; + this.controllerHideOnTouch = controllerHideOnTouch; this.useController = useController && controller != null; hideController(); } @@ -407,17 +418,17 @@ public final class SimpleExoPlayerView extends FrameLayout { } /** - * Returns whether the playback controls are enabled. + * Returns whether the playback controls can be shown. */ public boolean getUseController() { return useController; } /** - * Sets whether playback controls are enabled. If set to {@code false} the playback controls are - * never visible and are disconnected from the player. + * Sets whether the playback controls can be shown. If set to {@code false} the playback controls + * are never visible and are disconnected from the player. * - * @param useController Whether playback controls should be enabled. + * @param useController Whether the playback controls can be shown. */ public void setUseController(boolean useController) { Assertions.checkState(!useController || controller != null); @@ -486,6 +497,23 @@ public final class SimpleExoPlayerView extends FrameLayout { this.controllerShowTimeoutMs = controllerShowTimeoutMs; } + /** + * Returns whether the playback controls are hidden by touch events. + */ + public boolean getControllerHideOnTouch() { + return controllerHideOnTouch; + } + + /** + * Sets whether the playback controls are hidden by touch events. + * + * @param controllerHideOnTouch Whether the playback controls are hidden by touch events. + */ + public void setControllerHideOnTouch(boolean controllerHideOnTouch) { + Assertions.checkState(controller != null); + this.controllerHideOnTouch = controllerHideOnTouch; + } + /** * Set the {@link PlaybackControlView.VisibilityListener}. * @@ -573,10 +601,10 @@ public final class SimpleExoPlayerView extends FrameLayout { if (!useController || player == null || ev.getActionMasked() != MotionEvent.ACTION_DOWN) { return false; } - if (controller.isVisible()) { - controller.hide(); - } else { + if (!controller.isVisible()) { maybeShowController(true); + } else if (controllerHideOnTouch) { + controller.hide(); } return true; } diff --git a/library/ui/src/main/res/values/attrs.xml b/library/ui/src/main/res/values/attrs.xml index 46c7eb1a0c..521e535ce3 100644 --- a/library/ui/src/main/res/values/attrs.xml +++ b/library/ui/src/main/res/values/attrs.xml @@ -39,11 +39,12 @@ + + - From 8e8a6a2994296bbe0f36ec040669698cebf4d216 Mon Sep 17 00:00:00 2001 From: olly Date: Mon, 24 Apr 2017 06:51:42 -0700 Subject: [PATCH 107/119] Support efficient switching between SimpleExoPlayerView instances Prior to this change, the only way to switch SimpleExoPlayerView was to do: oldView.setPlayer(null); newView.setPlayer(player); This would cause the video renderer to have to transition through oldSurface->noSurface->newSurface, which is inefficient (noSurface requires platform decoders to be fully released). After this change we support: newView.setPlayer(player); oldView.setPlayer(null); This results in direct oldSurface->newSurface transitions, which are seamless on Android M and above. The change also adds some robustness against developers ending up with strange behavior as a result of clearing the player from a view in a different ordering than we expect w.r.t. registering of other listeners. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=154044976 --- .../android/exoplayer2/SimpleExoPlayer.java | 117 +++++++++++++++--- .../exoplayer2/ui/SimpleExoPlayerView.java | 44 ++++++- 2 files changed, 138 insertions(+), 23 deletions(-) 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 f799df2c5d..173078dad7 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 @@ -239,6 +239,18 @@ public class SimpleExoPlayer implements ExoPlayer { setVideoSurfaceInternal(surface, false); } + /** + * Clears the {@link Surface} onto which video is being rendered if it matches the one passed. + * Else does nothing. + * + * @param surface The surface to clear. + */ + public void clearVideoSurface(Surface surface) { + if (surface != null && surface == this.surface) { + setVideoSurface(null); + } + } + /** * Sets the {@link SurfaceHolder} that holds the {@link Surface} onto which video will be * rendered. The player will track the lifecycle of the surface automatically. @@ -256,6 +268,18 @@ public class SimpleExoPlayer implements ExoPlayer { } } + /** + * Clears the {@link SurfaceHolder} that holds the {@link Surface} onto which video is being + * rendered if it matches the one passed. Else does nothing. + * + * @param surfaceHolder The surface holder to clear. + */ + public void clearVideoSurfaceHolder(SurfaceHolder surfaceHolder) { + if (surfaceHolder != null && surfaceHolder == this.surfaceHolder) { + setVideoSurfaceHolder(null); + } + } + /** * Sets the {@link SurfaceView} onto which video will be rendered. The player will track the * lifecycle of the surface automatically. @@ -263,7 +287,17 @@ public class SimpleExoPlayer implements ExoPlayer { * @param surfaceView The surface view. */ public void setVideoSurfaceView(SurfaceView surfaceView) { - setVideoSurfaceHolder(surfaceView.getHolder()); + setVideoSurfaceHolder(surfaceView == null ? null : surfaceView.getHolder()); + } + + /** + * Clears the {@link SurfaceView} onto which video is being rendered if it matches the one passed. + * Else does nothing. + * + * @param surfaceView The texture view to clear. + */ + public void clearVideoSurfaceView(SurfaceView surfaceView) { + clearVideoSurfaceHolder(surfaceView == null ? null : surfaceView.getHolder()); } /** @@ -287,6 +321,18 @@ public class SimpleExoPlayer implements ExoPlayer { } } + /** + * Clears the {@link TextureView} onto which video is being rendered if it matches the one passed. + * Else does nothing. + * + * @param textureView The texture view to clear. + */ + public void clearVideoTextureView(TextureView textureView) { + if (textureView != null && textureView == this.textureView) { + setVideoTextureView(null); + } + } + /** * Sets the stream type for audio playback (see {@link C.StreamType} and * {@link android.media.AudioTrack#AudioTrack(int, int, int, int, int, int)}). If the stream type @@ -404,6 +450,57 @@ public class SimpleExoPlayer implements ExoPlayer { videoListener = listener; } + /** + * Clears the listener receiving video events if it matches the one passed. Else does nothing. + * + * @param listener The listener to clear. + */ + public void clearVideoListener(VideoListener listener) { + if (videoListener == listener) { + videoListener = null; + } + } + + /** + * Sets an output to receive text events. + * + * @param output The output. + */ + public void setTextOutput(TextRenderer.Output output) { + textOutput = output; + } + + /** + * Clears the output receiving text events if it matches the one passed. Else does nothing. + * + * @param output The output to clear. + */ + public void clearTextOutput(TextRenderer.Output output) { + if (textOutput == output) { + textOutput = null; + } + } + + /** + * Sets a listener to receive metadata events. + * + * @param output The output. + */ + public void setMetadataOutput(MetadataRenderer.Output output) { + metadataOutput = output; + } + + /** + * Clears the output receiving metadata events if it matches the one passed. Else does nothing. + * + * @param output The output to clear. + */ + public void clearMetadataOutput(MetadataRenderer.Output output) { + if (metadataOutput == output) { + metadataOutput = null; + } + } + /** * Sets a listener to receive debug events from the video renderer. * @@ -422,24 +519,6 @@ public class SimpleExoPlayer implements ExoPlayer { audioDebugListener = listener; } - /** - * Sets an output to receive text events. - * - * @param output The output. - */ - public void setTextOutput(TextRenderer.Output output) { - textOutput = output; - } - - /** - * Sets a listener to receive metadata events. - * - * @param output The output. - */ - public void setMetadataOutput(MetadataRenderer.Output output) { - metadataOutput = output; - } - // ExoPlayer implementation @Override diff --git a/library/ui/src/main/java/com/google/android/exoplayer2/ui/SimpleExoPlayerView.java b/library/ui/src/main/java/com/google/android/exoplayer2/ui/SimpleExoPlayerView.java index bb47226c42..fce05f5bc4 100644 --- a/library/ui/src/main/java/com/google/android/exoplayer2/ui/SimpleExoPlayerView.java +++ b/library/ui/src/main/java/com/google/android/exoplayer2/ui/SimpleExoPlayerView.java @@ -21,6 +21,8 @@ import android.content.res.Resources; import android.content.res.TypedArray; import android.graphics.Bitmap; import android.graphics.BitmapFactory; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; import android.util.AttributeSet; import android.view.KeyEvent; import android.view.LayoutInflater; @@ -319,6 +321,30 @@ public final class SimpleExoPlayerView extends FrameLayout { hideController(); } + /** + * Switches the view targeted by a given {@link SimpleExoPlayer}. + * + * @param player The player whose target view is being switched. + * @param oldPlayerView The old view to detach from the player. + * @param newPlayerView The new view to attach to the player. + */ + public static void switchTargetView(@NonNull SimpleExoPlayer player, + @Nullable SimpleExoPlayerView oldPlayerView, @Nullable SimpleExoPlayerView newPlayerView) { + if (oldPlayerView == newPlayerView) { + return; + } + // We attach the new view before detaching the old one because this ordering allows the player + // to swap directly from one surface to another, without transitioning through a state where no + // surface is attached. This is significantly more efficient and achieves a more seamless + // transition when using platform provided video decoders. + if (newPlayerView != null) { + newPlayerView.setPlayer(player); + } + if (oldPlayerView != null) { + oldPlayerView.setPlayer(null); + } + } + /** * Returns the player currently set on this view, or null if no player is set. */ @@ -330,6 +356,12 @@ public final class SimpleExoPlayerView extends FrameLayout { * Set the {@link SimpleExoPlayer} to use. The {@link SimpleExoPlayer#setTextOutput} and * {@link SimpleExoPlayer#setVideoListener} method of the player will be called and previous * assignments are overridden. + *

      + * To transition a {@link SimpleExoPlayer} from targeting one view to another, it's recommended to + * use {@link #switchTargetView(SimpleExoPlayer, SimpleExoPlayerView, SimpleExoPlayerView)} rather + * than this method. If you do wish to use this method directly, be sure to attach the player to + * the new view before calling {@code setPlayer(null)} to detach it from the old one. + * This ordering is significantly more efficient and may allow for more seamless transitions. * * @param player The {@link SimpleExoPlayer} to use. */ @@ -338,10 +370,14 @@ public final class SimpleExoPlayerView extends FrameLayout { return; } if (this.player != null) { - this.player.setTextOutput(null); - this.player.setVideoListener(null); this.player.removeListener(componentListener); - this.player.setVideoSurface(null); + this.player.clearTextOutput(componentListener); + this.player.clearVideoListener(componentListener); + if (surfaceView instanceof TextureView) { + this.player.clearVideoTextureView((TextureView) surfaceView); + } else if (surfaceView instanceof SurfaceView) { + this.player.clearVideoSurfaceView((SurfaceView) surfaceView); + } } this.player = player; if (useController) { @@ -357,8 +393,8 @@ public final class SimpleExoPlayerView extends FrameLayout { player.setVideoSurfaceView((SurfaceView) surfaceView); } player.setVideoListener(componentListener); - player.addListener(componentListener); player.setTextOutput(componentListener); + player.addListener(componentListener); maybeShowController(false); updateForCurrentTrackSelections(); } else { From f0a72c4908c5c8e901f0a628132fea5b992ee3be Mon Sep 17 00:00:00 2001 From: olly Date: Mon, 24 Apr 2017 11:53:50 -0700 Subject: [PATCH 108/119] Fix playback of DASH/VP9/SegmentList DASH content ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=154077149 --- .../extractor/DefaultExtractorsFactory.java | 16 +++++++++- .../extractor/mkv/MatroskaExtractor.java | 31 +++++++++++++++++-- .../source/dash/DefaultDashChunkSource.java | 2 +- 3 files changed, 44 insertions(+), 5 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/DefaultExtractorsFactory.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/DefaultExtractorsFactory.java index 4ea8452956..022ca1277d 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/DefaultExtractorsFactory.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/DefaultExtractorsFactory.java @@ -64,10 +64,24 @@ public final class DefaultExtractorsFactory implements ExtractorsFactory { FLAC_EXTRACTOR_CONSTRUCTOR = flacExtractorConstructor; } + private @MatroskaExtractor.Flags int matroskaFlags; private @FragmentedMp4Extractor.Flags int fragmentedMp4Flags; private @Mp3Extractor.Flags int mp3Flags; private @DefaultTsPayloadReaderFactory.Flags int tsFlags; + /** + * Sets flags for {@link MatroskaExtractor} instances created by the factory. + * + * @see MatroskaExtractor#MatroskaExtractor(int) + * @param flags The flags to use. + * @return The factory, for convenience. + */ + public synchronized DefaultExtractorsFactory setMatroskaExtractorFlags( + @MatroskaExtractor.Flags int flags) { + this.matroskaFlags = flags; + return this; + } + /** * Sets flags for {@link FragmentedMp4Extractor} instances created by the factory. * @@ -110,7 +124,7 @@ public final class DefaultExtractorsFactory implements ExtractorsFactory { @Override public synchronized Extractor[] createExtractors() { Extractor[] extractors = new Extractor[FLAC_EXTRACTOR_CONSTRUCTOR == null ? 11 : 12]; - extractors[0] = new MatroskaExtractor(); + extractors[0] = new MatroskaExtractor(matroskaFlags); extractors[1] = new FragmentedMp4Extractor(fragmentedMp4Flags); extractors[2] = new Mp4Extractor(); extractors[3] = new Mp3Extractor(mp3Flags); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mkv/MatroskaExtractor.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mkv/MatroskaExtractor.java index cf31a0bbd5..9273dc2952 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mkv/MatroskaExtractor.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mkv/MatroskaExtractor.java @@ -15,6 +15,7 @@ */ package com.google.android.exoplayer2.extractor.mkv; +import android.support.annotation.IntDef; import android.util.SparseArray; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.Format; @@ -38,6 +39,8 @@ import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.video.AvcConfig; import com.google.android.exoplayer2.video.HevcConfig; import java.io.IOException; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.util.ArrayList; @@ -64,6 +67,22 @@ public final class MatroskaExtractor implements Extractor { }; + /** + * Flags controlling the behavior of the extractor. + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(flag = true, value = {FLAG_DISABLE_SEEK_FOR_CUES}) + public @interface Flags {} + /** + * Flag to disable seeking for cues. + *

      + * Normally (i.e. when this flag is not set) the extractor will seek to the cues element if its + * position is specified in the seek head and if it's after the first cluster. Setting this flag + * disables seeking to the cues element. If the cues element is after the first cluster then the + * media is treated as being unseekable. + */ + public static final int FLAG_DISABLE_SEEK_FOR_CUES = 1; + private static final int UNSET_ENTRY_ID = -1; private static final int BLOCK_STATE_START = 0; @@ -222,6 +241,7 @@ public final class MatroskaExtractor implements Extractor { private final EbmlReader reader; private final VarintReader varintReader; private final SparseArray tracks; + private final boolean seekForCuesEnabled; // Temporary arrays. private final ParsableByteArray nalStartCode; @@ -289,12 +309,17 @@ public final class MatroskaExtractor implements Extractor { private ExtractorOutput extractorOutput; public MatroskaExtractor() { - this(new DefaultEbmlReader()); + this(0); } - /* package */ MatroskaExtractor(EbmlReader reader) { + public MatroskaExtractor(@Flags int flags) { + this(new DefaultEbmlReader(), flags); + } + + /* package */ MatroskaExtractor(EbmlReader reader, @Flags int flags) { this.reader = reader; this.reader.init(new InnerEbmlReaderOutput()); + seekForCuesEnabled = (flags & FLAG_DISABLE_SEEK_FOR_CUES) == 0; varintReader = new VarintReader(); tracks = new SparseArray<>(); scratch = new ParsableByteArray(4); @@ -448,7 +473,7 @@ public final class MatroskaExtractor implements Extractor { case ID_CLUSTER: if (!sentSeekMap) { // We need to build cues before parsing the cluster. - if (cuesContentPosition != C.POSITION_UNSET) { + if (seekForCuesEnabled && cuesContentPosition != C.POSITION_UNSET) { // We know where the Cues element is located. Seek to request it. seekForCues = true; } else { diff --git a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DefaultDashChunkSource.java b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DefaultDashChunkSource.java index 834f8c147e..e679ef635c 100644 --- a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DefaultDashChunkSource.java +++ b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DefaultDashChunkSource.java @@ -393,7 +393,7 @@ public class DefaultDashChunkSource implements DashChunkSource { if (MimeTypes.APPLICATION_RAWCC.equals(containerMimeType)) { extractor = new RawCcExtractor(representation.format); } else if (mimeTypeIsWebm(containerMimeType)) { - extractor = new MatroskaExtractor(); + extractor = new MatroskaExtractor(MatroskaExtractor.FLAG_DISABLE_SEEK_FOR_CUES); } else { int flags = 0; if (enableEventMessageTrack) { From d3fe20cc64873472ef9c00e186d16a5884d9e453 Mon Sep 17 00:00:00 2001 From: aquilescanta Date: Mon, 24 Apr 2017 12:02:36 -0700 Subject: [PATCH 109/119] Check the type of the (non-initial) loaded HLS playlist This prevents illegal casts. Issue:#2702 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=154078240 --- .../playlist/HlsMediaPlaylistParserTest.java | 1 - .../source/hls/playlist/HlsMasterPlaylist.java | 2 +- .../source/hls/playlist/HlsMediaPlaylist.java | 2 +- .../source/hls/playlist/HlsPlaylist.java | 17 +---------------- .../source/hls/playlist/HlsPlaylistTracker.java | 12 +++++++++--- 5 files changed, 12 insertions(+), 22 deletions(-) diff --git a/library/hls/src/androidTest/java/com/google/android/exoplayer2/source/hls/playlist/HlsMediaPlaylistParserTest.java b/library/hls/src/androidTest/java/com/google/android/exoplayer2/source/hls/playlist/HlsMediaPlaylistParserTest.java index 3d976353cc..e2eb173df8 100644 --- a/library/hls/src/androidTest/java/com/google/android/exoplayer2/source/hls/playlist/HlsMediaPlaylistParserTest.java +++ b/library/hls/src/androidTest/java/com/google/android/exoplayer2/source/hls/playlist/HlsMediaPlaylistParserTest.java @@ -70,7 +70,6 @@ public class HlsMediaPlaylistParserTest extends TestCase { try { HlsPlaylist playlist = new HlsPlaylistParser().parse(playlistUri, inputStream); assertNotNull(playlist); - assertEquals(HlsPlaylist.TYPE_MEDIA, playlist.type); HlsMediaPlaylist mediaPlaylist = (HlsMediaPlaylist) playlist; assertEquals(HlsMediaPlaylist.PLAYLIST_TYPE_VOD, mediaPlaylist.playlistType); diff --git a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsMasterPlaylist.java b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsMasterPlaylist.java index bd0a2db316..5a8c63f609 100644 --- a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsMasterPlaylist.java +++ b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsMasterPlaylist.java @@ -55,7 +55,7 @@ public final class HlsMasterPlaylist extends HlsPlaylist { public HlsMasterPlaylist(String baseUri, List variants, List audios, List subtitles, Format muxedAudioFormat, List muxedCaptionFormats) { - super(baseUri, HlsPlaylist.TYPE_MASTER); + super(baseUri); this.variants = Collections.unmodifiableList(variants); this.audios = Collections.unmodifiableList(audios); this.subtitles = Collections.unmodifiableList(subtitles); diff --git a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsMediaPlaylist.java b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsMediaPlaylist.java index 6b6e15e915..c7708a1d2f 100644 --- a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsMediaPlaylist.java +++ b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsMediaPlaylist.java @@ -97,7 +97,7 @@ public final class HlsMediaPlaylist extends HlsPlaylist { long startTimeUs, boolean hasDiscontinuitySequence, int discontinuitySequence, int mediaSequence, int version, long targetDurationUs, boolean hasEndTag, boolean hasProgramDateTime, Segment initializationSegment, List segments) { - super(baseUri, HlsPlaylist.TYPE_MEDIA); + super(baseUri); this.playlistType = playlistType; this.startTimeUs = startTimeUs; this.hasDiscontinuitySequence = hasDiscontinuitySequence; diff --git a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylist.java b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylist.java index aecd2fb324..7c3d64d701 100644 --- a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylist.java +++ b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylist.java @@ -15,30 +15,15 @@ */ package com.google.android.exoplayer2.source.hls.playlist; -import android.support.annotation.IntDef; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; - /** * Represents an HLS playlist. */ public abstract class HlsPlaylist { - /** - * The type of playlist. - */ - @Retention(RetentionPolicy.SOURCE) - @IntDef({TYPE_MASTER, TYPE_MEDIA}) - public @interface Type {} - public static final int TYPE_MASTER = 0; - public static final int TYPE_MEDIA = 1; - public final String baseUri; - @Type public final int type; - protected HlsPlaylist(String baseUri, @Type int type) { + protected HlsPlaylist(String baseUri) { this.baseUri = baseUri; - this.type = type; } } diff --git a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylistTracker.java b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylistTracker.java index 311f279b96..02a8e3f098 100644 --- a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylistTracker.java +++ b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylistTracker.java @@ -477,9 +477,15 @@ public final class HlsPlaylistTracker implements Loader.Callback loadable, long elapsedRealtimeMs, long loadDurationMs) { - processLoadedPlaylist((HlsMediaPlaylist) loadable.getResult()); - eventDispatcher.loadCompleted(loadable.dataSpec, C.DATA_TYPE_MANIFEST, elapsedRealtimeMs, - loadDurationMs, loadable.bytesLoaded()); + HlsPlaylist result = loadable.getResult(); + if (result instanceof HlsMediaPlaylist) { + processLoadedPlaylist((HlsMediaPlaylist) result); + eventDispatcher.loadCompleted(loadable.dataSpec, C.DATA_TYPE_MANIFEST, elapsedRealtimeMs, + loadDurationMs, loadable.bytesLoaded()); + } else { + onLoadError(loadable, elapsedRealtimeMs, loadDurationMs, + new ParserException("Loaded playlist has unexpected type.")); + } } @Override From e07b8fe7d5b241268bf7d428ff4e0ab348359000 Mon Sep 17 00:00:00 2001 From: anjalibh Date: Mon, 24 Apr 2017 14:33:15 -0700 Subject: [PATCH 110/119] Parse ColorInfo from WebM Container and set it on MediaFormat. * colorSpace, colorRange and colorTransfer are relevant to all videos. * The rest of the fields are relevant only to HDR videos. * Tested on a prototype device that decodes VP9 profile 2, 10 bit. * The Webm spec spells color as 'colour' so the webm id reference use the same spelling. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=154098189 --- .../google/android/exoplayer2/FormatTest.java | 9 +- .../java/com/google/android/exoplayer2/C.java | 62 ++++++ .../com/google/android/exoplayer2/Format.java | 107 ++++++---- .../extractor/mkv/MatroskaExtractor.java | 191 +++++++++++++++++- .../exoplayer2/extractor/mp4/AtomParsers.java | 2 +- .../android/exoplayer2/video/ColorInfo.java | 149 ++++++++++++++ 6 files changed, 476 insertions(+), 44 deletions(-) create mode 100644 library/core/src/main/java/com/google/android/exoplayer2/video/ColorInfo.java diff --git a/library/core/src/androidTest/java/com/google/android/exoplayer2/FormatTest.java b/library/core/src/androidTest/java/com/google/android/exoplayer2/FormatTest.java index e13afceb40..a47a3fb12d 100644 --- a/library/core/src/androidTest/java/com/google/android/exoplayer2/FormatTest.java +++ b/library/core/src/androidTest/java/com/google/android/exoplayer2/FormatTest.java @@ -29,6 +29,7 @@ import com.google.android.exoplayer2.metadata.id3.TextInformationFrame; import com.google.android.exoplayer2.testutil.TestUtil; import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.Util; +import com.google.android.exoplayer2.video.ColorInfo; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Arrays; @@ -61,11 +62,13 @@ public final class FormatTest extends TestCase { Metadata metadata = new Metadata( new TextInformationFrame("id1", "description1", "value1"), new TextInformationFrame("id2", "description2", "value2")); + ColorInfo colorInfo = new ColorInfo(C.COLOR_SPACE_BT709, + C.COLOR_RANGE_LIMITED, C.COLOR_TRANSFER_SDR, new byte[] {1, 2, 3, 4, 5, 6, 7}); Format formatToParcel = new Format("id", MimeTypes.VIDEO_MP4, MimeTypes.VIDEO_H264, null, - 1024, 2048, 1920, 1080, 24, 90, 2, projectionData, C.STEREO_MODE_TOP_BOTTOM, 6, 44100, - C.ENCODING_PCM_24BIT, 1001, 1002, 0, "und", Format.NO_VALUE, Format.OFFSET_SAMPLE_RELATIVE, - INIT_DATA, drmInitData, metadata); + 1024, 2048, 1920, 1080, 24, 90, 2, projectionData, C.STEREO_MODE_TOP_BOTTOM, colorInfo, 6, + 44100, C.ENCODING_PCM_24BIT, 1001, 1002, 0, "und", Format.NO_VALUE, + Format.OFFSET_SAMPLE_RELATIVE, INIT_DATA, drmInitData, metadata); Parcel parcel = Parcel.obtain(); formatToParcel.writeToParcel(parcel, 0); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/C.java b/library/core/src/main/java/com/google/android/exoplayer2/C.java index 6fb3c4500e..35a69df39e 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/C.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/C.java @@ -20,6 +20,7 @@ import android.content.Context; import android.media.AudioFormat; import android.media.AudioManager; import android.media.MediaCodec; +import android.media.MediaFormat; import android.support.annotation.IntDef; import android.view.Surface; import com.google.android.exoplayer2.util.Util; @@ -554,6 +555,67 @@ public final class C { */ public static final int STEREO_MODE_STEREO_MESH = 3; + /** + * Video colorspaces. + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({Format.NO_VALUE, COLOR_SPACE_BT709, COLOR_SPACE_BT601, COLOR_SPACE_BT2020}) + public @interface ColorSpace {} + /** + * @see MediaFormat#COLOR_STANDARD_BT709 + */ + @SuppressWarnings("InlinedApi") + public static final int COLOR_SPACE_BT709 = MediaFormat.COLOR_STANDARD_BT709; + /** + * @see MediaFormat#COLOR_STANDARD_BT601_PAL + */ + @SuppressWarnings("InlinedApi") + public static final int COLOR_SPACE_BT601 = MediaFormat.COLOR_STANDARD_BT601_PAL; + /** + * @see MediaFormat#COLOR_STANDARD_BT2020 + */ + @SuppressWarnings("InlinedApi") + public static final int COLOR_SPACE_BT2020 = MediaFormat.COLOR_STANDARD_BT2020; + + /** + * Video color transfer characteristics. + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({Format.NO_VALUE, COLOR_TRANSFER_SDR, COLOR_TRANSFER_ST2084, COLOR_TRANSFER_HLG}) + public @interface ColorTransfer {} + /** + * @see MediaFormat#COLOR_TRANSFER_SDR_VIDEO + */ + @SuppressWarnings("InlinedApi") + public static final int COLOR_TRANSFER_SDR = MediaFormat.COLOR_TRANSFER_SDR_VIDEO; + /** + * @see MediaFormat#COLOR_TRANSFER_ST2084 + */ + @SuppressWarnings("InlinedApi") + public static final int COLOR_TRANSFER_ST2084 = MediaFormat.COLOR_TRANSFER_ST2084; + /** + * @see MediaFormat#COLOR_TRANSFER_HLG + */ + @SuppressWarnings("InlinedApi") + public static final int COLOR_TRANSFER_HLG = MediaFormat.COLOR_TRANSFER_HLG; + + /** + * Video color range. + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({Format.NO_VALUE, COLOR_RANGE_LIMITED, COLOR_RANGE_FULL}) + public @interface ColorRange {} + /** + * @see MediaFormat#COLOR_RANGE_LIMITED + */ + @SuppressWarnings("InlinedApi") + public static final int COLOR_RANGE_LIMITED = MediaFormat.COLOR_RANGE_LIMITED; + /** + * @see MediaFormat#COLOR_RANGE_FULL + */ + @SuppressWarnings("InlinedApi") + public static final int COLOR_RANGE_FULL = MediaFormat.COLOR_RANGE_FULL; + /** * Priority for media playback. * diff --git a/library/core/src/main/java/com/google/android/exoplayer2/Format.java b/library/core/src/main/java/com/google/android/exoplayer2/Format.java index c29c8fbd83..0bffd28ba5 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/Format.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/Format.java @@ -24,6 +24,7 @@ import com.google.android.exoplayer2.drm.DrmInitData; import com.google.android.exoplayer2.metadata.Metadata; import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.Util; +import com.google.android.exoplayer2.video.ColorInfo; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Arrays; @@ -128,6 +129,10 @@ public final class Format implements Parcelable { * The projection data for 360/VR video, or null if not applicable. */ public final byte[] projectionData; + /** + * The color metadata associated with the video, helps with accurate color reproduction. + */ + public final ColorInfo colorInfo; // Audio specific. @@ -192,7 +197,7 @@ public final class Format implements Parcelable { String sampleMimeType, String codecs, int bitrate, int width, int height, float frameRate, List initializationData, @C.SelectionFlags int selectionFlags) { return new Format(id, containerMimeType, sampleMimeType, codecs, bitrate, NO_VALUE, width, - height, frameRate, NO_VALUE, NO_VALUE, null, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, + height, frameRate, NO_VALUE, NO_VALUE, null, NO_VALUE, null, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, selectionFlags, null, NO_VALUE, OFFSET_SAMPLE_RELATIVE, initializationData, null, null); } @@ -210,17 +215,18 @@ public final class Format implements Parcelable { DrmInitData drmInitData) { return createVideoSampleFormat(id, sampleMimeType, codecs, bitrate, maxInputSize, width, height, frameRate, initializationData, rotationDegrees, pixelWidthHeightRatio, null, - NO_VALUE, drmInitData); + NO_VALUE, null, drmInitData); } public static Format createVideoSampleFormat(String id, String sampleMimeType, String codecs, int bitrate, int maxInputSize, int width, int height, float frameRate, List initializationData, int rotationDegrees, float pixelWidthHeightRatio, - byte[] projectionData, @C.StereoMode int stereoMode, DrmInitData drmInitData) { + byte[] projectionData, @C.StereoMode int stereoMode, ColorInfo colorInfo, + DrmInitData drmInitData) { return new Format(id, null, sampleMimeType, codecs, bitrate, maxInputSize, width, height, - frameRate, rotationDegrees, pixelWidthHeightRatio, projectionData, stereoMode, NO_VALUE, - NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, 0, null, NO_VALUE, OFFSET_SAMPLE_RELATIVE, - initializationData, drmInitData, null); + frameRate, rotationDegrees, pixelWidthHeightRatio, projectionData, stereoMode, + colorInfo, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, 0, null, NO_VALUE, + OFFSET_SAMPLE_RELATIVE, initializationData, drmInitData, null); } // Audio. @@ -229,8 +235,8 @@ public final class Format implements Parcelable { String sampleMimeType, String codecs, int bitrate, int channelCount, int sampleRate, List initializationData, @C.SelectionFlags int selectionFlags, String language) { return new Format(id, containerMimeType, sampleMimeType, codecs, bitrate, NO_VALUE, NO_VALUE, - NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, null, NO_VALUE, channelCount, sampleRate, NO_VALUE, - NO_VALUE, NO_VALUE, selectionFlags, language, NO_VALUE, OFFSET_SAMPLE_RELATIVE, + NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, null, NO_VALUE, null, channelCount, sampleRate, + NO_VALUE, NO_VALUE, NO_VALUE, selectionFlags, language, NO_VALUE, OFFSET_SAMPLE_RELATIVE, initializationData, null, null); } @@ -257,7 +263,7 @@ public final class Format implements Parcelable { List initializationData, DrmInitData drmInitData, @C.SelectionFlags int selectionFlags, String language, Metadata metadata) { return new Format(id, null, sampleMimeType, codecs, bitrate, maxInputSize, NO_VALUE, NO_VALUE, - NO_VALUE, NO_VALUE, NO_VALUE, null, NO_VALUE, channelCount, sampleRate, pcmEncoding, + NO_VALUE, NO_VALUE, NO_VALUE, null, NO_VALUE, null, channelCount, sampleRate, pcmEncoding, encoderDelay, encoderPadding, selectionFlags, language, NO_VALUE, OFFSET_SAMPLE_RELATIVE, initializationData, drmInitData, metadata); } @@ -275,8 +281,8 @@ public final class Format implements Parcelable { String sampleMimeType, String codecs, int bitrate, @C.SelectionFlags int selectionFlags, String language, int accessibilityChannel) { return new Format(id, containerMimeType, sampleMimeType, codecs, bitrate, NO_VALUE, NO_VALUE, - NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, null, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, - NO_VALUE, NO_VALUE, selectionFlags, language, accessibilityChannel, + NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, null, NO_VALUE, null, NO_VALUE, NO_VALUE, + NO_VALUE, NO_VALUE, NO_VALUE, selectionFlags, language, accessibilityChannel, OFFSET_SAMPLE_RELATIVE, null, null, null); } @@ -305,7 +311,7 @@ public final class Format implements Parcelable { int accessibilityChannel, DrmInitData drmInitData, long subsampleOffsetUs, List initializationData) { return new Format(id, null, sampleMimeType, codecs, bitrate, NO_VALUE, NO_VALUE, NO_VALUE, - NO_VALUE, NO_VALUE, NO_VALUE, null, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, + NO_VALUE, NO_VALUE, NO_VALUE, null, NO_VALUE, null, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, selectionFlags, language, accessibilityChannel, subsampleOffsetUs, initializationData, drmInitData, null); } @@ -315,7 +321,7 @@ public final class Format implements Parcelable { public static Format createImageSampleFormat(String id, String sampleMimeType, String codecs, int bitrate, List initializationData, String language, DrmInitData drmInitData) { return new Format(id, null, sampleMimeType, codecs, bitrate, NO_VALUE, NO_VALUE, NO_VALUE, - NO_VALUE, NO_VALUE, NO_VALUE, null, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, + NO_VALUE, NO_VALUE, NO_VALUE, null, NO_VALUE, null, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, 0, language, NO_VALUE, OFFSET_SAMPLE_RELATIVE, initializationData, drmInitData, null); } @@ -326,7 +332,7 @@ public final class Format implements Parcelable { String sampleMimeType, String codecs, int bitrate, @C.SelectionFlags int selectionFlags, String language) { return new Format(id, containerMimeType, sampleMimeType, codecs, bitrate, NO_VALUE, NO_VALUE, - NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, null, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, + NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, null, NO_VALUE, null, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, selectionFlags, language, NO_VALUE, OFFSET_SAMPLE_RELATIVE, null, null, null); } @@ -334,22 +340,22 @@ public final class Format implements Parcelable { public static Format createSampleFormat(String id, String sampleMimeType, long subsampleOffsetUs) { return new Format(id, null, sampleMimeType, null, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, - NO_VALUE, NO_VALUE, NO_VALUE, null, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, + NO_VALUE, NO_VALUE, NO_VALUE, null, NO_VALUE, null, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, 0, null, NO_VALUE, subsampleOffsetUs, null, null, null); } public static Format createSampleFormat(String id, String sampleMimeType, String codecs, int bitrate, DrmInitData drmInitData) { return new Format(id, null, sampleMimeType, codecs, bitrate, NO_VALUE, NO_VALUE, NO_VALUE, - NO_VALUE, NO_VALUE, NO_VALUE, null, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, + NO_VALUE, NO_VALUE, NO_VALUE, null, NO_VALUE, null, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, 0, null, NO_VALUE, OFFSET_SAMPLE_RELATIVE, null, drmInitData, null); } /* package */ Format(String id, String containerMimeType, String sampleMimeType, String codecs, int bitrate, int maxInputSize, int width, int height, float frameRate, int rotationDegrees, float pixelWidthHeightRatio, byte[] projectionData, @C.StereoMode int stereoMode, - int channelCount, int sampleRate, @C.PcmEncoding int pcmEncoding, int encoderDelay, - int encoderPadding, @C.SelectionFlags int selectionFlags, String language, + ColorInfo colorInfo, int channelCount, int sampleRate, @C.PcmEncoding int pcmEncoding, + int encoderDelay, int encoderPadding, @C.SelectionFlags int selectionFlags, String language, int accessibilityChannel, long subsampleOffsetUs, List initializationData, DrmInitData drmInitData, Metadata metadata) { this.id = id; @@ -365,6 +371,7 @@ public final class Format implements Parcelable { this.pixelWidthHeightRatio = pixelWidthHeightRatio; this.projectionData = projectionData; this.stereoMode = stereoMode; + this.colorInfo = colorInfo; this.channelCount = channelCount; this.sampleRate = sampleRate; this.pcmEncoding = pcmEncoding; @@ -396,6 +403,7 @@ public final class Format implements Parcelable { boolean hasProjectionData = in.readInt() != 0; projectionData = hasProjectionData ? in.createByteArray() : null; stereoMode = in.readInt(); + colorInfo = in.readParcelable(ColorInfo.class.getClassLoader()); channelCount = in.readInt(); sampleRate = in.readInt(); pcmEncoding = in.readInt(); @@ -417,26 +425,26 @@ public final class Format implements Parcelable { public Format copyWithMaxInputSize(int maxInputSize) { return new Format(id, containerMimeType, sampleMimeType, codecs, bitrate, maxInputSize, width, height, frameRate, rotationDegrees, pixelWidthHeightRatio, projectionData, - stereoMode, channelCount, sampleRate, pcmEncoding, encoderDelay, encoderPadding, - selectionFlags, language, accessibilityChannel, subsampleOffsetUs, initializationData, - drmInitData, metadata); + stereoMode, colorInfo, channelCount, sampleRate, pcmEncoding, encoderDelay, + encoderPadding, selectionFlags, language, accessibilityChannel, subsampleOffsetUs, + initializationData, drmInitData, metadata); } public Format copyWithSubsampleOffsetUs(long subsampleOffsetUs) { return new Format(id, containerMimeType, sampleMimeType, codecs, bitrate, maxInputSize, width, height, frameRate, rotationDegrees, pixelWidthHeightRatio, projectionData, - stereoMode, channelCount, sampleRate, pcmEncoding, encoderDelay, encoderPadding, - selectionFlags, language, accessibilityChannel, subsampleOffsetUs, initializationData, - drmInitData, metadata); + stereoMode, colorInfo, channelCount, sampleRate, pcmEncoding, encoderDelay, + encoderPadding, selectionFlags, language, accessibilityChannel, subsampleOffsetUs, + initializationData, drmInitData, metadata); } public Format copyWithContainerInfo(String id, String codecs, int bitrate, int width, int height, @C.SelectionFlags int selectionFlags, String language) { return new Format(id, containerMimeType, sampleMimeType, codecs, bitrate, maxInputSize, width, height, frameRate, rotationDegrees, pixelWidthHeightRatio, projectionData, - stereoMode, channelCount, sampleRate, pcmEncoding, encoderDelay, encoderPadding, - selectionFlags, language, accessibilityChannel, subsampleOffsetUs, initializationData, - drmInitData, metadata); + stereoMode, colorInfo, channelCount, sampleRate, pcmEncoding, encoderDelay, + encoderPadding, selectionFlags, language, accessibilityChannel, subsampleOffsetUs, + initializationData, drmInitData, metadata); } @SuppressWarnings("ReferenceEquality") @@ -455,33 +463,33 @@ public final class Format implements Parcelable { : this.drmInitData; return new Format(id, containerMimeType, sampleMimeType, codecs, bitrate, maxInputSize, width, height, frameRate, rotationDegrees, pixelWidthHeightRatio, projectionData, stereoMode, - channelCount, sampleRate, pcmEncoding, encoderDelay, encoderPadding, selectionFlags, - language, accessibilityChannel, subsampleOffsetUs, initializationData, drmInitData, - metadata); + colorInfo, channelCount, sampleRate, pcmEncoding, encoderDelay, encoderPadding, + selectionFlags, language, accessibilityChannel, subsampleOffsetUs, initializationData, + drmInitData, metadata); } public Format copyWithGaplessInfo(int encoderDelay, int encoderPadding) { return new Format(id, containerMimeType, sampleMimeType, codecs, bitrate, maxInputSize, width, height, frameRate, rotationDegrees, pixelWidthHeightRatio, projectionData, - stereoMode, channelCount, sampleRate, pcmEncoding, encoderDelay, encoderPadding, - selectionFlags, language, accessibilityChannel, subsampleOffsetUs, initializationData, - drmInitData, metadata); + stereoMode, colorInfo, channelCount, sampleRate, pcmEncoding, encoderDelay, + encoderPadding, selectionFlags, language, accessibilityChannel, subsampleOffsetUs, + initializationData, drmInitData, metadata); } public Format copyWithDrmInitData(DrmInitData drmInitData) { return new Format(id, containerMimeType, sampleMimeType, codecs, bitrate, maxInputSize, width, height, frameRate, rotationDegrees, pixelWidthHeightRatio, projectionData, - stereoMode, channelCount, sampleRate, pcmEncoding, encoderDelay, encoderPadding, - selectionFlags, language, accessibilityChannel, subsampleOffsetUs, initializationData, - drmInitData, metadata); + stereoMode, colorInfo, channelCount, sampleRate, pcmEncoding, encoderDelay, + encoderPadding, selectionFlags, language, accessibilityChannel, subsampleOffsetUs, + initializationData, drmInitData, metadata); } public Format copyWithMetadata(Metadata metadata) { return new Format(id, containerMimeType, sampleMimeType, codecs, bitrate, maxInputSize, width, height, frameRate, rotationDegrees, pixelWidthHeightRatio, projectionData, - stereoMode, channelCount, sampleRate, pcmEncoding, encoderDelay, encoderPadding, - selectionFlags, language, accessibilityChannel, subsampleOffsetUs, initializationData, - drmInitData, metadata); + stereoMode, colorInfo, channelCount, sampleRate, pcmEncoding, encoderDelay, + encoderPadding, selectionFlags, language, accessibilityChannel, subsampleOffsetUs, + initializationData, drmInitData, metadata); } /** @@ -513,6 +521,7 @@ public final class Format implements Parcelable { for (int i = 0; i < initializationData.size(); i++) { format.setByteBuffer("csd-" + i, ByteBuffer.wrap(initializationData.get(i))); } + maybeSetColorInfoV24(format, colorInfo); return format; } @@ -569,6 +578,7 @@ public final class Format implements Parcelable { || !Util.areEqual(codecs, other.codecs) || !Util.areEqual(drmInitData, other.drmInitData) || !Util.areEqual(metadata, other.metadata) + || !Util.areEqual(colorInfo, other.colorInfo) || !Arrays.equals(projectionData, other.projectionData) || initializationData.size() != other.initializationData.size()) { return false; @@ -581,6 +591,17 @@ public final class Format implements Parcelable { return true; } + @TargetApi(24) + private static void maybeSetColorInfoV24(MediaFormat format, ColorInfo colorInfo) { + if (colorInfo == null) { + return; + } + maybeSetIntegerV16(format, MediaFormat.KEY_COLOR_TRANSFER, colorInfo.colorTransfer); + maybeSetIntegerV16(format, MediaFormat.KEY_COLOR_STANDARD, colorInfo.colorSpace); + maybeSetIntegerV16(format, MediaFormat.KEY_COLOR_RANGE, colorInfo.colorRange); + maybeSetByteBufferV16(format, MediaFormat.KEY_HDR_STATIC_INFO, colorInfo.hdrStaticInfo); + } + @TargetApi(16) private static void maybeSetStringV16(MediaFormat format, String key, String value) { if (value != null) { @@ -602,6 +623,13 @@ public final class Format implements Parcelable { } } + @TargetApi(16) + private static void maybeSetByteBufferV16(MediaFormat format, String key, byte[] value) { + if (value != null) { + format.setByteBuffer(key, ByteBuffer.wrap(value)); + } + } + // Utility methods /** @@ -659,6 +687,7 @@ public final class Format implements Parcelable { dest.writeByteArray(projectionData); } dest.writeInt(stereoMode); + dest.writeParcelable(colorInfo, flags); dest.writeInt(channelCount); dest.writeInt(sampleRate); dest.writeInt(pcmEncoding); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mkv/MatroskaExtractor.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mkv/MatroskaExtractor.java index 9273dc2952..8f3abf4688 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mkv/MatroskaExtractor.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mkv/MatroskaExtractor.java @@ -37,6 +37,7 @@ import com.google.android.exoplayer2.util.NalUnitUtil; import com.google.android.exoplayer2.util.ParsableByteArray; import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.video.AvcConfig; +import com.google.android.exoplayer2.video.ColorInfo; import com.google.android.exoplayer2.video.HevcConfig; import java.io.IOException; import java.lang.annotation.Retention; @@ -187,6 +188,23 @@ public final class MatroskaExtractor implements Extractor { private static final int ID_PROJECTION = 0x7670; private static final int ID_PROJECTION_PRIVATE = 0x7672; private static final int ID_STEREO_MODE = 0x53B8; + private static final int ID_COLOUR = 0x55B0; + private static final int ID_COLOUR_RANGE = 0x55B9; + private static final int ID_COLOUR_TRANSFER = 0x55BA; + private static final int ID_COLOUR_PRIMARIES = 0x55BB; + private static final int ID_MAX_CLL = 0x55BC; + private static final int ID_MAX_FALL = 0x55BD; + private static final int ID_MASTERING_METADATA = 0x55D0; + private static final int ID_PRIMARY_R_CHROMATICITY_X = 0x55D1; + private static final int ID_PRIMARY_R_CHROMATICITY_Y = 0x55D2; + private static final int ID_PRIMARY_G_CHROMATICITY_X = 0x55D3; + private static final int ID_PRIMARY_G_CHROMATICITY_Y = 0x55D4; + private static final int ID_PRIMARY_B_CHROMATICITY_X = 0x55D5; + private static final int ID_PRIMARY_B_CHROMATICITY_Y = 0x55D6; + private static final int ID_WHITE_POINT_CHROMATICITY_X = 0x55D7; + private static final int ID_WHITE_POINT_CHROMATICITY_Y = 0x55D8; + private static final int ID_LUMNINANCE_MAX = 0x55D9; + private static final int ID_LUMNINANCE_MIN = 0x55DA; private static final int LACING_NONE = 0; private static final int LACING_XIPH = 1; @@ -393,6 +411,8 @@ public final class MatroskaExtractor implements Extractor { case ID_CUE_TRACK_POSITIONS: case ID_BLOCK_GROUP: case ID_PROJECTION: + case ID_COLOUR: + case ID_MASTERING_METADATA: return EbmlReader.TYPE_MASTER; case ID_EBML_READ_VERSION: case ID_DOC_TYPE_READ_VERSION: @@ -423,6 +443,11 @@ public final class MatroskaExtractor implements Extractor { case ID_CUE_CLUSTER_POSITION: case ID_REFERENCE_BLOCK: case ID_STEREO_MODE: + case ID_COLOUR_RANGE: + case ID_COLOUR_TRANSFER: + case ID_COLOUR_PRIMARIES: + case ID_MAX_CLL: + case ID_MAX_FALL: return EbmlReader.TYPE_UNSIGNED_INT; case ID_DOC_TYPE: case ID_CODEC_ID: @@ -438,6 +463,16 @@ public final class MatroskaExtractor implements Extractor { return EbmlReader.TYPE_BINARY; case ID_DURATION: case ID_SAMPLING_FREQUENCY: + case ID_PRIMARY_R_CHROMATICITY_X: + case ID_PRIMARY_R_CHROMATICITY_Y: + case ID_PRIMARY_G_CHROMATICITY_X: + case ID_PRIMARY_G_CHROMATICITY_Y: + case ID_PRIMARY_B_CHROMATICITY_X: + case ID_PRIMARY_B_CHROMATICITY_Y: + case ID_WHITE_POINT_CHROMATICITY_X: + case ID_WHITE_POINT_CHROMATICITY_Y: + case ID_LUMNINANCE_MAX: + case ID_LUMNINANCE_MIN: return EbmlReader.TYPE_FLOAT; default: return EbmlReader.TYPE_UNKNOWN; @@ -496,6 +531,9 @@ public final class MatroskaExtractor implements Extractor { case ID_TRACK_ENTRY: currentTrack = new Track(); break; + case ID_MASTERING_METADATA: + currentTrack.hasColorInfo = true; + break; default: break; } @@ -706,6 +744,60 @@ public final class MatroskaExtractor implements Extractor { break; } break; + case ID_COLOUR_PRIMARIES: + currentTrack.hasColorInfo = true; + switch ((int) value) { + case 1: + currentTrack.colorSpace = C.COLOR_SPACE_BT709; + break; + case 4: // BT.470M. + case 5: // BT.470BG. + case 6: // SMPTE 170M. + case 7: // SMPTE 240M. + currentTrack.colorSpace = C.COLOR_SPACE_BT601; + break; + case 9: + currentTrack.colorSpace = C.COLOR_SPACE_BT2020; + break; + default: + break; + } + break; + case ID_COLOUR_TRANSFER: + switch ((int) value) { + case 1: // BT.709. + case 6: // SMPTE 170M. + case 7: // SMPTE 240M. + currentTrack.colorTransfer = C.COLOR_TRANSFER_SDR; + break; + case 16: + currentTrack.colorTransfer = C.COLOR_TRANSFER_ST2084; + break; + case 18: + currentTrack.colorTransfer = C.COLOR_TRANSFER_HLG; + break; + default: + break; + } + break; + case ID_COLOUR_RANGE: + switch((int) value) { + case 1: // Broadcast range. + currentTrack.colorRange = C.COLOR_RANGE_LIMITED; + break; + case 2: + currentTrack.colorRange = C.COLOR_RANGE_FULL; + break; + default: + break; + } + break; + case ID_MAX_CLL: + currentTrack.maxContentLuminance = (int) value; + break; + case ID_MAX_FALL: + currentTrack.maxFrameAverageLuminance = (int) value; + break; default: break; } @@ -719,6 +811,36 @@ public final class MatroskaExtractor implements Extractor { case ID_SAMPLING_FREQUENCY: currentTrack.sampleRate = (int) value; break; + case ID_PRIMARY_R_CHROMATICITY_X: + currentTrack.primaryRChromaticityX = (float) value; + break; + case ID_PRIMARY_R_CHROMATICITY_Y: + currentTrack.primaryRChromaticityY = (float) value; + break; + case ID_PRIMARY_G_CHROMATICITY_X: + currentTrack.primaryGChromaticityX = (float) value; + break; + case ID_PRIMARY_G_CHROMATICITY_Y: + currentTrack.primaryGChromaticityY = (float) value; + break; + case ID_PRIMARY_B_CHROMATICITY_X: + currentTrack.primaryBChromaticityX = (float) value; + break; + case ID_PRIMARY_B_CHROMATICITY_Y: + currentTrack.primaryBChromaticityY = (float) value; + break; + case ID_WHITE_POINT_CHROMATICITY_X: + currentTrack.whitePointChromaticityX = (float) value; + break; + case ID_WHITE_POINT_CHROMATICITY_Y: + currentTrack.whitePointChromaticityY = (float) value; + break; + case ID_LUMNINANCE_MAX: + currentTrack.maxMasteringLuminance = (float) value; + break; + case ID_LUMNINANCE_MIN: + currentTrack.minMasteringLuminance = (float) value; + break; default: break; } @@ -1330,6 +1452,16 @@ public final class MatroskaExtractor implements Extractor { private static final class Track { private static final int DISPLAY_UNIT_PIXELS = 0; + private static final int MAX_CHROMATICITY = 50000; // Defined in CTA-861.3. + /** + * Default max content light level (CLL) that should be encoded into hdrStaticInfo. + */ + private static final int DEFAULT_MAX_CLL = 1000; // nits. + + /** + * Default frame-average light level (FALL) that should be encoded into hdrStaticInfo. + */ + private static final int DEFAULT_MAX_FALL = 200; // nits. // Common elements. public String codecId; @@ -1351,6 +1483,25 @@ public final class MatroskaExtractor implements Extractor { public byte[] projectionData = null; @C.StereoMode public int stereoMode = Format.NO_VALUE; + public boolean hasColorInfo = false; + @C.ColorSpace + public int colorSpace = Format.NO_VALUE; + @C.ColorTransfer + public int colorTransfer = Format.NO_VALUE; + @C.ColorRange + public int colorRange = Format.NO_VALUE; + public int maxContentLuminance = DEFAULT_MAX_CLL; + public int maxFrameAverageLuminance = DEFAULT_MAX_FALL; + public float primaryRChromaticityX = Format.NO_VALUE; + public float primaryRChromaticityY = Format.NO_VALUE; + public float primaryGChromaticityX = Format.NO_VALUE; + public float primaryGChromaticityY = Format.NO_VALUE; + public float primaryBChromaticityX = Format.NO_VALUE; + public float primaryBChromaticityY = Format.NO_VALUE; + public float whitePointChromaticityX = Format.NO_VALUE; + public float whitePointChromaticityY = Format.NO_VALUE; + public float maxMasteringLuminance = Format.NO_VALUE; + public float minMasteringLuminance = Format.NO_VALUE; // Audio elements. Initially set to their default values. public int channelCount = 1; @@ -1520,9 +1671,15 @@ public final class MatroskaExtractor implements Extractor { if (displayWidth != Format.NO_VALUE && displayHeight != Format.NO_VALUE) { pixelWidthHeightRatio = ((float) (height * displayWidth)) / (width * displayHeight); } + ColorInfo colorInfo = null; + if (hasColorInfo) { + byte[] hdrStaticInfo = getHdrStaticInfo(); + colorInfo = new ColorInfo(colorSpace, colorRange, colorTransfer, hdrStaticInfo); + } format = Format.createVideoSampleFormat(Integer.toString(trackId), mimeType, null, Format.NO_VALUE, maxInputSize, width, height, Format.NO_VALUE, initializationData, - Format.NO_VALUE, pixelWidthHeightRatio, projectionData, stereoMode, drmInitData); + Format.NO_VALUE, pixelWidthHeightRatio, projectionData, stereoMode, colorInfo, + drmInitData); } else if (MimeTypes.APPLICATION_SUBRIP.equals(mimeType)) { type = C.TRACK_TYPE_TEXT; format = Format.createTextSampleFormat(Integer.toString(trackId), mimeType, null, @@ -1541,6 +1698,38 @@ public final class MatroskaExtractor implements Extractor { this.output.format(format); } + /** + * Returns the HDR Static Info as defined in CTA-861.3. + */ + private byte[] getHdrStaticInfo() { + // Are all fields present. + if (primaryRChromaticityX == Format.NO_VALUE || primaryRChromaticityY == Format.NO_VALUE + || primaryGChromaticityX == Format.NO_VALUE || primaryGChromaticityY == Format.NO_VALUE + || primaryBChromaticityX == Format.NO_VALUE || primaryBChromaticityY == Format.NO_VALUE + || whitePointChromaticityX == Format.NO_VALUE + || whitePointChromaticityY == Format.NO_VALUE || maxMasteringLuminance == Format.NO_VALUE + || minMasteringLuminance == Format.NO_VALUE) { + return null; + } + + byte[] hdrStaticInfoData = new byte[25]; + ByteBuffer hdrStaticInfo = ByteBuffer.wrap(hdrStaticInfoData); + hdrStaticInfo.put((byte) 0); // Type. + hdrStaticInfo.putShort((short) ((primaryRChromaticityX * MAX_CHROMATICITY) + 0.5f)); + hdrStaticInfo.putShort((short) ((primaryRChromaticityY * MAX_CHROMATICITY) + 0.5f)); + hdrStaticInfo.putShort((short) ((primaryGChromaticityX * MAX_CHROMATICITY) + 0.5f)); + hdrStaticInfo.putShort((short) ((primaryGChromaticityY * MAX_CHROMATICITY) + 0.5f)); + hdrStaticInfo.putShort((short) ((primaryBChromaticityX * MAX_CHROMATICITY) + 0.5f)); + hdrStaticInfo.putShort((short) ((primaryBChromaticityY * MAX_CHROMATICITY) + 0.5f)); + hdrStaticInfo.putShort((short) ((whitePointChromaticityX * MAX_CHROMATICITY) + 0.5f)); + hdrStaticInfo.putShort((short) ((whitePointChromaticityY * MAX_CHROMATICITY) + 0.5f)); + hdrStaticInfo.putShort((short) (maxMasteringLuminance + 0.5f)); + hdrStaticInfo.putShort((short) (minMasteringLuminance + 0.5f)); + hdrStaticInfo.putShort((short) maxContentLuminance); + hdrStaticInfo.putShort((short) maxFrameAverageLuminance); + return hdrStaticInfoData; + } + /** * Builds initialization data for a {@link Format} from FourCC codec private data. *

      diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/AtomParsers.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/AtomParsers.java index 4b30b383ec..474ba65d86 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/AtomParsers.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/AtomParsers.java @@ -762,7 +762,7 @@ import java.util.List; out.format = Format.createVideoSampleFormat(Integer.toString(trackId), mimeType, null, Format.NO_VALUE, Format.NO_VALUE, width, height, Format.NO_VALUE, initializationData, - rotationDegrees, pixelWidthHeightRatio, projectionData, stereoMode, drmInitData); + rotationDegrees, pixelWidthHeightRatio, projectionData, stereoMode, null, drmInitData); } /** diff --git a/library/core/src/main/java/com/google/android/exoplayer2/video/ColorInfo.java b/library/core/src/main/java/com/google/android/exoplayer2/video/ColorInfo.java new file mode 100644 index 0000000000..7bdc43f85c --- /dev/null +++ b/library/core/src/main/java/com/google/android/exoplayer2/video/ColorInfo.java @@ -0,0 +1,149 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.exoplayer2.video; + +import android.os.Parcel; +import android.os.Parcelable; +import com.google.android.exoplayer2.C; +import com.google.android.exoplayer2.Format; +import java.util.Arrays; + +/** + * Stores color info. + */ +public final class ColorInfo implements Parcelable { + + /** + * The color space of the video. Valid values are {@link C#COLOR_SPACE_BT601}, {@link + * C#COLOR_SPACE_BT709}, {@link C#COLOR_SPACE_BT2020} or {@link Format#NO_VALUE} if unknown. + */ + @C.ColorSpace + public final int colorSpace; + + /** + * The color range of the video. Valid values are {@link C#COLOR_RANGE_LIMITED}, {@link + * C#COLOR_RANGE_FULL} or {@link Format#NO_VALUE} if unknown. + */ + @C.ColorRange + public final int colorRange; + + /** + * The color transfer characteristicks of the video. Valid values are {@link + * C#COLOR_TRANSFER_HLG}, {@link C#COLOR_TRANSFER_ST2084}, {@link C#COLOR_TRANSFER_SDR} or {@link + * Format#NO_VALUE} if unknown. + */ + @C.ColorTransfer + public final int colorTransfer; + + /** + * HdrStaticInfo as defined in CTA-861.3. + */ + public final byte[] hdrStaticInfo; + + // Lazily initialized hashcode. + private int hashCode; + + /** + * Constructs the ColorInfo. + * + * @param colorSpace The color space of the video. + * @param colorRange The color range of the video. + * @param colorTransfer The color transfer characteristics of the video. + * @param hdrStaticInfo HdrStaticInfo as defined in CTA-861.3. + */ + public ColorInfo(@C.ColorSpace int colorSpace, @C.ColorRange int colorRange, + @C.ColorTransfer int colorTransfer, byte[] hdrStaticInfo) { + this.colorSpace = colorSpace; + this.colorRange = colorRange; + this.colorTransfer = colorTransfer; + this.hdrStaticInfo = hdrStaticInfo; + } + + @SuppressWarnings("ResourceType") + /* package */ ColorInfo(Parcel in) { + colorSpace = in.readInt(); + colorRange = in.readInt(); + colorTransfer = in.readInt(); + boolean hasHdrStaticInfo = in.readInt() != 0; + hdrStaticInfo = hasHdrStaticInfo ? in.createByteArray() : null; + } + + // Parcelable implementation. + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + ColorInfo other = (ColorInfo) obj; + if (colorSpace != other.colorSpace || colorRange != other.colorRange + || colorTransfer != other.colorTransfer + || !Arrays.equals(hdrStaticInfo, other.hdrStaticInfo)) { + return false; + } + return true; + } + + @Override + public String toString() { + return "ColorInfo(" + colorSpace + ", " + colorRange + ", " + colorTransfer + + ", " + (hdrStaticInfo != null) + ")"; + } + + @Override + public int hashCode() { + if (hashCode == 0) { + int result = 17; + result = 31 * result + colorSpace; + result = 31 * result + colorRange; + result = 31 * result + colorTransfer; + result = 31 * result + Arrays.hashCode(hdrStaticInfo); + hashCode = result; + } + return hashCode; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(colorSpace); + dest.writeInt(colorRange); + dest.writeInt(colorTransfer); + dest.writeInt(hdrStaticInfo != null ? 1 : 0); + if (hdrStaticInfo != null) { + dest.writeByteArray(hdrStaticInfo); + } + } + + public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { + @Override + public ColorInfo createFromParcel(Parcel in) { + return new ColorInfo(in); + } + + @Override + public ColorInfo[] newArray(int size) { + return new ColorInfo[0]; + } + }; + +} From fb88087ac4f6f58636c3fc90a94cd91bbdcb8f9a Mon Sep 17 00:00:00 2001 From: olly Date: Tue, 25 Apr 2017 07:42:03 -0700 Subject: [PATCH 111/119] Tweak video renderer surface swaps Fixes the following issues: 1. When a surface is set, it's expected that the renderer will notify video size + when a frame is rendered to it. This is true even if the surface isn't changing. Right now this is achieved by setting renderedFirstFrame to false, but this is problematic in the case that the surface isn't changing because (a) it will force rendering of a subsequent frame to the output even when paused, which is incorrect, and (b) isReady will return false until this occurs. 2. When the surface really is changing, isReady can return false until the first frame has been rendered into the new surface, which will break seamless surface switching. This change allows isReady to return true up to allowedJoiningTimeMs for this case. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=154171111 --- .../ext/vp9/LibvpxVideoRenderer.java | 69 ++++++++++++++----- .../video/MediaCodecVideoRenderer.java | 54 ++++++++++++--- 2 files changed, 98 insertions(+), 25 deletions(-) diff --git a/extensions/vp9/src/main/java/com/google/android/exoplayer2/ext/vp9/LibvpxVideoRenderer.java b/extensions/vp9/src/main/java/com/google/android/exoplayer2/ext/vp9/LibvpxVideoRenderer.java index 2f3517b35a..c44c703bb1 100644 --- a/extensions/vp9/src/main/java/com/google/android/exoplayer2/ext/vp9/LibvpxVideoRenderer.java +++ b/extensions/vp9/src/main/java/com/google/android/exoplayer2/ext/vp9/LibvpxVideoRenderer.java @@ -464,13 +464,16 @@ public final class LibvpxVideoRenderer extends BaseRenderer { protected void onPositionReset(long positionUs, boolean joining) { inputStreamEnded = false; outputStreamEnded = false; - renderedFirstFrame = false; + clearRenderedFirstFrame(); consecutiveDroppedFrameCount = 0; if (decoder != null) { flushDecoder(); } - joiningDeadlineMs = joining && allowedJoiningTimeMs > 0 - ? (SystemClock.elapsedRealtime() + allowedJoiningTimeMs) : C.TIME_UNSET; + if (joining) { + setJoiningDeadlineMs(); + } else { + joiningDeadlineMs = C.TIME_UNSET; + } } @Override @@ -492,6 +495,7 @@ public final class LibvpxVideoRenderer extends BaseRenderer { format = null; waitingForKeys = false; clearReportedVideoSize(); + clearRenderedFirstFrame(); try { releaseDecoder(); } finally { @@ -568,28 +572,44 @@ public final class LibvpxVideoRenderer extends BaseRenderer { private void setOutput(Surface surface, VpxOutputBufferRenderer outputBufferRenderer) { // At most one output may be non-null. Both may be null if the output is being cleared. Assertions.checkState(surface == null || outputBufferRenderer == null); - // We only need to update the decoder if the output has changed. if (this.surface != surface || this.outputBufferRenderer != outputBufferRenderer) { + // The output has changed. this.surface = surface; this.outputBufferRenderer = outputBufferRenderer; outputMode = outputBufferRenderer != null ? VpxDecoder.OUTPUT_MODE_YUV : surface != null ? VpxDecoder.OUTPUT_MODE_RGB : VpxDecoder.OUTPUT_MODE_NONE; - // If outputMode is OUTPUT_MODE_NONE we leave the mode of the underlying decoder unchanged in - // anticipation that a subsequent output will likely be of the same type as the one that was - // set previously. - if (decoder != null && outputMode != VpxDecoder.OUTPUT_MODE_NONE) { - decoder.setOutputMode(outputMode); + if (outputMode != VpxDecoder.OUTPUT_MODE_NONE) { + if (decoder != null) { + decoder.setOutputMode(outputMode); + } + // If we know the video size, report it again immediately. + maybeRenotifyVideoSizeChanged(); + // We haven't rendered to the new output yet. + clearRenderedFirstFrame(); + if (getState() == STATE_STARTED) { + setJoiningDeadlineMs(); + } + } else { + // The output has been removed. We leave the outputMode of the underlying decoder unchanged + // in anticipation that a subsequent output will likely be of the same type. + clearReportedVideoSize(); + clearRenderedFirstFrame(); } + } else if (outputMode != VpxDecoder.OUTPUT_MODE_NONE) { + // The output is unchanged and non-null. If we know the video size and/or have already + // rendered to the output, report these again immediately. + maybeRenotifyVideoSizeChanged(); + maybeRenotifyRenderedFirstFrame(); } - // Clear state so that we always call the event listener with the video size and when a frame - // is rendered, even if the output hasn't changed. - renderedFirstFrame = false; - clearReportedVideoSize(); } - private void clearReportedVideoSize() { - reportedWidth = Format.NO_VALUE; - reportedHeight = Format.NO_VALUE; + private void setJoiningDeadlineMs() { + joiningDeadlineMs = allowedJoiningTimeMs > 0 + ? (SystemClock.elapsedRealtime() + allowedJoiningTimeMs) : C.TIME_UNSET; + } + + private void clearRenderedFirstFrame() { + renderedFirstFrame = false; } private void maybeNotifyRenderedFirstFrame() { @@ -599,6 +619,17 @@ public final class LibvpxVideoRenderer extends BaseRenderer { } } + private void maybeRenotifyRenderedFirstFrame() { + if (renderedFirstFrame) { + eventDispatcher.renderedFirstFrame(surface); + } + } + + private void clearReportedVideoSize() { + reportedWidth = Format.NO_VALUE; + reportedHeight = Format.NO_VALUE; + } + private void maybeNotifyVideoSizeChanged(int width, int height) { if (reportedWidth != width || reportedHeight != height) { reportedWidth = width; @@ -607,6 +638,12 @@ public final class LibvpxVideoRenderer extends BaseRenderer { } } + private void maybeRenotifyVideoSizeChanged() { + if (reportedWidth != Format.NO_VALUE || reportedHeight != Format.NO_VALUE) { + eventDispatcher.videoSizeChanged(reportedWidth, reportedHeight, 0, 1); + } + } + private void maybeNotifyDroppedFrames() { if (droppedFrames > 0) { long now = SystemClock.elapsedRealtime(); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java b/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java index a4985f066b..ac4bb36035 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java @@ -166,7 +166,7 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { currentPixelWidthHeightRatio = Format.NO_VALUE; pendingPixelWidthHeightRatio = Format.NO_VALUE; scalingMode = C.VIDEO_SCALING_MODE_DEFAULT; - clearLastReportedVideoSize(); + clearReportedVideoSize(); } @Override @@ -229,8 +229,11 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { super.onPositionReset(positionUs, joining); clearRenderedFirstFrame(); consecutiveDroppedFrameCount = 0; - joiningDeadlineMs = joining && allowedJoiningTimeMs > 0 - ? (SystemClock.elapsedRealtime() + allowedJoiningTimeMs) : C.TIME_UNSET; + if (joining) { + setJoiningDeadlineMs(); + } else { + joiningDeadlineMs = C.TIME_UNSET; + } } @Override @@ -272,7 +275,8 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { currentHeight = Format.NO_VALUE; currentPixelWidthHeightRatio = Format.NO_VALUE; pendingPixelWidthHeightRatio = Format.NO_VALUE; - clearLastReportedVideoSize(); + clearReportedVideoSize(); + clearRenderedFirstFrame(); frameReleaseTimeHelper.disable(); tunnelingOnFrameRenderedListener = null; try { @@ -312,11 +316,25 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { maybeInitCodec(); } } + if (surface != null) { + // If we know the video size, report it again immediately. + maybeRenotifyVideoSizeChanged(); + // We haven't rendered to the new surface yet. + clearRenderedFirstFrame(); + if (state == STATE_STARTED) { + setJoiningDeadlineMs(); + } + } else { + // The surface has been removed. + clearReportedVideoSize(); + clearRenderedFirstFrame(); + } + } else if (surface != null) { + // The surface is unchanged and non-null. If we know the video size and/or have already + // rendered to the surface, report these again immediately. + maybeRenotifyVideoSizeChanged(); + maybeRenotifyRenderedFirstFrame(); } - // Clear state so that we always call the event listener with the video size and when a frame - // is rendered, even if the surface hasn't changed. - clearRenderedFirstFrame(); - clearLastReportedVideoSize(); } @Override @@ -521,6 +539,11 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { maybeNotifyRenderedFirstFrame(); } + private void setJoiningDeadlineMs() { + joiningDeadlineMs = allowedJoiningTimeMs > 0 + ? (SystemClock.elapsedRealtime() + allowedJoiningTimeMs) : C.TIME_UNSET; + } + private void clearRenderedFirstFrame() { renderedFirstFrame = false; // The first frame notification is triggered by renderOutputBuffer or renderOutputBufferV21 for @@ -543,7 +566,13 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { } } - private void clearLastReportedVideoSize() { + private void maybeRenotifyRenderedFirstFrame() { + if (renderedFirstFrame) { + eventDispatcher.renderedFirstFrame(surface); + } + } + + private void clearReportedVideoSize() { reportedWidth = Format.NO_VALUE; reportedHeight = Format.NO_VALUE; reportedPixelWidthHeightRatio = Format.NO_VALUE; @@ -563,6 +592,13 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { } } + private void maybeRenotifyVideoSizeChanged() { + if (reportedWidth != Format.NO_VALUE || reportedHeight != Format.NO_VALUE) { + eventDispatcher.videoSizeChanged(currentWidth, currentHeight, currentUnappliedRotationDegrees, + currentPixelWidthHeightRatio); + } + } + private void maybeNotifyDroppedFrames() { if (droppedFrames > 0) { long now = SystemClock.elapsedRealtime(); From c50cfd8345829a1ab942e2b76c514c3885dbc4cd Mon Sep 17 00:00:00 2001 From: Oliver Woodman Date: Tue, 25 Apr 2017 16:41:04 +0100 Subject: [PATCH 112/119] Minor additional cleanup of multiple DVB subtitles per PID support --- .../extractor/ts/DvbSubtitleReader.java | 5 +++-- .../exoplayer2/extractor/ts/TsExtractor.java | 6 +++--- .../extractor/ts/TsPayloadReader.java | 17 ++++++++++------- 3 files changed, 16 insertions(+), 12 deletions(-) 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 87adfcea86..e00c63a354 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 @@ -23,6 +23,7 @@ import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.DvbSubtitleInf import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.TrackIdGenerator; import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.ParsableByteArray; +import java.util.Collections; import java.util.List; /** @@ -58,8 +59,8 @@ public final class DvbSubtitleReader implements ElementaryStreamReader { idGenerator.generateNewId(); TrackOutput output = extractorOutput.track(idGenerator.getTrackId(), C.TRACK_TYPE_TEXT); output.format(Format.createImageSampleFormat(idGenerator.getFormatId(), - MimeTypes.APPLICATION_DVBSUBS, null, Format.NO_VALUE, subtitleInfo.initializationData, - subtitleInfo.language, null)); + MimeTypes.APPLICATION_DVBSUBS, null, Format.NO_VALUE, + Collections.singletonList(subtitleInfo.initializationData), subtitleInfo.language, null)); outputs[i] = output; } } 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 adcdb7d9d7..df6efb722c 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 @@ -517,11 +517,11 @@ public final class TsExtractor implements Extractor { dvbSubtitleInfos = new ArrayList<>(); while (data.getPosition() < positionOfNextDescriptor) { String dvbLanguage = data.readString(3).trim(); - int dvbProgramElementType = data.readUnsignedByte(); + int dvbSubtitlingType = data.readUnsignedByte(); byte[] initializationData = new byte[4]; data.readBytes(initializationData, 0, 4); - dvbSubtitleInfos.add(new DvbSubtitleInfo(dvbLanguage, dvbProgramElementType, - Collections.singletonList(initializationData))); + dvbSubtitleInfos.add(new DvbSubtitleInfo(dvbLanguage, dvbSubtitlingType, + initializationData)); } } // Skip unused bytes of current descriptor. 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 ef3a6af085..e7996c66c3 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 @@ -20,7 +20,6 @@ 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.util.Collections; import java.util.List; @@ -85,18 +84,22 @@ public interface TsPayloadReader { } /** - * Holds information about a DVB subtitle. + * Holds information about a DVB subtitle, as defined in ETSI EN 300 468 V1.11.1 section 6.2.41. */ final class DvbSubtitleInfo { public final String language; - public final int programElementType; - public final List initializationData; + public final int type; + public final byte[] initializationData; - public DvbSubtitleInfo(String language, int programElementType, - List initializationData) { + /** + * @param language The ISO 639-2 three character language. + * @param type The subtitling type. + * @param initializationData The composition and ancillary page ids. + */ + public DvbSubtitleInfo(String language, int type, byte[] initializationData) { this.language = language; - this.programElementType = programElementType; + this.type = type; this.initializationData = initializationData; } From 9d76ab71336ed3bf68c73f6be4fd79d0976f7a54 Mon Sep 17 00:00:00 2001 From: vigneshv Date: Tue, 25 Apr 2017 09:06:28 -0700 Subject: [PATCH 113/119] vp9_extension: Update libyuv build configuration Disable libjpeg dependency when building libyuv. Partially fixes Github Issue #2661 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=154179126 --- extensions/vp9/README.md | 2 +- extensions/vp9/src/main/jni/Android.mk | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/extensions/vp9/README.md b/extensions/vp9/README.md index 90ded8fdc0..53ef4b0bfd 100644 --- a/extensions/vp9/README.md +++ b/extensions/vp9/README.md @@ -49,7 +49,7 @@ git clone https://chromium.googlesource.com/libyuv/libyuv libyuv cd "${VP9_EXT_PATH}/jni/libvpx" && \ git checkout tags/v1.6.1 -b v1.6.1 && \ cd "${VP9_EXT_PATH}/jni/libyuv" && \ -git checkout e2611a73 +git checkout 996a2bbd ``` * Run a script that generates necessary configuration files for libvpx: diff --git a/extensions/vp9/src/main/jni/Android.mk b/extensions/vp9/src/main/jni/Android.mk index 8ad32a254a..92fed0a064 100644 --- a/extensions/vp9/src/main/jni/Android.mk +++ b/extensions/vp9/src/main/jni/Android.mk @@ -21,6 +21,7 @@ LIBYUV_ROOT := $(WORKING_DIR)/libyuv # build libyuv_static.a LOCAL_PATH := $(WORKING_DIR) +LIBYUV_DISABLE_JPEG := "yes" include $(LIBYUV_ROOT)/Android.mk # build libvpx.so From b88deaa1ca7b853cc20b0ca676f5d5b244680654 Mon Sep 17 00:00:00 2001 From: olly Date: Tue, 25 Apr 2017 11:30:37 -0700 Subject: [PATCH 114/119] Bump version and update release notes ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=154198359 --- RELEASENOTES.md | 37 +++++++++++++++++++ build.gradle | 2 +- demo/src/main/AndroidManifest.xml | 4 +- .../exoplayer2/ExoPlayerLibraryInfo.java | 6 +-- 4 files changed, 43 insertions(+), 6 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 48bf6dd073..caed0a74c3 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -1,5 +1,36 @@ # Release notes # +### r2.4.0 ### + +* New modular library structure. You can read more about depending on individual + library modules [here](TODO). +* Variable speed playback support on API level 16+. You can read more about + changing the playback speed [here](TODO) + ([#26](https://github.com/google/ExoPlayer/issues/26))." +* New time bar view, including support for displaying ad break markers. +* Support DVB subtitles in MPEG-TS and MKV. +* Support adaptive playback for audio only DASH, HLS and SmoothStreaming + ([#1975](https://github.com/google/ExoPlayer/issues/1975)). +* Support for setting extractor flags on DefaultExtractorsFactory + ([#2657](https://github.com/google/ExoPlayer/issues/2657)). +* Support injecting custom renderers into SimpleExoPlayer using a new + RenderersFactory interface. +* Correctly set ExoPlayer's internal thread priority to `THREAD_PRIORITY_AUDIO`. +* TX3G: Support styling and positioning. +* FLV: + * Support MP3 in FLV. + * Skip unhandled metadata rather than failing + ([#2634](https://github.com/google/ExoPlayer/issues/2634)). + * Fix potential OutOfMemory errors. +* ID3: Better handle malformed ID3 data + ([#2604](https://github.com/google/ExoPlayer/issues/2604), + [#2663](https://github.com/google/ExoPlayer/issues/2663)). +* FFmpeg extension: Fixed build instructions + ([#2561](https://github.com/google/ExoPlayer/issues/2561)). +* VP9 extension: Reduced binary size. +* FLAC extension: Enabled 64 bit targets. +* Misc bugfixes. + ### r2.3.1 ### * Fix NPE enabling WebVTT subtitles in DASH streams @@ -302,6 +333,12 @@ in all V2 releases. This cannot be assumed for changes in r1.5.12 and later, however it can be assumed that all such changes are included in the most recent V2 release. +### r1.5.16 ### + +* VP9 extension: Reduced binary size. +* FLAC extension: Enabled 64 bit targets and fixed proguard config. +* Misc bugfixes. + ### r1.5.15 ### * SmoothStreaming: Fixed handling of start_time placeholder diff --git a/build.gradle b/build.gradle index 01b1c28bf6..cbc34cecd6 100644 --- a/build.gradle +++ b/build.gradle @@ -48,7 +48,7 @@ allprojects { releaseRepoName = getBintrayRepo() releaseUserOrg = 'google' releaseGroupId = 'com.google.android.exoplayer' - releaseVersion = 'r2.3.1' + releaseVersion = 'r2.4.0' releaseWebsite = 'https://github.com/google/ExoPlayer' } if (it.hasProperty('externalBuildDir')) { diff --git a/demo/src/main/AndroidManifest.xml b/demo/src/main/AndroidManifest.xml index 9a6e1a4d3a..6580e687cc 100644 --- a/demo/src/main/AndroidManifest.xml +++ b/demo/src/main/AndroidManifest.xml @@ -16,8 +16,8 @@ + android:versionCode="2400" + android:versionName="2.4.0"> 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 74f622946e..13cf35d449 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 @@ -24,13 +24,13 @@ public interface 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. - String VERSION = "2.3.1"; + String VERSION = "2.4.0"; /** * The version of the library expressed as {@code "ExoPlayerLib/" + VERSION}. */ // Intentionally hardcoded. Do not derive from other constants (e.g. VERSION) or vice versa. - String VERSION_SLASHY = "ExoPlayerLib/2.3.1"; + String VERSION_SLASHY = "ExoPlayerLib/2.4.0"; /** * The version of the library expressed as an integer, for example 1002003. @@ -40,7 +40,7 @@ public interface ExoPlayerLibraryInfo { * integer version 123045006 (123-045-006). */ // Intentionally hardcoded. Do not derive from other constants (e.g. VERSION) or vice versa. - int VERSION_INT = 2003001; + int VERSION_INT = 2004000; /** * Whether the library was compiled with {@link com.google.android.exoplayer2.util.Assertions} From 9c2b0f6de2c444418ab8412fc575e83119d0cb93 Mon Sep 17 00:00:00 2001 From: olly Date: Tue, 25 Apr 2017 11:50:34 -0700 Subject: [PATCH 115/119] Fix lint errors when building release ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=154201059 --- .../android/exoplayer2/ui/DefaultTimeBar.java | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/library/ui/src/main/java/com/google/android/exoplayer2/ui/DefaultTimeBar.java b/library/ui/src/main/java/com/google/android/exoplayer2/ui/DefaultTimeBar.java index a6042fd48b..d40da451a2 100644 --- a/library/ui/src/main/java/com/google/android/exoplayer2/ui/DefaultTimeBar.java +++ b/library/ui/src/main/java/com/google/android/exoplayer2/ui/DefaultTimeBar.java @@ -186,9 +186,8 @@ public class DefaultTimeBar extends View implements TimeBar { keyTimeIncrement = C.TIME_UNSET; keyCountIncrement = DEFAULT_INCREMENT_COUNT; setFocusable(true); - if (Util.SDK_INT >= 16 - && getImportantForAccessibility() == View.IMPORTANT_FOR_ACCESSIBILITY_AUTO) { - setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES); + if (Util.SDK_INT >= 16) { + maybeSetImportantForAccessibilityV16(); } } @@ -372,7 +371,7 @@ public class DefaultTimeBar extends View implements TimeBar { event.setClassName(DefaultTimeBar.class.getName()); } - @TargetApi(14) + @TargetApi(21) @Override public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { super.onInitializeAccessibilityNodeInfo(info); @@ -416,6 +415,13 @@ public class DefaultTimeBar extends View implements TimeBar { // Internal methods. + @TargetApi(16) + private void maybeSetImportantForAccessibilityV16() { + if (getImportantForAccessibility() == View.IMPORTANT_FOR_ACCESSIBILITY_AUTO) { + setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES); + } + } + private void startScrubbing() { scrubbing = true; updateScrubberState(); From f9c374235492389142ed2b59ad3cf444b1905afb Mon Sep 17 00:00:00 2001 From: vigneshv Date: Wed, 26 Apr 2017 01:22:55 -0700 Subject: [PATCH 116/119] vp9_extension: Include cstring explicitly Without this, newer android NDKs (r14+) fail to compile. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=154271517 --- extensions/vp9/src/main/jni/vpx_jni.cc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/extensions/vp9/src/main/jni/vpx_jni.cc b/extensions/vp9/src/main/jni/vpx_jni.cc index c091091389..d02d524713 100644 --- a/extensions/vp9/src/main/jni/vpx_jni.cc +++ b/extensions/vp9/src/main/jni/vpx_jni.cc @@ -20,8 +20,9 @@ #include #include -#include #include +#include +#include #include #include "libyuv.h" // NOLINT From 0f9d9d56d6b4e2b1c5a8f3e83a7a843b14035034 Mon Sep 17 00:00:00 2001 From: tonihei Date: Wed, 26 Apr 2017 01:24:30 -0700 Subject: [PATCH 117/119] Fix scrub seek position estimation for multi window time bar. When using a multi window time bar, the onScrubStop method uses another method to estimate the seek position than the updateProgress method for rendering the time bar. If the time line contains windows starting with an ad period followed by other content, they differ in their behaviour. - updateProgress checks all periods and leaves out all ad periods - onScrubStop only checks windows and leaves them out when the first period is an ad. Changed onScrubStop to fit approach in updateProgess. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=154271628 --- .../exoplayer2/ui/PlaybackControlView.java | 63 +++++++++++-------- 1 file changed, 37 insertions(+), 26 deletions(-) diff --git a/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlaybackControlView.java b/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlaybackControlView.java index ff0e92add5..ce2e81020f 100644 --- a/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlaybackControlView.java +++ b/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlaybackControlView.java @@ -728,6 +728,42 @@ public class PlaybackControlView extends FrameLayout { } } + private void seekToTimebarPosition(long timebarPositionMs) { + if (multiWindowTimeBar) { + Timeline timeline = player.getCurrentTimeline(); + int windowCount = timeline.getWindowCount(); + long remainingMs = timebarPositionMs; + for (int i = 0; i < windowCount; i++) { + timeline.getWindow(i, window); + for (int j = window.firstPeriodIndex; j <= window.lastPeriodIndex; j++) { + if (!timeline.getPeriod(j, period).isAd) { + long periodDurationMs = period.getDurationMs(); + if (periodDurationMs == C.TIME_UNSET) { + // Should never happen as canShowMultiWindowTimeBar is true. + throw new IllegalStateException(); + } + if (j == window.firstPeriodIndex) { + periodDurationMs -= window.getPositionInFirstPeriodMs(); + } + if (i == windowCount - 1 && j == window.lastPeriodIndex + && remainingMs >= periodDurationMs) { + // Seeking past the end of the last window should seek to the end of the timeline. + seekTo(i, window.getDurationMs()); + return; + } + if (remainingMs < periodDurationMs) { + seekTo(i, period.getPositionInWindowMs() + remainingMs); + return; + } + remainingMs -= periodDurationMs; + } + } + } + } else { + seekTo(timebarPositionMs); + } + } + @Override public void onAttachedToWindow() { super.onAttachedToWindow(); @@ -855,32 +891,7 @@ public class PlaybackControlView extends FrameLayout { public void onScrubStop(TimeBar timeBar, long position, boolean canceled) { scrubbing = false; if (!canceled && player != null) { - if (multiWindowTimeBar) { - Timeline timeline = player.getCurrentTimeline(); - int windowCount = timeline.getWindowCount(); - long remainingMs = position; - for (int i = 0; i < windowCount; i++) { - timeline.getWindow(i, window); - if (!timeline.getPeriod(window.firstPeriodIndex, period).isAd) { - long windowDurationMs = window.getDurationMs(); - if (windowDurationMs == C.TIME_UNSET) { - break; - } - if (i == windowCount - 1 && remainingMs >= windowDurationMs) { - // Seeking past the end of the last window should seek to the end of the timeline. - seekTo(i, windowDurationMs); - break; - } - if (remainingMs < windowDurationMs) { - seekTo(i, remainingMs); - break; - } - remainingMs -= windowDurationMs; - } - } - } else { - seekTo(position); - } + seekToTimebarPosition(position); } hideAfterTimeout(); } From 1e12c0795476488b4065c50486d16fc1e022da25 Mon Sep 17 00:00:00 2001 From: olly Date: Wed, 26 Apr 2017 07:44:33 -0700 Subject: [PATCH 118/119] Move renderer building into a factory class. To inject custom renderers into SimpleExoPlayer, developers currently need to extend SimpleExoPlayer and override the renderer building methods. This is in contrast to the rest of the library, where we use proper injection. This change restores consistency. I think it's fine to make SimpleExoPlayer final again, but if we find people extending it for non-renderer purposes, we can revert that part of the change. ExoPlayerFactory now has analogous methods for the simple and non-simple cases, which is a nice outcome. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=154295726 --- .../exoplayer2/demo/PlayerActivity.java | 17 +- .../exoplayer2/DefaultRenderersFactory.java | 327 ++++++++++++++++++ .../android/exoplayer2/ExoPlayerFactory.java | 69 +++- .../android/exoplayer2/RenderersFactory.java | 44 +++ .../android/exoplayer2/SimpleExoPlayer.java | 241 +------------ .../playbacktests/gts/DashTestRunner.java | 8 +- ...Player.java => DebugRenderersFactory.java} | 32 +- .../playbacktests/util/ExoHostedTest.java | 9 +- 8 files changed, 466 insertions(+), 281 deletions(-) create mode 100644 library/core/src/main/java/com/google/android/exoplayer2/DefaultRenderersFactory.java create mode 100644 library/core/src/main/java/com/google/android/exoplayer2/RenderersFactory.java rename playbacktests/src/main/java/com/google/android/exoplayer2/playbacktests/util/{DebugSimpleExoPlayer.java => DebugRenderersFactory.java} (80%) diff --git a/demo/src/main/java/com/google/android/exoplayer2/demo/PlayerActivity.java b/demo/src/main/java/com/google/android/exoplayer2/demo/PlayerActivity.java index 5a812e6e62..2542f23e95 100644 --- a/demo/src/main/java/com/google/android/exoplayer2/demo/PlayerActivity.java +++ b/demo/src/main/java/com/google/android/exoplayer2/demo/PlayerActivity.java @@ -31,7 +31,7 @@ import android.widget.LinearLayout; import android.widget.TextView; import android.widget.Toast; import com.google.android.exoplayer2.C; -import com.google.android.exoplayer2.DefaultLoadControl; +import com.google.android.exoplayer2.DefaultRenderersFactory; import com.google.android.exoplayer2.ExoPlaybackException; import com.google.android.exoplayer2.ExoPlayer; import com.google.android.exoplayer2.ExoPlayerFactory; @@ -253,18 +253,21 @@ public class PlayerActivity extends Activity implements OnClickListener, ExoPlay } } - @SimpleExoPlayer.ExtensionRendererMode int extensionRendererMode = + @DefaultRenderersFactory.ExtensionRendererMode int extensionRendererMode = ((DemoApplication) getApplication()).useExtensionRenderers() - ? (preferExtensionDecoders ? SimpleExoPlayer.EXTENSION_RENDERER_MODE_PREFER - : SimpleExoPlayer.EXTENSION_RENDERER_MODE_ON) - : SimpleExoPlayer.EXTENSION_RENDERER_MODE_OFF; + ? (preferExtensionDecoders ? DefaultRenderersFactory.EXTENSION_RENDERER_MODE_PREFER + : DefaultRenderersFactory.EXTENSION_RENDERER_MODE_ON) + : DefaultRenderersFactory.EXTENSION_RENDERER_MODE_OFF; + DefaultRenderersFactory renderersFactory = new DefaultRenderersFactory(this, + drmSessionManager, extensionRendererMode); + TrackSelection.Factory videoTrackSelectionFactory = new AdaptiveTrackSelection.Factory(BANDWIDTH_METER); trackSelector = new DefaultTrackSelector(videoTrackSelectionFactory); trackSelectionHelper = new TrackSelectionHelper(trackSelector, videoTrackSelectionFactory); lastSeenTrackGroupArray = null; - player = ExoPlayerFactory.newSimpleInstance(this, trackSelector, new DefaultLoadControl(), - drmSessionManager, extensionRendererMode); + + player = ExoPlayerFactory.newSimpleInstance(renderersFactory, trackSelector); player.addListener(this); eventLogger = new EventLogger(trackSelector); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/DefaultRenderersFactory.java b/library/core/src/main/java/com/google/android/exoplayer2/DefaultRenderersFactory.java new file mode 100644 index 0000000000..3e7cb8a68b --- /dev/null +++ b/library/core/src/main/java/com/google/android/exoplayer2/DefaultRenderersFactory.java @@ -0,0 +1,327 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.exoplayer2; + +import android.content.Context; +import android.os.Handler; +import android.os.Looper; +import android.support.annotation.IntDef; +import android.util.Log; +import com.google.android.exoplayer2.audio.AudioCapabilities; +import com.google.android.exoplayer2.audio.AudioProcessor; +import com.google.android.exoplayer2.audio.AudioRendererEventListener; +import com.google.android.exoplayer2.audio.MediaCodecAudioRenderer; +import com.google.android.exoplayer2.drm.DrmSessionManager; +import com.google.android.exoplayer2.drm.FrameworkMediaCrypto; +import com.google.android.exoplayer2.mediacodec.MediaCodecSelector; +import com.google.android.exoplayer2.metadata.MetadataRenderer; +import com.google.android.exoplayer2.text.TextRenderer; +import com.google.android.exoplayer2.trackselection.TrackSelector; +import com.google.android.exoplayer2.video.MediaCodecVideoRenderer; +import com.google.android.exoplayer2.video.VideoRendererEventListener; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.reflect.Constructor; +import java.util.ArrayList; + +/** + * Default {@link RenderersFactory} implementation. + */ +public class DefaultRenderersFactory implements RenderersFactory { + + /** + * The default maximum duration for which a video renderer can attempt to seamlessly join an + * ongoing playback. + */ + public static final long DEFAULT_ALLOWED_VIDEO_JOINING_TIME_MS = 5000; + + /** + * Modes for using extension renderers. + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({EXTENSION_RENDERER_MODE_OFF, EXTENSION_RENDERER_MODE_ON, + EXTENSION_RENDERER_MODE_PREFER}) + public @interface ExtensionRendererMode {} + /** + * Do not allow use of extension renderers. + */ + public static final int EXTENSION_RENDERER_MODE_OFF = 0; + /** + * Allow use of extension renderers. Extension renderers are indexed after core renderers of the + * same type. A {@link TrackSelector} that prefers the first suitable renderer will therefore + * prefer to use a core renderer to an extension renderer in the case that both are able to play + * a given track. + */ + public static final int EXTENSION_RENDERER_MODE_ON = 1; + /** + * Allow use of extension renderers. Extension renderers are indexed before core renderers of the + * same type. A {@link TrackSelector} that prefers the first suitable renderer will therefore + * prefer to use an extension renderer to a core renderer in the case that both are able to play + * a given track. + */ + public static final int EXTENSION_RENDERER_MODE_PREFER = 2; + + private static final String TAG = "DefaultRenderersFactory"; + + protected static final int MAX_DROPPED_VIDEO_FRAME_COUNT_TO_NOTIFY = 50; + + private final Context context; + private final DrmSessionManager drmSessionManager; + private final @ExtensionRendererMode int extensionRendererMode; + private final long allowedVideoJoiningTimeMs; + + /** + * @param context A {@link Context}. + */ + public DefaultRenderersFactory(Context context) { + this(context, null); + } + + /** + * @param context A {@link Context}. + * @param drmSessionManager An optional {@link DrmSessionManager}. May be null if DRM protected + * playbacks are not required. + */ + public DefaultRenderersFactory(Context context, + DrmSessionManager drmSessionManager) { + this(context, drmSessionManager, EXTENSION_RENDERER_MODE_OFF); + } + + /** + * @param context A {@link Context}. + * @param drmSessionManager An optional {@link DrmSessionManager}. May be null if DRM protected + * playbacks are not required.. + * @param extensionRendererMode The extension renderer mode, which determines if and how + * available extension renderers are used. Note that extensions must be included in the + * application build for them to be considered available. + */ + public DefaultRenderersFactory(Context context, + DrmSessionManager drmSessionManager, + @ExtensionRendererMode int extensionRendererMode) { + this(context, drmSessionManager, extensionRendererMode, + DEFAULT_ALLOWED_VIDEO_JOINING_TIME_MS); + } + + /** + * @param context A {@link Context}. + * @param drmSessionManager An optional {@link DrmSessionManager}. May be null if DRM protected + * playbacks are not required.. + * @param extensionRendererMode The extension renderer mode, which determines if and how + * available extension renderers are used. Note that extensions must be included in the + * application build for them to be considered available. + * @param allowedVideoJoiningTimeMs The maximum duration for which video renderers can attempt + * to seamlessly join an ongoing playback. + */ + public DefaultRenderersFactory(Context context, + DrmSessionManager drmSessionManager, + @ExtensionRendererMode int extensionRendererMode, long allowedVideoJoiningTimeMs) { + this.context = context; + this.drmSessionManager = drmSessionManager; + this.extensionRendererMode = extensionRendererMode; + this.allowedVideoJoiningTimeMs = allowedVideoJoiningTimeMs; + } + + @Override + public Renderer[] createRenderers(Handler eventHandler, + VideoRendererEventListener videoRendererEventListener, + AudioRendererEventListener audioRendererEventListener, + TextRenderer.Output textRendererOutput, MetadataRenderer.Output metadataRendererOutput) { + ArrayList renderersList = new ArrayList<>(); + buildVideoRenderers(context, drmSessionManager, allowedVideoJoiningTimeMs, + eventHandler, videoRendererEventListener, extensionRendererMode, renderersList); + buildAudioRenderers(context, drmSessionManager, buildAudioProcessors(), + eventHandler, audioRendererEventListener, extensionRendererMode, renderersList); + buildTextRenderers(context, textRendererOutput, eventHandler.getLooper(), + extensionRendererMode, renderersList); + buildMetadataRenderers(context, metadataRendererOutput, eventHandler.getLooper(), + extensionRendererMode, renderersList); + buildMiscellaneousRenderers(context, eventHandler, extensionRendererMode, renderersList); + return renderersList.toArray(new Renderer[renderersList.size()]); + } + + /** + * Builds video renderers for use by the player. + * + * @param context The {@link Context} associated with the player. + * @param drmSessionManager An optional {@link DrmSessionManager}. May be null if the player + * will not be used for DRM protected playbacks. + * @param allowedVideoJoiningTimeMs The maximum duration in milliseconds for which video + * renderers can attempt to seamlessly join an ongoing playback. + * @param eventHandler A handler associated with the main thread's looper. + * @param eventListener An event listener. + * @param extensionRendererMode The extension renderer mode. + * @param out An array to which the built renderers should be appended. + */ + protected void buildVideoRenderers(Context context, + DrmSessionManager drmSessionManager, long allowedVideoJoiningTimeMs, + Handler eventHandler, VideoRendererEventListener eventListener, + @ExtensionRendererMode int extensionRendererMode, ArrayList out) { + out.add(new MediaCodecVideoRenderer(context, MediaCodecSelector.DEFAULT, + allowedVideoJoiningTimeMs, drmSessionManager, false, eventHandler, eventListener, + MAX_DROPPED_VIDEO_FRAME_COUNT_TO_NOTIFY)); + + if (extensionRendererMode == EXTENSION_RENDERER_MODE_OFF) { + return; + } + int extensionRendererIndex = out.size(); + if (extensionRendererMode == EXTENSION_RENDERER_MODE_PREFER) { + extensionRendererIndex--; + } + + try { + Class clazz = + Class.forName("com.google.android.exoplayer2.ext.vp9.LibvpxVideoRenderer"); + Constructor constructor = clazz.getConstructor(boolean.class, long.class, Handler.class, + VideoRendererEventListener.class, int.class); + Renderer renderer = (Renderer) constructor.newInstance(true, allowedVideoJoiningTimeMs, + eventHandler, eventListener, MAX_DROPPED_VIDEO_FRAME_COUNT_TO_NOTIFY); + out.add(extensionRendererIndex++, renderer); + Log.i(TAG, "Loaded LibvpxVideoRenderer."); + } catch (ClassNotFoundException e) { + // Expected if the app was built without the extension. + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + /** + * Builds audio renderers for use by the player. + * + * @param context The {@link Context} associated with the player. + * @param drmSessionManager An optional {@link DrmSessionManager}. May be null if the player + * will not be used for DRM protected playbacks. + * @param audioProcessors An array of {@link AudioProcessor}s that will process PCM audio + * buffers before output. May be empty. + * @param eventHandler A handler to use when invoking event listeners and outputs. + * @param eventListener An event listener. + * @param extensionRendererMode The extension renderer mode. + * @param out An array to which the built renderers should be appended. + */ + protected void buildAudioRenderers(Context context, + DrmSessionManager drmSessionManager, + AudioProcessor[] audioProcessors, Handler eventHandler, + AudioRendererEventListener eventListener, @ExtensionRendererMode int extensionRendererMode, + ArrayList out) { + out.add(new MediaCodecAudioRenderer(MediaCodecSelector.DEFAULT, drmSessionManager, true, + eventHandler, eventListener, AudioCapabilities.getCapabilities(context), audioProcessors)); + + if (extensionRendererMode == EXTENSION_RENDERER_MODE_OFF) { + return; + } + int extensionRendererIndex = out.size(); + if (extensionRendererMode == EXTENSION_RENDERER_MODE_PREFER) { + extensionRendererIndex--; + } + + try { + Class clazz = + Class.forName("com.google.android.exoplayer2.ext.opus.LibopusAudioRenderer"); + Constructor constructor = clazz.getConstructor(Handler.class, + AudioRendererEventListener.class, AudioProcessor[].class); + Renderer renderer = (Renderer) constructor.newInstance(eventHandler, eventListener, + audioProcessors); + out.add(extensionRendererIndex++, renderer); + Log.i(TAG, "Loaded LibopusAudioRenderer."); + } catch (ClassNotFoundException e) { + // Expected if the app was built without the extension. + } catch (Exception e) { + throw new RuntimeException(e); + } + + try { + Class clazz = + Class.forName("com.google.android.exoplayer2.ext.flac.LibflacAudioRenderer"); + Constructor constructor = clazz.getConstructor(Handler.class, + AudioRendererEventListener.class, AudioProcessor[].class); + Renderer renderer = (Renderer) constructor.newInstance(eventHandler, eventListener, + audioProcessors); + out.add(extensionRendererIndex++, renderer); + Log.i(TAG, "Loaded LibflacAudioRenderer."); + } catch (ClassNotFoundException e) { + // Expected if the app was built without the extension. + } catch (Exception e) { + throw new RuntimeException(e); + } + + try { + Class clazz = + Class.forName("com.google.android.exoplayer2.ext.ffmpeg.FfmpegAudioRenderer"); + Constructor constructor = clazz.getConstructor(Handler.class, + AudioRendererEventListener.class, AudioProcessor[].class); + Renderer renderer = (Renderer) constructor.newInstance(eventHandler, eventListener, + audioProcessors); + out.add(extensionRendererIndex++, renderer); + Log.i(TAG, "Loaded FfmpegAudioRenderer."); + } catch (ClassNotFoundException e) { + // Expected if the app was built without the extension. + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + /** + * Builds text renderers for use by the player. + * + * @param context The {@link Context} associated with the player. + * @param output An output for the renderers. + * @param outputLooper The looper associated with the thread on which the output should be + * called. + * @param extensionRendererMode The extension renderer mode. + * @param out An array to which the built renderers should be appended. + */ + protected void buildTextRenderers(Context context, TextRenderer.Output output, + Looper outputLooper, @ExtensionRendererMode int extensionRendererMode, + ArrayList out) { + out.add(new TextRenderer(output, outputLooper)); + } + + /** + * Builds metadata renderers for use by the player. + * + * @param context The {@link Context} associated with the player. + * @param output An output for the renderers. + * @param outputLooper The looper associated with the thread on which the output should be + * called. + * @param extensionRendererMode The extension renderer mode. + * @param out An array to which the built renderers should be appended. + */ + protected void buildMetadataRenderers(Context context, MetadataRenderer.Output output, + Looper outputLooper, @ExtensionRendererMode int extensionRendererMode, + ArrayList out) { + out.add(new MetadataRenderer(output, outputLooper)); + } + + /** + * Builds any miscellaneous renderers used by the player. + * + * @param context The {@link Context} associated with the player. + * @param eventHandler A handler to use when invoking event listeners and outputs. + * @param extensionRendererMode The extension renderer mode. + * @param out An array to which the built renderers should be appended. + */ + protected void buildMiscellaneousRenderers(Context context, Handler eventHandler, + @ExtensionRendererMode int extensionRendererMode, ArrayList out) { + // Do nothing. + } + + /** + * Builds an array of {@link AudioProcessor}s that will process PCM audio before output. + */ + protected AudioProcessor[] buildAudioProcessors() { + return new AudioProcessor[0]; + } + +} diff --git a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerFactory.java b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerFactory.java index 43de6fe751..7aecd20d4e 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerFactory.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerFactory.java @@ -26,12 +26,6 @@ import com.google.android.exoplayer2.trackselection.TrackSelector; */ public final class ExoPlayerFactory { - /** - * The default maximum duration for which a video renderer can attempt to seamlessly join an - * ongoing playback. - */ - public static final long DEFAULT_ALLOWED_VIDEO_JOINING_TIME_MS = 5000; - private ExoPlayerFactory() {} /** @@ -41,10 +35,13 @@ public final class ExoPlayerFactory { * @param context A {@link Context}. * @param trackSelector The {@link TrackSelector} that will be used by the instance. * @param loadControl The {@link LoadControl} that will be used by the instance. + * @deprecated Use {@link #newSimpleInstance(RenderersFactory, TrackSelector, LoadControl)}. */ + @Deprecated public static SimpleExoPlayer newSimpleInstance(Context context, TrackSelector trackSelector, LoadControl loadControl) { - return newSimpleInstance(context, trackSelector, loadControl, null); + RenderersFactory renderersFactory = new DefaultRenderersFactory(context); + return newSimpleInstance(renderersFactory, trackSelector, loadControl); } /** @@ -56,11 +53,13 @@ public final class ExoPlayerFactory { * @param loadControl The {@link LoadControl} that will be used by the instance. * @param drmSessionManager An optional {@link DrmSessionManager}. May be null if the instance * will not be used for DRM protected playbacks. + * @deprecated Use {@link #newSimpleInstance(RenderersFactory, TrackSelector, LoadControl)}. */ + @Deprecated public static SimpleExoPlayer newSimpleInstance(Context context, TrackSelector trackSelector, LoadControl loadControl, DrmSessionManager drmSessionManager) { - return newSimpleInstance(context, trackSelector, loadControl, - drmSessionManager, SimpleExoPlayer.EXTENSION_RENDERER_MODE_OFF); + RenderersFactory renderersFactory = new DefaultRenderersFactory(context, drmSessionManager); + return newSimpleInstance(renderersFactory, trackSelector, loadControl); } /** @@ -75,12 +74,15 @@ public final class ExoPlayerFactory { * @param extensionRendererMode The extension renderer mode, which determines if and how available * extension renderers are used. Note that extensions must be included in the application * build for them to be considered available. + * @deprecated Use {@link #newSimpleInstance(RenderersFactory, TrackSelector, LoadControl)}. */ + @Deprecated public static SimpleExoPlayer newSimpleInstance(Context context, TrackSelector trackSelector, LoadControl loadControl, DrmSessionManager drmSessionManager, - @SimpleExoPlayer.ExtensionRendererMode int extensionRendererMode) { - return newSimpleInstance(context, trackSelector, loadControl, drmSessionManager, - extensionRendererMode, DEFAULT_ALLOWED_VIDEO_JOINING_TIME_MS); + @DefaultRenderersFactory.ExtensionRendererMode int extensionRendererMode) { + RenderersFactory renderersFactory = new DefaultRenderersFactory(context, drmSessionManager, + extensionRendererMode); + return newSimpleInstance(renderersFactory, trackSelector, loadControl); } /** @@ -97,13 +99,52 @@ public final class ExoPlayerFactory { * build for them to be considered available. * @param allowedVideoJoiningTimeMs The maximum duration for which a video renderer can attempt to * seamlessly join an ongoing playback. + * @deprecated Use {@link #newSimpleInstance(RenderersFactory, TrackSelector, LoadControl)}. */ + @Deprecated public static SimpleExoPlayer newSimpleInstance(Context context, TrackSelector trackSelector, LoadControl loadControl, DrmSessionManager drmSessionManager, - @SimpleExoPlayer.ExtensionRendererMode int extensionRendererMode, + @DefaultRenderersFactory.ExtensionRendererMode int extensionRendererMode, long allowedVideoJoiningTimeMs) { - return new SimpleExoPlayer(context, trackSelector, loadControl, drmSessionManager, + RenderersFactory renderersFactory = new DefaultRenderersFactory(context, drmSessionManager, extensionRendererMode, allowedVideoJoiningTimeMs); + return newSimpleInstance(renderersFactory, trackSelector, loadControl); + } + + /** + * Creates a {@link SimpleExoPlayer} instance. Must be called from a thread that has an associated + * {@link Looper}. + * + * @param context A {@link Context}. + * @param trackSelector The {@link TrackSelector} that will be used by the instance. + */ + public static SimpleExoPlayer newSimpleInstance(Context context, TrackSelector trackSelector) { + return newSimpleInstance(new DefaultRenderersFactory(context), trackSelector); + } + + /** + * Creates a {@link SimpleExoPlayer} instance. Must be called from a thread that has an associated + * {@link Looper}. + * + * @param renderersFactory A factory for creating {@link Renderer}s to be used by the instance. + * @param trackSelector The {@link TrackSelector} that will be used by the instance. + */ + public static SimpleExoPlayer newSimpleInstance(RenderersFactory renderersFactory, + TrackSelector trackSelector) { + return newSimpleInstance(renderersFactory, trackSelector, new DefaultLoadControl()); + } + + /** + * Creates a {@link SimpleExoPlayer} instance. Must be called from a thread that has an associated + * {@link Looper}. + * + * @param renderersFactory A factory for creating {@link Renderer}s to be used by the instance. + * @param trackSelector The {@link TrackSelector} that will be used by the instance. + * @param loadControl The {@link LoadControl} that will be used by the instance. + */ + public static SimpleExoPlayer newSimpleInstance(RenderersFactory renderersFactory, + TrackSelector trackSelector, LoadControl loadControl) { + return new SimpleExoPlayer(renderersFactory, trackSelector, loadControl); } /** diff --git a/library/core/src/main/java/com/google/android/exoplayer2/RenderersFactory.java b/library/core/src/main/java/com/google/android/exoplayer2/RenderersFactory.java new file mode 100644 index 0000000000..728cfa387a --- /dev/null +++ b/library/core/src/main/java/com/google/android/exoplayer2/RenderersFactory.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.exoplayer2; + +import android.os.Handler; +import com.google.android.exoplayer2.audio.AudioRendererEventListener; +import com.google.android.exoplayer2.metadata.MetadataRenderer; +import com.google.android.exoplayer2.text.TextRenderer; +import com.google.android.exoplayer2.video.VideoRendererEventListener; + +/** + * Builds {@link Renderer} instances for use by a {@link SimpleExoPlayer}. + */ +public interface RenderersFactory { + + /** + * Builds the {@link Renderer} instances for a {@link SimpleExoPlayer}. + * + * @param eventHandler A handler to use when invoking event listeners and outputs. + * @param videoRendererEventListener An event listener for video renderers. + * @param videoRendererEventListener An event listener for audio renderers. + * @param textRendererOutput An output for text renderers. + * @param metadataRendererOutput An output for metadata renderers. + * @return The {@link Renderer instances}. + */ + Renderer[] createRenderers(Handler eventHandler, + VideoRendererEventListener videoRendererEventListener, + AudioRendererEventListener audioRendererEventListener, + TextRenderer.Output textRendererOutput, MetadataRenderer.Output metadataRendererOutput); + +} 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 173078dad7..28ba8cf9d7 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 @@ -16,26 +16,18 @@ package com.google.android.exoplayer2; import android.annotation.TargetApi; -import android.content.Context; import android.graphics.SurfaceTexture; import android.media.MediaCodec; import android.media.PlaybackParams; import android.os.Handler; -import android.support.annotation.IntDef; import android.support.annotation.Nullable; import android.util.Log; import android.view.Surface; import android.view.SurfaceHolder; import android.view.SurfaceView; import android.view.TextureView; -import com.google.android.exoplayer2.audio.AudioCapabilities; -import com.google.android.exoplayer2.audio.AudioProcessor; import com.google.android.exoplayer2.audio.AudioRendererEventListener; -import com.google.android.exoplayer2.audio.MediaCodecAudioRenderer; import com.google.android.exoplayer2.decoder.DecoderCounters; -import com.google.android.exoplayer2.drm.DrmSessionManager; -import com.google.android.exoplayer2.drm.FrameworkMediaCrypto; -import com.google.android.exoplayer2.mediacodec.MediaCodecSelector; import com.google.android.exoplayer2.metadata.Metadata; import com.google.android.exoplayer2.metadata.MetadataRenderer; import com.google.android.exoplayer2.source.MediaSource; @@ -44,12 +36,7 @@ import com.google.android.exoplayer2.text.Cue; import com.google.android.exoplayer2.text.TextRenderer; import com.google.android.exoplayer2.trackselection.TrackSelectionArray; import com.google.android.exoplayer2.trackselection.TrackSelector; -import com.google.android.exoplayer2.video.MediaCodecVideoRenderer; import com.google.android.exoplayer2.video.VideoRendererEventListener; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.reflect.Constructor; -import java.util.ArrayList; import java.util.List; /** @@ -91,38 +78,12 @@ public class SimpleExoPlayer implements ExoPlayer { } - /** - * Modes for using extension renderers. - */ - @Retention(RetentionPolicy.SOURCE) - @IntDef({EXTENSION_RENDERER_MODE_OFF, EXTENSION_RENDERER_MODE_ON, EXTENSION_RENDERER_MODE_PREFER}) - public @interface ExtensionRendererMode {} - /** - * Do not allow use of extension renderers. - */ - public static final int EXTENSION_RENDERER_MODE_OFF = 0; - /** - * Allow use of extension renderers. Extension renderers are indexed after core renderers of the - * same type. A {@link TrackSelector} that prefers the first suitable renderer will therefore - * prefer to use a core renderer to an extension renderer in the case that both are able to play - * a given track. - */ - public static final int EXTENSION_RENDERER_MODE_ON = 1; - /** - * Allow use of extension renderers. Extension renderers are indexed before core renderers of the - * same type. A {@link TrackSelector} that prefers the first suitable renderer will therefore - * prefer to use an extension renderer to a core renderer in the case that both are able to play - * a given track. - */ - public static final int EXTENSION_RENDERER_MODE_PREFER = 2; - private static final String TAG = "SimpleExoPlayer"; - protected static final int MAX_DROPPED_VIDEO_FRAME_COUNT_TO_NOTIFY = 50; + + protected final Renderer[] renderers; private final ExoPlayer player; - private final Renderer[] renderers; private final ComponentListener componentListener; - private final Handler mainHandler; private final int videoRendererCount; private final int audioRendererCount; @@ -147,17 +108,11 @@ public class SimpleExoPlayer implements ExoPlayer { private int audioStreamType; private float audioVolume; - protected SimpleExoPlayer(Context context, TrackSelector trackSelector, LoadControl loadControl, - DrmSessionManager drmSessionManager, - @ExtensionRendererMode int extensionRendererMode, long allowedVideoJoiningTimeMs) { - mainHandler = new Handler(); + protected SimpleExoPlayer(RenderersFactory renderersFactory, TrackSelector trackSelector, + LoadControl loadControl) { componentListener = new ComponentListener(); - - // Build the renderers. - ArrayList renderersList = new ArrayList<>(); - buildRenderers(context, mainHandler, drmSessionManager, extensionRendererMode, - allowedVideoJoiningTimeMs, renderersList); - renderers = renderersList.toArray(new Renderer[renderersList.size()]); + renderers = renderersFactory.createRenderers(new Handler(), componentListener, + componentListener, componentListener, componentListener); // Obtain counts of video and audio renderers. int videoRendererCount = 0; @@ -688,190 +643,6 @@ public class SimpleExoPlayer implements ExoPlayer { return player.isCurrentWindowSeekable(); } - // Renderer building. - - private void buildRenderers(Context context, Handler mainHandler, - DrmSessionManager drmSessionManager, - @ExtensionRendererMode int extensionRendererMode, long allowedVideoJoiningTimeMs, - ArrayList out) { - buildVideoRenderers(context, mainHandler, drmSessionManager, extensionRendererMode, - componentListener, allowedVideoJoiningTimeMs, out); - buildAudioRenderers(context, mainHandler, drmSessionManager, extensionRendererMode, - componentListener, buildAudioProcessors(), out); - buildTextRenderers(context, mainHandler, extensionRendererMode, componentListener, out); - buildMetadataRenderers(context, mainHandler, extensionRendererMode, componentListener, out); - buildMiscellaneousRenderers(context, mainHandler, extensionRendererMode, out); - } - - /** - * Builds video renderers for use by the player. - * - * @param context The {@link Context} associated with the player. - * @param mainHandler A handler associated with the main thread's looper. - * @param drmSessionManager An optional {@link DrmSessionManager}. May be null if the player will - * not be used for DRM protected playbacks. - * @param extensionRendererMode The extension renderer mode. - * @param eventListener An event listener. - * @param allowedVideoJoiningTimeMs The maximum duration in milliseconds for which video renderers - * can attempt to seamlessly join an ongoing playback. - * @param out An array to which the built renderers should be appended. - */ - protected void buildVideoRenderers(Context context, Handler mainHandler, - DrmSessionManager drmSessionManager, - @ExtensionRendererMode int extensionRendererMode, VideoRendererEventListener eventListener, - long allowedVideoJoiningTimeMs, ArrayList out) { - out.add(new MediaCodecVideoRenderer(context, MediaCodecSelector.DEFAULT, - allowedVideoJoiningTimeMs, drmSessionManager, false, mainHandler, eventListener, - MAX_DROPPED_VIDEO_FRAME_COUNT_TO_NOTIFY)); - - if (extensionRendererMode == EXTENSION_RENDERER_MODE_OFF) { - return; - } - int extensionRendererIndex = out.size(); - if (extensionRendererMode == EXTENSION_RENDERER_MODE_PREFER) { - extensionRendererIndex--; - } - - try { - Class clazz = - Class.forName("com.google.android.exoplayer2.ext.vp9.LibvpxVideoRenderer"); - Constructor constructor = clazz.getConstructor(boolean.class, long.class, Handler.class, - VideoRendererEventListener.class, int.class); - Renderer renderer = (Renderer) constructor.newInstance(true, allowedVideoJoiningTimeMs, - mainHandler, eventListener, MAX_DROPPED_VIDEO_FRAME_COUNT_TO_NOTIFY); - out.add(extensionRendererIndex++, renderer); - Log.i(TAG, "Loaded LibvpxVideoRenderer."); - } catch (ClassNotFoundException e) { - // Expected if the app was built without the extension. - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - /** - * Builds audio renderers for use by the player. - * - * @param context The {@link Context} associated with the player. - * @param mainHandler A handler associated with the main thread's looper. - * @param drmSessionManager An optional {@link DrmSessionManager}. May be null if the player will - * not be used for DRM protected playbacks. - * @param extensionRendererMode The extension renderer mode. - * @param eventListener An event listener. - * @param audioProcessors An array of {@link AudioProcessor}s that will process PCM audio buffers - * before output. May be empty. - * @param out An array to which the built renderers should be appended. - */ - protected void buildAudioRenderers(Context context, Handler mainHandler, - DrmSessionManager drmSessionManager, - @ExtensionRendererMode int extensionRendererMode, AudioRendererEventListener eventListener, - AudioProcessor[] audioProcessors, ArrayList out) { - out.add(new MediaCodecAudioRenderer(MediaCodecSelector.DEFAULT, drmSessionManager, true, - mainHandler, eventListener, AudioCapabilities.getCapabilities(context), audioProcessors)); - - if (extensionRendererMode == EXTENSION_RENDERER_MODE_OFF) { - return; - } - int extensionRendererIndex = out.size(); - if (extensionRendererMode == EXTENSION_RENDERER_MODE_PREFER) { - extensionRendererIndex--; - } - - try { - Class clazz = - Class.forName("com.google.android.exoplayer2.ext.opus.LibopusAudioRenderer"); - Constructor constructor = clazz.getConstructor(Handler.class, - AudioRendererEventListener.class, AudioProcessor[].class); - Renderer renderer = (Renderer) constructor.newInstance(mainHandler, eventListener, - audioProcessors); - out.add(extensionRendererIndex++, renderer); - Log.i(TAG, "Loaded LibopusAudioRenderer."); - } catch (ClassNotFoundException e) { - // Expected if the app was built without the extension. - } catch (Exception e) { - throw new RuntimeException(e); - } - - try { - Class clazz = - Class.forName("com.google.android.exoplayer2.ext.flac.LibflacAudioRenderer"); - Constructor constructor = clazz.getConstructor(Handler.class, - AudioRendererEventListener.class, AudioProcessor[].class); - Renderer renderer = (Renderer) constructor.newInstance(mainHandler, eventListener, - audioProcessors); - out.add(extensionRendererIndex++, renderer); - Log.i(TAG, "Loaded LibflacAudioRenderer."); - } catch (ClassNotFoundException e) { - // Expected if the app was built without the extension. - } catch (Exception e) { - throw new RuntimeException(e); - } - - try { - Class clazz = - Class.forName("com.google.android.exoplayer2.ext.ffmpeg.FfmpegAudioRenderer"); - Constructor constructor = clazz.getConstructor(Handler.class, - AudioRendererEventListener.class, AudioProcessor[].class); - Renderer renderer = (Renderer) constructor.newInstance(mainHandler, eventListener, - audioProcessors); - out.add(extensionRendererIndex++, renderer); - Log.i(TAG, "Loaded FfmpegAudioRenderer."); - } catch (ClassNotFoundException e) { - // Expected if the app was built without the extension. - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - /** - * Builds text renderers for use by the player. - * - * @param context The {@link Context} associated with the player. - * @param mainHandler A handler associated with the main thread's looper. - * @param extensionRendererMode The extension renderer mode. - * @param output An output for the renderers. - * @param out An array to which the built renderers should be appended. - */ - protected void buildTextRenderers(Context context, Handler mainHandler, - @ExtensionRendererMode int extensionRendererMode, TextRenderer.Output output, - ArrayList out) { - out.add(new TextRenderer(output, mainHandler.getLooper())); - } - - /** - * Builds metadata renderers for use by the player. - * - * @param context The {@link Context} associated with the player. - * @param mainHandler A handler associated with the main thread's looper. - * @param extensionRendererMode The extension renderer mode. - * @param output An output for the renderers. - * @param out An array to which the built renderers should be appended. - */ - protected void buildMetadataRenderers(Context context, Handler mainHandler, - @ExtensionRendererMode int extensionRendererMode, MetadataRenderer.Output output, - ArrayList out) { - out.add(new MetadataRenderer(output, mainHandler.getLooper())); - } - - /** - * Builds any miscellaneous renderers used by the player. - * - * @param context The {@link Context} associated with the player. - * @param mainHandler A handler associated with the main thread's looper. - * @param extensionRendererMode The extension renderer mode. - * @param out An array to which the built renderers should be appended. - */ - protected void buildMiscellaneousRenderers(Context context, Handler mainHandler, - @ExtensionRendererMode int extensionRendererMode, ArrayList out) { - // Do nothing. - } - - /** - * Builds an array of {@link AudioProcessor}s that will process PCM audio before output. - */ - protected AudioProcessor[] buildAudioProcessors() { - return new AudioProcessor[0]; - } - // Internal methods. private void removeSurfaceCallbacks() { diff --git a/playbacktests/src/androidTest/java/com/google/android/exoplayer2/playbacktests/gts/DashTestRunner.java b/playbacktests/src/androidTest/java/com/google/android/exoplayer2/playbacktests/gts/DashTestRunner.java index 6374fd97a9..381a873d94 100644 --- a/playbacktests/src/androidTest/java/com/google/android/exoplayer2/playbacktests/gts/DashTestRunner.java +++ b/playbacktests/src/androidTest/java/com/google/android/exoplayer2/playbacktests/gts/DashTestRunner.java @@ -25,8 +25,8 @@ import android.net.Uri; import android.util.Log; import android.view.Surface; import com.google.android.exoplayer2.C; -import com.google.android.exoplayer2.DefaultLoadControl; import com.google.android.exoplayer2.ExoPlaybackException; +import com.google.android.exoplayer2.ExoPlayerFactory; import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.RendererCapabilities; import com.google.android.exoplayer2.SimpleExoPlayer; @@ -39,7 +39,7 @@ import com.google.android.exoplayer2.drm.MediaDrmCallback; import com.google.android.exoplayer2.drm.UnsupportedDrmException; import com.google.android.exoplayer2.mediacodec.MediaCodecUtil; import com.google.android.exoplayer2.playbacktests.util.ActionSchedule; -import com.google.android.exoplayer2.playbacktests.util.DebugSimpleExoPlayer; +import com.google.android.exoplayer2.playbacktests.util.DebugRenderersFactory; import com.google.android.exoplayer2.playbacktests.util.DecoderCountersUtil; import com.google.android.exoplayer2.playbacktests.util.ExoHostedTest; import com.google.android.exoplayer2.playbacktests.util.HostActivity; @@ -296,8 +296,8 @@ public final class DashTestRunner { protected SimpleExoPlayer buildExoPlayer(HostActivity host, Surface surface, MappingTrackSelector trackSelector, DrmSessionManager drmSessionManager) { - SimpleExoPlayer player = new DebugSimpleExoPlayer(host, trackSelector, - new DefaultLoadControl(), drmSessionManager); + SimpleExoPlayer player = ExoPlayerFactory.newSimpleInstance( + new DebugRenderersFactory(host, drmSessionManager), trackSelector); player.setVideoSurface(surface); return player; } diff --git a/playbacktests/src/main/java/com/google/android/exoplayer2/playbacktests/util/DebugSimpleExoPlayer.java b/playbacktests/src/main/java/com/google/android/exoplayer2/playbacktests/util/DebugRenderersFactory.java similarity index 80% rename from playbacktests/src/main/java/com/google/android/exoplayer2/playbacktests/util/DebugSimpleExoPlayer.java rename to playbacktests/src/main/java/com/google/android/exoplayer2/playbacktests/util/DebugRenderersFactory.java index c530ab63c1..6cb7673ebd 100644 --- a/playbacktests/src/main/java/com/google/android/exoplayer2/playbacktests/util/DebugSimpleExoPlayer.java +++ b/playbacktests/src/main/java/com/google/android/exoplayer2/playbacktests/util/DebugRenderersFactory.java @@ -18,39 +18,37 @@ package com.google.android.exoplayer2.playbacktests.util; import android.annotation.TargetApi; import android.content.Context; import android.os.Handler; +import com.google.android.exoplayer2.DefaultRenderersFactory; import com.google.android.exoplayer2.ExoPlaybackException; import com.google.android.exoplayer2.Format; -import com.google.android.exoplayer2.LoadControl; import com.google.android.exoplayer2.Renderer; -import com.google.android.exoplayer2.SimpleExoPlayer; import com.google.android.exoplayer2.decoder.DecoderInputBuffer; import com.google.android.exoplayer2.drm.DrmSessionManager; import com.google.android.exoplayer2.drm.FrameworkMediaCrypto; import com.google.android.exoplayer2.mediacodec.MediaCodecSelector; -import com.google.android.exoplayer2.trackselection.TrackSelector; import com.google.android.exoplayer2.video.MediaCodecVideoRenderer; import com.google.android.exoplayer2.video.VideoRendererEventListener; import java.util.ArrayList; /** - * A debug extension of {@link SimpleExoPlayer}. Provides video buffer timestamp assertions. + * A debug extension of {@link DefaultRenderersFactory}. Provides a video renderer that performs + * video buffer timestamp assertions. */ @TargetApi(16) -public class DebugSimpleExoPlayer extends SimpleExoPlayer { +public class DebugRenderersFactory extends DefaultRenderersFactory { - public DebugSimpleExoPlayer(Context context, TrackSelector trackSelector, - LoadControl loadControl, DrmSessionManager drmSessionManager) { - super(context, trackSelector, loadControl, drmSessionManager, - SimpleExoPlayer.EXTENSION_RENDERER_MODE_OFF, 0); + public DebugRenderersFactory(Context context, + DrmSessionManager drmSessionManager) { + super(context, drmSessionManager, DefaultRenderersFactory.EXTENSION_RENDERER_MODE_OFF, 0); } @Override - protected void buildVideoRenderers(Context context, Handler mainHandler, - DrmSessionManager drmSessionManager, - @ExtensionRendererMode int extensionRendererMode, VideoRendererEventListener eventListener, - long allowedVideoJoiningTimeMs, ArrayList out) { + protected void buildVideoRenderers(Context context, + DrmSessionManager drmSessionManager, long allowedVideoJoiningTimeMs, + Handler eventHandler, VideoRendererEventListener eventListener, + @ExtensionRendererMode int extensionRendererMode, ArrayList out) { out.add(new DebugMediaCodecVideoRenderer(context, MediaCodecSelector.DEFAULT, - allowedVideoJoiningTimeMs, mainHandler, drmSessionManager, eventListener, + allowedVideoJoiningTimeMs, drmSessionManager, eventHandler, eventListener, MAX_DROPPED_VIDEO_FRAME_COUNT_TO_NOTIFY)); } @@ -70,9 +68,9 @@ public class DebugSimpleExoPlayer extends SimpleExoPlayer { private int minimumInsertIndex; public DebugMediaCodecVideoRenderer(Context context, MediaCodecSelector mediaCodecSelector, - long allowedJoiningTimeMs, Handler eventHandler, - DrmSessionManager drmSessionManager, - VideoRendererEventListener eventListener, int maxDroppedFrameCountToNotify) { + long allowedJoiningTimeMs, DrmSessionManager drmSessionManager, + Handler eventHandler, VideoRendererEventListener eventListener, + int maxDroppedFrameCountToNotify) { super(context, mediaCodecSelector, allowedJoiningTimeMs, drmSessionManager, false, eventHandler, eventListener, maxDroppedFrameCountToNotify); } diff --git a/playbacktests/src/main/java/com/google/android/exoplayer2/playbacktests/util/ExoHostedTest.java b/playbacktests/src/main/java/com/google/android/exoplayer2/playbacktests/util/ExoHostedTest.java index b13cfbdd15..f48318687d 100644 --- a/playbacktests/src/main/java/com/google/android/exoplayer2/playbacktests/util/ExoHostedTest.java +++ b/playbacktests/src/main/java/com/google/android/exoplayer2/playbacktests/util/ExoHostedTest.java @@ -19,12 +19,13 @@ import android.os.Handler; import android.os.SystemClock; import android.util.Log; import android.view.Surface; -import com.google.android.exoplayer2.DefaultLoadControl; +import com.google.android.exoplayer2.DefaultRenderersFactory; import com.google.android.exoplayer2.ExoPlaybackException; import com.google.android.exoplayer2.ExoPlayer; import com.google.android.exoplayer2.ExoPlayerFactory; import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.PlaybackParameters; +import com.google.android.exoplayer2.RenderersFactory; import com.google.android.exoplayer2.SimpleExoPlayer; import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.audio.AudioRendererEventListener; @@ -326,9 +327,9 @@ public abstract class ExoHostedTest implements HostedTest, ExoPlayer.EventListen protected SimpleExoPlayer buildExoPlayer(HostActivity host, Surface surface, MappingTrackSelector trackSelector, DrmSessionManager drmSessionManager) { - SimpleExoPlayer player = ExoPlayerFactory.newSimpleInstance(host, trackSelector, - new DefaultLoadControl(), drmSessionManager, SimpleExoPlayer.EXTENSION_RENDERER_MODE_OFF, - 0); + RenderersFactory renderersFactory = new DefaultRenderersFactory(host, drmSessionManager, + DefaultRenderersFactory.EXTENSION_RENDERER_MODE_OFF, 0); + SimpleExoPlayer player = ExoPlayerFactory.newSimpleInstance(renderersFactory, trackSelector); player.setVideoSurface(surface); return player; } From 7761cdcd1ef0d7c9dd9cbe2231247b5d1ab6f9f7 Mon Sep 17 00:00:00 2001 From: olly Date: Wed, 26 Apr 2017 08:12:33 -0700 Subject: [PATCH 119/119] Fill in release note TODOs ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=154298148 --- RELEASENOTES.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index caed0a74c3..d216e767b0 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -3,10 +3,12 @@ ### r2.4.0 ### * New modular library structure. You can read more about depending on individual - library modules [here](TODO). + library modules + [here](https://medium.com/google-exoplayer/exoplayers-new-modular-structure-a916c0874907). * Variable speed playback support on API level 16+. You can read more about - changing the playback speed [here](TODO) - ([#26](https://github.com/google/ExoPlayer/issues/26))." + changing the playback speed + [here](https://medium.com/google-exoplayer/variable-speed-playback-with-exoplayer-e6e6a71e0343) + ([#26](https://github.com/google/ExoPlayer/issues/26)). * New time bar view, including support for displaying ad break markers. * Support DVB subtitles in MPEG-TS and MKV. * Support adaptive playback for audio only DASH, HLS and SmoothStreaming