Merge pull request #8634 from google/dev-v2-r2.13.2

r2.13.2
This commit is contained in:
Marc Baechinger 2021-02-25 13:15:45 +00:00 committed by GitHub
commit ce48a28aec
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
35 changed files with 310 additions and 156 deletions

1
.gitignore vendored
View File

@ -47,6 +47,7 @@ bazel-testlogs
.DS_Store .DS_Store
cmake-build-debug cmake-build-debug
dist dist
jacoco.exec
tmp tmp
# External native builds # External native builds

View File

@ -1,5 +1,38 @@
# Release notes # Release notes
### 2.13.2 (2021-02-25)
* Extractors:
* Add support for MP4 and QuickTime meta atoms that are not full atoms.
* UI:
* Make conditions to enable UI actions consistent in
`DefaultControlDispatcher`, `PlayerControlView`,
`StyledPlayerControlView`, `PlayerNotificationManager` and
`TimelineQueueNavigator`.
* Fix conditions to enable seeking to next/previous media item to handle
the case where a live stream has ended.
* Audio:
* Fix `SimpleExoPlayer` reporting audio session ID as 0 in some cases
([#8585](https://github.com/google/ExoPlayer/issues/8585)).
* IMA extension:
* Fix a bug where playback could get stuck when seeking into a playlist
item with ads, if the preroll ad had preloaded but the window position
of the seek should instead trigger playback of a midroll.
* Fix a bug with playback of ads in playlists, where the incorrect period
index was used when deciding whether to trigger playback of an ad after
a seek.
* Text:
* Parse SSA/ASS font size in `Style:` lines
([#8435](https://github.com/google/ExoPlayer/issues/8435)).
* VP9 extension: Update to use NDK r21
([#8581](https://github.com/google/ExoPlayer/issues/8581)).
* FLAC extension: Update to use NDK r21
([#8581](https://github.com/google/ExoPlayer/issues/8581)).
* Opus extension: Update to use NDK r21
([#8581](https://github.com/google/ExoPlayer/issues/8581)).
* FFmpeg extension: Update to use NDK r21
([#8581](https://github.com/google/ExoPlayer/issues/8581)).
### 2.13.1 (2021-02-12) ### 2.13.1 (2021-02-12)
* Live streaming: * Live streaming:

View File

@ -13,8 +13,8 @@
// limitations under the License. // limitations under the License.
project.ext { project.ext {
// ExoPlayer version and version code. // ExoPlayer version and version code.
releaseVersion = '2.13.1' releaseVersion = '2.13.2'
releaseVersionCode = 2013001 releaseVersionCode = 2013002
minSdkVersion = 16 minSdkVersion = 16
appTargetSdkVersion = 29 appTargetSdkVersion = 29
targetSdkVersion = 28 // TODO: Bump once b/143232359 is resolved. Also fix TODOs in UtilTest. targetSdkVersion = 28 // TODO: Bump once b/143232359 is resolved. Also fix TODOs in UtilTest.

View File

@ -30,7 +30,7 @@ FFMPEG_EXT_PATH="${EXOPLAYER_ROOT}/extensions/ffmpeg/src/main"
``` ```
* Download the [Android NDK][] and set its location in a shell variable. * Download the [Android NDK][] and set its location in a shell variable.
This build configuration has been tested on NDK r20. This build configuration has been tested on NDK r21.
``` ```
NDK_PATH="<path to Android NDK>" NDK_PATH="<path to Android NDK>"

View File

@ -29,7 +29,7 @@ FLAC_EXT_PATH="${EXOPLAYER_ROOT}/extensions/flac/src/main"
``` ```
* Download the [Android NDK][] and set its location in an environment variable. * Download the [Android NDK][] and set its location in an environment variable.
This build configuration has been tested on NDK r20. This build configuration has been tested on NDK r21.
``` ```
NDK_PATH="<path to Android NDK>" NDK_PATH="<path to Android NDK>"

View File

@ -341,11 +341,25 @@ import java.util.Map;
boolean playWhenReady = player.getPlayWhenReady(); boolean playWhenReady = player.getPlayWhenReady();
onTimelineChanged(player.getCurrentTimeline(), Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE); onTimelineChanged(player.getCurrentTimeline(), Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE);
if (!AdPlaybackState.NONE.equals(adPlaybackState) @Nullable AdsManager adsManager = this.adsManager;
&& adsManager != null if (!AdPlaybackState.NONE.equals(adPlaybackState) && adsManager != null && imaPausedContent) {
&& imaPausedContent // Check whether the current ad break matches the expected ad break based on the current
&& playWhenReady) { // position. If not, discard the current ad break so that the correct ad break can load.
adsManager.resume(); long contentPositionMs = getContentPeriodPositionMs(player, timeline, period);
int adGroupForPositionIndex =
adPlaybackState.getAdGroupIndexForPositionUs(
C.msToUs(contentPositionMs), C.msToUs(contentDurationMs));
if (adGroupForPositionIndex != C.INDEX_UNSET
&& imaAdInfo != null
&& imaAdInfo.adGroupIndex != adGroupForPositionIndex) {
if (configuration.debugModeEnabled) {
Log.d(TAG, "Discarding preloaded ad " + imaAdInfo);
}
adsManager.discardAdBreak();
}
if (playWhenReady) {
adsManager.resume();
}
} }
} }
@ -826,7 +840,7 @@ import java.util.Map;
ensureSentContentCompleteIfAtEndOfStream(); ensureSentContentCompleteIfAtEndOfStream();
if (!sentContentComplete && !timeline.isEmpty()) { if (!sentContentComplete && !timeline.isEmpty()) {
long positionMs = getContentPeriodPositionMs(player, timeline, period); long positionMs = getContentPeriodPositionMs(player, timeline, period);
timeline.getPeriod(/* periodIndex= */ 0, period); timeline.getPeriod(player.getCurrentPeriodIndex(), period);
int newAdGroupIndex = period.getAdGroupIndexForPositionUs(C.msToUs(positionMs)); int newAdGroupIndex = period.getAdGroupIndexForPositionUs(C.msToUs(positionMs));
if (newAdGroupIndex != C.INDEX_UNSET) { if (newAdGroupIndex != C.INDEX_UNSET) {
sentPendingContentPositionMs = false; sentPendingContentPositionMs = false;

View File

@ -98,8 +98,8 @@ public abstract class TimelineQueueNavigator implements MediaSessionConnector.Qu
if (!timeline.isEmpty() && !player.isPlayingAd()) { if (!timeline.isEmpty() && !player.isPlayingAd()) {
timeline.getWindow(player.getCurrentWindowIndex(), window); timeline.getWindow(player.getCurrentWindowIndex(), window);
enableSkipTo = timeline.getWindowCount() > 1; enableSkipTo = timeline.getWindowCount() > 1;
enablePrevious = window.isSeekable || !window.isDynamic || player.hasPrevious(); enablePrevious = window.isSeekable || !window.isLive() || player.hasPrevious();
enableNext = window.isDynamic || player.hasNext(); enableNext = (window.isLive() && window.isDynamic) || player.hasNext();
} }
long actions = 0; long actions = 0;

View File

@ -29,7 +29,7 @@ OPUS_EXT_PATH="${EXOPLAYER_ROOT}/extensions/opus/src/main"
``` ```
* Download the [Android NDK][] and set its location in an environment variable. * Download the [Android NDK][] and set its location in an environment variable.
This build configuration has been tested on NDK r20. This build configuration has been tested on NDK r21.
``` ```
NDK_PATH="<path to Android NDK>" NDK_PATH="<path to Android NDK>"

View File

@ -29,7 +29,7 @@ VP9_EXT_PATH="${EXOPLAYER_ROOT}/extensions/vp9/src/main"
``` ```
* Download the [Android NDK][] and set its location in an environment variable. * Download the [Android NDK][] and set its location in an environment variable.
This build configuration has been tested on NDK r20. This build configuration has been tested on NDK r21.
``` ```
NDK_PATH="<path to Android NDK>" NDK_PATH="<path to Android NDK>"

View File

@ -30,11 +30,11 @@ public final class ExoPlayerLibraryInfo {
/** The version of the library expressed as a string, for example "1.2.3". */ /** 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. // Intentionally hardcoded. Do not derive from other constants (e.g. VERSION_INT) or vice versa.
public static final String VERSION = "2.13.1"; public static final String VERSION = "2.13.2";
/** The version of the library expressed as {@code "ExoPlayerLib/" + VERSION}. */ /** The version of the library expressed as {@code "ExoPlayerLib/" + VERSION}. */
// Intentionally hardcoded. Do not derive from other constants (e.g. VERSION) or vice versa. // Intentionally hardcoded. Do not derive from other constants (e.g. VERSION) or vice versa.
public static final String VERSION_SLASHY = "ExoPlayerLib/2.13.1"; public static final String VERSION_SLASHY = "ExoPlayerLib/2.13.2";
/** /**
* The version of the library expressed as an integer, for example 1002003. * The version of the library expressed as an integer, for example 1002003.
@ -44,7 +44,7 @@ public final class ExoPlayerLibraryInfo {
* integer version 123045006 (123-045-006). * integer version 123045006 (123-045-006).
*/ */
// Intentionally hardcoded. Do not derive from other constants (e.g. VERSION) or vice versa. // Intentionally hardcoded. Do not derive from other constants (e.g. VERSION) or vice versa.
public static final int VERSION_INT = 2013001; public static final int VERSION_INT = 2013002;
/** /**
* The default user agent for requests made by the library. * The default user agent for requests made by the library.

View File

@ -79,11 +79,12 @@ public class DefaultControlDispatcher implements ControlDispatcher {
int windowIndex = player.getCurrentWindowIndex(); int windowIndex = player.getCurrentWindowIndex();
timeline.getWindow(windowIndex, window); timeline.getWindow(windowIndex, window);
int previousWindowIndex = player.getPreviousWindowIndex(); int previousWindowIndex = player.getPreviousWindowIndex();
boolean isUnseekableLiveStream = window.isLive() && !window.isSeekable;
if (previousWindowIndex != C.INDEX_UNSET if (previousWindowIndex != C.INDEX_UNSET
&& (player.getCurrentPosition() <= MAX_POSITION_FOR_SEEK_TO_PREVIOUS && (player.getCurrentPosition() <= MAX_POSITION_FOR_SEEK_TO_PREVIOUS
|| (window.isDynamic && !window.isSeekable))) { || isUnseekableLiveStream)) {
player.seekTo(previousWindowIndex, C.TIME_UNSET); player.seekTo(previousWindowIndex, C.TIME_UNSET);
} else { } else if (!isUnseekableLiveStream) {
player.seekTo(windowIndex, /* positionMs= */ 0); player.seekTo(windowIndex, /* positionMs= */ 0);
} }
return true; return true;
@ -96,10 +97,11 @@ public class DefaultControlDispatcher implements ControlDispatcher {
return true; return true;
} }
int windowIndex = player.getCurrentWindowIndex(); int windowIndex = player.getCurrentWindowIndex();
timeline.getWindow(windowIndex, window);
int nextWindowIndex = player.getNextWindowIndex(); int nextWindowIndex = player.getNextWindowIndex();
if (nextWindowIndex != C.INDEX_UNSET) { if (nextWindowIndex != C.INDEX_UNSET) {
player.seekTo(nextWindowIndex, C.TIME_UNSET); player.seekTo(nextWindowIndex, C.TIME_UNSET);
} else if (timeline.getWindow(windowIndex, window).isLive()) { } else if (window.isLive() && window.isDynamic) {
player.seekTo(windowIndex, C.TIME_UNSET); player.seekTo(windowIndex, C.TIME_UNSET);
} }
return true; return true;

View File

@ -2140,7 +2140,6 @@ public class SimpleExoPlayer extends BasePlayer
analyticsCollector.onAudioDisabled(counters); analyticsCollector.onAudioDisabled(counters);
audioFormat = null; audioFormat = null;
audioDecoderCounters = null; audioDecoderCounters = null;
audioSessionId = C.AUDIO_SESSION_ID_UNSET;
} }
@Override @Override

View File

@ -305,11 +305,15 @@ public final class SilenceMediaSource extends BaseMediaSource {
return C.RESULT_BUFFER_READ; return C.RESULT_BUFFER_READ;
} }
buffer.timeUs = getAudioPositionUs(positionBytes);
buffer.addFlag(C.BUFFER_FLAG_KEY_FRAME);
if (buffer.isFlagsOnly()) {
return C.RESULT_BUFFER_READ;
}
int bytesToWrite = (int) min(SILENCE_SAMPLE.length, bytesRemaining); int bytesToWrite = (int) min(SILENCE_SAMPLE.length, bytesRemaining);
buffer.ensureSpaceForWrite(bytesToWrite); buffer.ensureSpaceForWrite(bytesToWrite);
buffer.data.put(SILENCE_SAMPLE, /* offset= */ 0, bytesToWrite); buffer.data.put(SILENCE_SAMPLE, /* offset= */ 0, bytesToWrite);
buffer.timeUs = getAudioPositionUs(positionBytes);
buffer.addFlag(C.BUFFER_FLAG_KEY_FRAME);
positionBytes += bytesToWrite; positionBytes += bytesToWrite;
return C.RESULT_BUFFER_READ; return C.RESULT_BUFFER_READ;
} }

View File

@ -51,9 +51,9 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull; import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
/** /**
* A {@link MediaSource} that inserts ads linearly with a provided content media source. This source * A {@link MediaSource} that inserts ads linearly into a provided content media source.
* cannot be used as a child source in a composition. It must be the top-level source used to *
* prepare the player. * <p>The wrapped content media source must contain a single {@link Timeline.Period}.
*/ */
public final class AdsMediaSource extends CompositeMediaSource<MediaPeriodId> { public final class AdsMediaSource extends CompositeMediaSource<MediaPeriodId> {

View File

@ -314,6 +314,10 @@ public final class SsaDecoder extends SimpleSubtitleDecoder {
/* end= */ spannableText.length(), /* end= */ spannableText.length(),
SpannableString.SPAN_EXCLUSIVE_EXCLUSIVE); SpannableString.SPAN_EXCLUSIVE_EXCLUSIVE);
} }
if (style.fontSize != Cue.DIMEN_UNSET && screenHeight != Cue.DIMEN_UNSET) {
cue.setTextSize(
style.fontSize / screenHeight, Cue.TEXT_SIZE_TYPE_FRACTIONAL_IGNORE_PADDING);
}
} }
@SsaStyle.SsaAlignment int alignment; @SsaStyle.SsaAlignment int alignment;

View File

@ -27,6 +27,7 @@ import androidx.annotation.ColorInt;
import androidx.annotation.IntDef; import androidx.annotation.IntDef;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.text.Cue;
import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.Log; import com.google.android.exoplayer2.util.Log;
import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.util.Util;
@ -90,12 +91,17 @@ import java.util.regex.Pattern;
public final String name; public final String name;
@SsaAlignment public final int alignment; @SsaAlignment public final int alignment;
@Nullable @ColorInt public final Integer primaryColor; @Nullable @ColorInt public final Integer primaryColor;
public final float fontSize;
private SsaStyle( private SsaStyle(
String name, @SsaAlignment int alignment, @Nullable @ColorInt Integer primaryColor) { String name,
@SsaAlignment int alignment,
@Nullable @ColorInt Integer primaryColor,
float fontSize) {
this.name = name; this.name = name;
this.alignment = alignment; this.alignment = alignment;
this.primaryColor = primaryColor; this.primaryColor = primaryColor;
this.fontSize = fontSize;
} }
@Nullable @Nullable
@ -114,7 +120,8 @@ import java.util.regex.Pattern;
return new SsaStyle( return new SsaStyle(
styleValues[format.nameIndex].trim(), styleValues[format.nameIndex].trim(),
parseAlignment(styleValues[format.alignmentIndex].trim()), parseAlignment(styleValues[format.alignmentIndex].trim()),
parseColor(styleValues[format.primaryColorIndex].trim())); parseColor(styleValues[format.primaryColorIndex].trim()),
parseFontSize(styleValues[format.fontSizeIndex].trim()));
} catch (RuntimeException e) { } catch (RuntimeException e) {
Log.w(TAG, "Skipping malformed 'Style:' line: '" + styleLine + "'", e); Log.w(TAG, "Skipping malformed 'Style:' line: '" + styleLine + "'", e);
return null; return null;
@ -191,6 +198,15 @@ import java.util.regex.Pattern;
return Color.argb(a, r, g, b); return Color.argb(a, r, g, b);
} }
private static float parseFontSize(String fontSize) {
try {
return Float.parseFloat(fontSize);
} catch (NumberFormatException e) {
Log.w(TAG, "Failed to parse font size: '" + fontSize + "'", e);
return Cue.DIMEN_UNSET;
}
}
/** /**
* Represents a {@code Format:} line from the {@code [V4+ Styles]} section * Represents a {@code Format:} line from the {@code [V4+ Styles]} section
* *
@ -202,12 +218,15 @@ import java.util.regex.Pattern;
public final int nameIndex; public final int nameIndex;
public final int alignmentIndex; public final int alignmentIndex;
public final int primaryColorIndex; public final int primaryColorIndex;
public final int fontSizeIndex;
public final int length; public final int length;
private Format(int nameIndex, int alignmentIndex, int primaryColorIndex, int length) { private Format(
int nameIndex, int alignmentIndex, int primaryColorIndex, int fontSizeIndex, int length) {
this.nameIndex = nameIndex; this.nameIndex = nameIndex;
this.alignmentIndex = alignmentIndex; this.alignmentIndex = alignmentIndex;
this.primaryColorIndex = primaryColorIndex; this.primaryColorIndex = primaryColorIndex;
this.fontSizeIndex = fontSizeIndex;
this.length = length; this.length = length;
} }
@ -221,6 +240,7 @@ import java.util.regex.Pattern;
int nameIndex = C.INDEX_UNSET; int nameIndex = C.INDEX_UNSET;
int alignmentIndex = C.INDEX_UNSET; int alignmentIndex = C.INDEX_UNSET;
int primaryColorIndex = C.INDEX_UNSET; int primaryColorIndex = C.INDEX_UNSET;
int fontSizeIndex = C.INDEX_UNSET;
String[] keys = String[] keys =
TextUtils.split(styleFormatLine.substring(SsaDecoder.FORMAT_LINE_PREFIX.length()), ","); TextUtils.split(styleFormatLine.substring(SsaDecoder.FORMAT_LINE_PREFIX.length()), ",");
for (int i = 0; i < keys.length; i++) { for (int i = 0; i < keys.length; i++) {
@ -234,10 +254,13 @@ import java.util.regex.Pattern;
case "primarycolour": case "primarycolour":
primaryColorIndex = i; primaryColorIndex = i;
break; break;
case "fontsize":
fontSizeIndex = i;
break;
} }
} }
return nameIndex != C.INDEX_UNSET return nameIndex != C.INDEX_UNSET
? new Format(nameIndex, alignmentIndex, primaryColorIndex, keys.length) ? new Format(nameIndex, alignmentIndex, primaryColorIndex, fontSizeIndex, keys.length)
: null; : null;
} }
} }

View File

@ -47,7 +47,8 @@ public final class SsaDecoderTest {
private static final String INVALID_TIMECODES = "media/ssa/invalid_timecodes"; private static final String INVALID_TIMECODES = "media/ssa/invalid_timecodes";
private static final String INVALID_POSITIONS = "media/ssa/invalid_positioning"; private static final String INVALID_POSITIONS = "media/ssa/invalid_positioning";
private static final String POSITIONS_WITHOUT_PLAYRES = "media/ssa/positioning_without_playres"; private static final String POSITIONS_WITHOUT_PLAYRES = "media/ssa/positioning_without_playres";
private static final String COLORS = "media/ssa/colors"; private static final String STYLE_COLORS = "media/ssa/style_colors";
private static final String STYLE_FONT_SIZE = "media/ssa/style_font_size";
@Test @Test
public void decodeEmpty() throws IOException { public void decodeEmpty() throws IOException {
@ -274,7 +275,7 @@ public final class SsaDecoderTest {
@Test @Test
public void decodeColors() throws IOException { public void decodeColors() throws IOException {
SsaDecoder decoder = new SsaDecoder(); SsaDecoder decoder = new SsaDecoder();
byte[] bytes = TestUtil.getByteArray(ApplicationProvider.getApplicationContext(), COLORS); byte[] bytes = TestUtil.getByteArray(ApplicationProvider.getApplicationContext(), STYLE_COLORS);
Subtitle subtitle = decoder.decode(bytes, bytes.length, false); Subtitle subtitle = decoder.decode(bytes, bytes.length, false);
assertThat(subtitle.getEventTimeCount()).isEqualTo(14); assertThat(subtitle.getEventTimeCount()).isEqualTo(14);
// &H000000FF (AABBGGRR) -> #FFFF0000 (AARRGGBB) // &H000000FF (AABBGGRR) -> #FFFF0000 (AARRGGBB)
@ -319,6 +320,22 @@ public final class SsaDecoderTest {
.hasNoForegroundColorSpanBetween(0, seventhCueText.length()); .hasNoForegroundColorSpanBetween(0, seventhCueText.length());
} }
@Test
public void decodeFontSize() throws IOException {
SsaDecoder decoder = new SsaDecoder();
byte[] bytes =
TestUtil.getByteArray(ApplicationProvider.getApplicationContext(), STYLE_FONT_SIZE);
Subtitle subtitle = decoder.decode(bytes, bytes.length, false);
assertThat(subtitle.getEventTimeCount()).isEqualTo(4);
Cue firstCue = Iterables.getOnlyElement(subtitle.getCues(subtitle.getEventTime(0)));
assertThat(firstCue.textSize).isWithin(1.0e-8f).of(30f / 720f);
assertThat(firstCue.textSizeType).isEqualTo(Cue.TEXT_SIZE_TYPE_FRACTIONAL_IGNORE_PADDING);
Cue secondCue = Iterables.getOnlyElement(subtitle.getCues(subtitle.getEventTime(2)));
assertThat(secondCue.textSize).isWithin(1.0e-8f).of(72.2f / 720f);
assertThat(secondCue.textSizeType).isEqualTo(Cue.TEXT_SIZE_TYPE_FRACTIONAL_IGNORE_PADDING);
}
private static void assertTypicalCue1(Subtitle subtitle, int eventIndex) { private static void assertTypicalCue1(Subtitle subtitle, int eventIndex) {
assertThat(subtitle.getEventTime(eventIndex)).isEqualTo(0); assertThat(subtitle.getEventTime(eventIndex)).isEqualTo(0);
assertThat(subtitle.getCues(subtitle.getEventTime(eventIndex)).get(0).text.toString()) assertThat(subtitle.getCues(subtitle.getEventTime(eventIndex)).get(0).text.toString())

View File

@ -17,7 +17,6 @@ package com.google.android.exoplayer2.upstream.cache;
import static com.google.android.exoplayer2.testutil.CacheAsserts.assertCachedData; import static com.google.android.exoplayer2.testutil.CacheAsserts.assertCachedData;
import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertThat;
import static java.lang.Math.min;
import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertThrows;
import android.net.Uri; import android.net.Uri;
@ -36,65 +35,18 @@ import org.junit.After;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.mockito.Answers;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations; import org.mockito.MockitoAnnotations;
/** Unit tests for {@link CacheWriter}. */ /** Unit tests for {@link CacheWriter}. */
@RunWith(AndroidJUnit4.class) @RunWith(AndroidJUnit4.class)
public final class CacheWriterTest { public final class CacheWriterTest {
/**
* Abstract fake Cache implementation used by the test. This class must be public so Mockito can
* create a proxy for it.
*/
public abstract static class AbstractFakeCache implements Cache {
// This array is set to alternating length of cached and not cached regions in tests:
// spansAndGaps = {<length of 1st cached region>, <length of 1st not cached region>,
// <length of 2nd cached region>, <length of 2nd not cached region>, ... }
// Ideally it should end with a cached region but it shouldn't matter for any code.
private int[] spansAndGaps;
private long contentLength;
private void init() {
spansAndGaps = new int[] {};
contentLength = C.LENGTH_UNSET;
}
@Override
public long getCachedLength(String key, long position, long length) {
if (length == C.LENGTH_UNSET) {
length = Long.MAX_VALUE;
}
for (int i = 0; i < spansAndGaps.length; i++) {
int spanOrGap = spansAndGaps[i];
if (position < spanOrGap) {
long left = min(spanOrGap - position, length);
return (i & 1) == 1 ? -left : left;
}
position -= spanOrGap;
}
return -length;
}
@Override
public ContentMetadata getContentMetadata(String key) {
DefaultContentMetadata metadata = new DefaultContentMetadata();
ContentMetadataMutations mutations = new ContentMetadataMutations();
ContentMetadataMutations.setContentLength(mutations, contentLength);
return metadata.copyWithMutationsApplied(mutations);
}
}
@Mock(answer = Answers.CALLS_REAL_METHODS) private AbstractFakeCache mockCache;
private File tempFolder; private File tempFolder;
private SimpleCache cache; private SimpleCache cache;
@Before @Before
public void setUp() throws Exception { public void setUp() throws Exception {
MockitoAnnotations.initMocks(this); MockitoAnnotations.initMocks(this);
mockCache.init();
tempFolder = tempFolder =
Util.createTempDirectory(ApplicationProvider.getApplicationContext(), "ExoPlayerTest"); Util.createTempDirectory(ApplicationProvider.getApplicationContext(), "ExoPlayerTest");
cache = cache =

View File

@ -145,12 +145,11 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
* Parses a udta atom. * Parses a udta atom.
* *
* @param udtaAtom The udta (user data) atom to decode. * @param udtaAtom The udta (user data) atom to decode.
* @param isQuickTime True for QuickTime media. False otherwise.
* @return A {@link Pair} containing the metadata from the meta child atom as first value (if * @return A {@link Pair} containing the metadata from the meta child atom as first value (if
* any), and the metadata from the smta child atom as second value (if any). * any), and the metadata from the smta child atom as second value (if any).
*/ */
public static Pair<@NullableType Metadata, @NullableType Metadata> parseUdta( public static Pair<@NullableType Metadata, @NullableType Metadata> parseUdta(
Atom.LeafAtom udtaAtom, boolean isQuickTime) { Atom.LeafAtom udtaAtom) {
ParsableByteArray udtaData = udtaAtom.data; ParsableByteArray udtaData = udtaAtom.data;
udtaData.setPosition(Atom.HEADER_SIZE); udtaData.setPosition(Atom.HEADER_SIZE);
@Nullable Metadata metaMetadata = null; @Nullable Metadata metaMetadata = null;
@ -159,8 +158,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
int atomPosition = udtaData.getPosition(); int atomPosition = udtaData.getPosition();
int atomSize = udtaData.readInt(); int atomSize = udtaData.readInt();
int atomType = udtaData.readInt(); int atomType = udtaData.readInt();
// Meta boxes are regular boxes rather than full boxes in QuickTime. Ignore them for now. if (atomType == Atom.TYPE_meta) {
if (atomType == Atom.TYPE_meta && !isQuickTime) {
udtaData.setPosition(atomPosition); udtaData.setPosition(atomPosition);
metaMetadata = parseUdtaMeta(udtaData, atomPosition + atomSize); metaMetadata = parseUdtaMeta(udtaData, atomPosition + atomSize);
} else if (atomType == Atom.TYPE_smta) { } else if (atomType == Atom.TYPE_smta) {
@ -227,6 +225,30 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
return entries.isEmpty() ? null : new Metadata(entries); return entries.isEmpty() ? null : new Metadata(entries);
} }
/**
* Possibly skips the version and flags fields (1+3 byte) of a full meta atom.
*
* <p>Atoms of type {@link Atom#TYPE_meta} are defined to be full atoms which have four additional
* bytes for a version and a flags field (see 4.2 'Object Structure' in ISO/IEC 14496-12:2005).
* QuickTime do not have such a full box structure. Since some of these files are encoded wrongly,
* we can't rely on the file type though. Instead we must check the 8 bytes after the common
* header bytes ourselves.
*
* @param meta The 8 or more bytes following the meta atom size and type.
*/
public static void maybeSkipRemainingMetaAtomHeaderBytes(ParsableByteArray meta) {
int endPosition = meta.getPosition();
// The next 8 bytes can be either:
// (iso) [1 byte version + 3 bytes flags][4 byte size of next atom]
// (qt) [4 byte size of next atom ][4 byte hdlr atom type ]
// In case of (iso) we need to skip the next 4 bytes.
meta.skipBytes(4);
if (meta.readInt() != Atom.TYPE_hdlr) {
endPosition += 4;
}
meta.setPosition(endPosition);
}
/** /**
* Parses a trak atom (defined in ISO/IEC 14496-12). * Parses a trak atom (defined in ISO/IEC 14496-12).
* *
@ -677,7 +699,8 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
@Nullable @Nullable
private static Metadata parseUdtaMeta(ParsableByteArray meta, int limit) { private static Metadata parseUdtaMeta(ParsableByteArray meta, int limit) {
meta.skipBytes(Atom.FULL_HEADER_SIZE); meta.skipBytes(Atom.HEADER_SIZE);
maybeSkipRemainingMetaAtomHeaderBytes(meta);
while (meta.getPosition() < limit) { while (meta.getPosition() < limit) {
int atomPosition = meta.getPosition(); int atomPosition = meta.getPosition();
int atomSize = meta.readInt(); int atomSize = meta.readInt();

View File

@ -470,7 +470,7 @@ public final class Mp4Extractor implements Extractor, SeekMap {
@Nullable Atom.LeafAtom udta = moov.getLeafAtomOfType(Atom.TYPE_udta); @Nullable Atom.LeafAtom udta = moov.getLeafAtomOfType(Atom.TYPE_udta);
if (udta != null) { if (udta != null) {
Pair<@NullableType Metadata, @NullableType Metadata> udtaMetadata = Pair<@NullableType Metadata, @NullableType Metadata> udtaMetadata =
AtomParsers.parseUdta(udta, isQuickTime); AtomParsers.parseUdta(udta);
udtaMetaMetadata = udtaMetadata.first; udtaMetaMetadata = udtaMetadata.first;
smtaMetadata = udtaMetadata.second; smtaMetadata = udtaMetadata.second;
if (udtaMetaMetadata != null) { if (udtaMetaMetadata != null) {
@ -727,29 +727,12 @@ public final class Mp4Extractor implements Extractor, SeekMap {
} }
} }
/**
* Possibly skips the version and flags fields (1+3 byte) of a full meta atom of the {@code
* input}.
*
* <p>Atoms of type {@link Atom#TYPE_meta} are defined to be full atoms which have four additional
* bytes for a version and a flags field (see 4.2 'Object Structure' in ISO/IEC 14496-12:2005).
* QuickTime do not have such a full box structure. Since some of these files are encoded wrongly,
* we can't rely on the file type though. Instead we must check the 8 bytes after the common
* header bytes ourselves.
*/
private void maybeSkipRemainingMetaAtomHeaderBytes(ExtractorInput input) throws IOException { private void maybeSkipRemainingMetaAtomHeaderBytes(ExtractorInput input) throws IOException {
scratch.reset(8); scratch.reset(8);
// Peek the next 8 bytes which can be either
// (iso) [1 byte version + 3 bytes flags][4 byte size of next atom]
// (qt) [4 byte size of next atom ][4 byte hdlr atom type ]
// In case of (iso) we need to skip the next 4 bytes.
input.peekFully(scratch.getData(), 0, 8); input.peekFully(scratch.getData(), 0, 8);
scratch.skipBytes(4); AtomParsers.maybeSkipRemainingMetaAtomHeaderBytes(scratch);
if (scratch.readInt() == Atom.TYPE_hdlr) { input.skipFully(scratch.getPosition());
input.resetPeekPosition(); input.resetPeekPosition();
} else {
input.skipFully(4);
}
} }
/** Processes an atom whose payload does not need to be parsed. */ /** Processes an atom whose payload does not need to be parsed. */

View File

@ -913,10 +913,10 @@ public class PlayerControlView extends FrameLayout {
timeline.getWindow(player.getCurrentWindowIndex(), window); timeline.getWindow(player.getCurrentWindowIndex(), window);
boolean isSeekable = window.isSeekable; boolean isSeekable = window.isSeekable;
enableSeeking = isSeekable; enableSeeking = isSeekable;
enablePrevious = isSeekable || !window.isDynamic || player.hasPrevious(); enablePrevious = isSeekable || !window.isLive() || player.hasPrevious();
enableRewind = isSeekable && controlDispatcher.isRewindEnabled(); enableRewind = isSeekable && controlDispatcher.isRewindEnabled();
enableFastForward = isSeekable && controlDispatcher.isFastForwardEnabled(); enableFastForward = isSeekable && controlDispatcher.isFastForwardEnabled();
enableNext = window.isDynamic || player.hasNext(); enableNext = (window.isLive() && window.isDynamic) || player.hasNext();
} }
} }

View File

@ -1227,10 +1227,11 @@ public class PlayerNotificationManager {
Timeline timeline = player.getCurrentTimeline(); Timeline timeline = player.getCurrentTimeline();
if (!timeline.isEmpty() && !player.isPlayingAd()) { if (!timeline.isEmpty() && !player.isPlayingAd()) {
timeline.getWindow(player.getCurrentWindowIndex(), window); timeline.getWindow(player.getCurrentWindowIndex(), window);
enablePrevious = window.isSeekable || !window.isDynamic || player.hasPrevious(); boolean isSeekable = window.isSeekable;
enableRewind = controlDispatcher.isRewindEnabled(); enablePrevious = isSeekable || !window.isLive() || player.hasPrevious();
enableFastForward = controlDispatcher.isFastForwardEnabled(); enableRewind = isSeekable && controlDispatcher.isRewindEnabled();
enableNext = window.isDynamic || player.hasNext(); enableFastForward = isSeekable && controlDispatcher.isFastForwardEnabled();
enableNext = (window.isLive() && window.isDynamic) || player.hasNext();
} }
List<String> stringActions = new ArrayList<>(); List<String> stringActions = new ArrayList<>();

View File

@ -1141,10 +1141,10 @@ public class StyledPlayerControlView extends FrameLayout {
timeline.getWindow(player.getCurrentWindowIndex(), window); timeline.getWindow(player.getCurrentWindowIndex(), window);
boolean isSeekable = window.isSeekable; boolean isSeekable = window.isSeekable;
enableSeeking = isSeekable; enableSeeking = isSeekable;
enablePrevious = isSeekable || !window.isDynamic || player.hasPrevious(); enablePrevious = isSeekable || !window.isLive() || player.hasPrevious();
enableRewind = isSeekable && controlDispatcher.isRewindEnabled(); enableRewind = isSeekable && controlDispatcher.isRewindEnabled();
enableFastForward = isSeekable && controlDispatcher.isFastForwardEnabled(); enableFastForward = isSeekable && controlDispatcher.isFastForwardEnabled();
enableNext = window.isDynamic || player.hasNext(); enableNext = (window.isLive() && window.isDynamic) || player.hasNext();
} }
} }

View File

@ -22,7 +22,7 @@
android:addStatesFromChildren="true" android:addStatesFromChildren="true"
style="@style/ExoStyledControls.Button.Center"> style="@style/ExoStyledControls.Button.Center">
<!-- View's don't have foreground until API 23 so we have to nest in a parent. --> <!-- View's don't have foreground until API 23 so we have to nest in a parent. -->
<Button android:id="@+id/exo_ffwd_with_amount" <Button android:id="@id/exo_ffwd_with_amount"
android:background="@drawable/exo_styled_controls_fastforward" android:background="@drawable/exo_styled_controls_fastforward"
android:layout_marginLeft="0dp" android:layout_marginLeft="0dp"
android:layout_marginRight="0dp" android:layout_marginRight="0dp"

View File

@ -22,7 +22,7 @@
android:addStatesFromChildren="true" android:addStatesFromChildren="true"
style="@style/ExoStyledControls.Button.Center"> style="@style/ExoStyledControls.Button.Center">
<!-- View's don't have foreground until API 23 so we have to nest in a parent. --> <!-- View's don't have foreground until API 23 so we have to nest in a parent. -->
<Button android:id="@+id/exo_rew_with_amount" <Button android:id="@id/exo_rew_with_amount"
android:background="@drawable/exo_styled_controls_rewind" android:background="@drawable/exo_styled_controls_rewind"
android:layout_marginLeft="0dp" android:layout_marginLeft="0dp"
android:layout_marginRight="0dp" android:layout_marginRight="0dp"

View File

@ -15,13 +15,13 @@
--> -->
<merge xmlns:android="http://schemas.android.com/apk/res/android"> <merge xmlns:android="http://schemas.android.com/apk/res/android">
<View android:id="@+id/exo_controls_background" <View android:id="@id/exo_controls_background"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_gravity="center" android:layout_gravity="center"
android:background="@color/exo_black_opacity_60"/> android:background="@color/exo_black_opacity_60"/>
<FrameLayout android:id="@+id/exo_bottom_bar" <FrameLayout android:id="@id/exo_bottom_bar"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="@dimen/exo_styled_bottom_bar_height" android:layout_height="@dimen/exo_styled_bottom_bar_height"
android:layout_marginTop="@dimen/exo_styled_bottom_bar_margin_top" android:layout_marginTop="@dimen/exo_styled_bottom_bar_margin_top"
@ -29,7 +29,7 @@
android:background="@color/exo_bottom_bar_background" android:background="@color/exo_bottom_bar_background"
android:layoutDirection="ltr"> android:layoutDirection="ltr">
<LinearLayout android:id="@+id/exo_time" <LinearLayout android:id="@id/exo_time"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:paddingStart="@dimen/exo_styled_bottom_bar_time_padding" android:paddingStart="@dimen/exo_styled_bottom_bar_time_padding"
@ -39,58 +39,58 @@
android:layout_gravity="center_vertical|start" android:layout_gravity="center_vertical|start"
android:layoutDirection="ltr"> android:layoutDirection="ltr">
<TextView android:id="@+id/exo_position" <TextView android:id="@id/exo_position"
style="@style/ExoStyledControls.TimeText.Position"/> style="@style/ExoStyledControls.TimeText.Position"/>
<TextView <TextView
style="@style/ExoStyledControls.TimeText.Separator"/> style="@style/ExoStyledControls.TimeText.Separator"/>
<TextView android:id="@+id/exo_duration" <TextView android:id="@id/exo_duration"
style="@style/ExoStyledControls.TimeText.Duration"/> style="@style/ExoStyledControls.TimeText.Duration"/>
</LinearLayout> </LinearLayout>
<LinearLayout android:id="@+id/exo_basic_controls" <LinearLayout android:id="@id/exo_basic_controls"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="center_vertical|end" android:layout_gravity="center_vertical|end"
android:layoutDirection="ltr"> android:layoutDirection="ltr">
<ImageButton android:id="@+id/exo_vr" <ImageButton android:id="@id/exo_vr"
style="@style/ExoStyledControls.Button.Bottom.VR"/> style="@style/ExoStyledControls.Button.Bottom.VR"/>
<ImageButton android:id="@+id/exo_shuffle" <ImageButton android:id="@id/exo_shuffle"
style="@style/ExoStyledControls.Button.Bottom.Shuffle"/> style="@style/ExoStyledControls.Button.Bottom.Shuffle"/>
<ImageButton android:id="@+id/exo_repeat_toggle" <ImageButton android:id="@id/exo_repeat_toggle"
style="@style/ExoStyledControls.Button.Bottom.RepeatToggle"/> style="@style/ExoStyledControls.Button.Bottom.RepeatToggle"/>
<ImageButton android:id="@+id/exo_subtitle" <ImageButton android:id="@id/exo_subtitle"
style="@style/ExoStyledControls.Button.Bottom.CC"/> style="@style/ExoStyledControls.Button.Bottom.CC"/>
<ImageButton android:id="@+id/exo_settings" <ImageButton android:id="@id/exo_settings"
style="@style/ExoStyledControls.Button.Bottom.Settings"/> style="@style/ExoStyledControls.Button.Bottom.Settings"/>
<ImageButton android:id="@+id/exo_fullscreen" <ImageButton android:id="@id/exo_fullscreen"
style="@style/ExoStyledControls.Button.Bottom.FullScreen"/> style="@style/ExoStyledControls.Button.Bottom.FullScreen"/>
<ImageButton android:id="@+id/exo_overflow_show" <ImageButton android:id="@id/exo_overflow_show"
style="@style/ExoStyledControls.Button.Bottom.OverflowShow"/> style="@style/ExoStyledControls.Button.Bottom.OverflowShow"/>
</LinearLayout> </LinearLayout>
<HorizontalScrollView android:id="@+id/exo_extra_controls_scroll_view" <HorizontalScrollView android:id="@id/exo_extra_controls_scroll_view"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="center_vertical|end" android:layout_gravity="center_vertical|end"
android:visibility="invisible"> android:visibility="invisible">
<LinearLayout android:id="@+id/exo_extra_controls" <LinearLayout android:id="@id/exo_extra_controls"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layoutDirection="ltr"> android:layoutDirection="ltr">
<ImageButton android:id="@+id/exo_overflow_hide" <ImageButton android:id="@id/exo_overflow_hide"
style="@style/ExoStyledControls.Button.Bottom.OverflowHide"/> style="@style/ExoStyledControls.Button.Bottom.OverflowHide"/>
</LinearLayout> </LinearLayout>
@ -99,13 +99,13 @@
</FrameLayout> </FrameLayout>
<View android:id="@+id/exo_progress_placeholder" <View android:id="@id/exo_progress_placeholder"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="@dimen/exo_styled_progress_layout_height" android:layout_height="@dimen/exo_styled_progress_layout_height"
android:layout_gravity="bottom" android:layout_gravity="bottom"
android:layout_marginBottom="@dimen/exo_styled_progress_margin_bottom"/> android:layout_marginBottom="@dimen/exo_styled_progress_margin_bottom"/>
<LinearLayout android:id="@+id/exo_minimal_controls" <LinearLayout android:id="@id/exo_minimal_controls"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="bottom|end" android:layout_gravity="bottom|end"
@ -114,13 +114,13 @@
android:gravity="center_vertical" android:gravity="center_vertical"
android:layoutDirection="ltr"> android:layoutDirection="ltr">
<ImageButton android:id="@+id/exo_minimal_fullscreen" <ImageButton android:id="@id/exo_minimal_fullscreen"
style="@style/ExoStyledControls.Button.Bottom.FullScreen"/> style="@style/ExoStyledControls.Button.Bottom.FullScreen"/>
</LinearLayout> </LinearLayout>
<LinearLayout <LinearLayout
android:id="@+id/exo_center_controls" android:id="@id/exo_center_controls"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="center" android:layout_gravity="center"
@ -128,18 +128,17 @@
android:gravity="center" android:gravity="center"
android:padding="@dimen/exo_styled_controls_padding"> android:padding="@dimen/exo_styled_controls_padding">
<ImageButton android:id="@id/exo_prev"
<ImageButton android:id="@+id/exo_prev"
style="@style/ExoStyledControls.Button.Center.Previous"/> style="@style/ExoStyledControls.Button.Center.Previous"/>
<include layout="@layout/exo_styled_player_control_rewind_button" /> <include layout="@layout/exo_styled_player_control_rewind_button" />
<ImageButton android:id="@+id/exo_play_pause" <ImageButton android:id="@id/exo_play_pause"
style="@style/ExoStyledControls.Button.Center.PlayPause"/> style="@style/ExoStyledControls.Button.Center.PlayPause"/>
<include layout="@layout/exo_styled_player_control_ffwd_button" /> <include layout="@layout/exo_styled_player_control_ffwd_button" />
<ImageButton android:id="@+id/exo_next" <ImageButton android:id="@id/exo_next"
style="@style/ExoStyledControls.Button.Center.Next"/> style="@style/ExoStyledControls.Button.Center.Next"/>
</LinearLayout> </LinearLayout>

View File

@ -58,4 +58,5 @@
android:textDirection="locale" android:textDirection="locale"
android:textSize="@dimen/exo_settings_sub_text_size"/> android:textSize="@dimen/exo_settings_sub_text_size"/>
</LinearLayout> </LinearLayout>
</LinearLayout> </LinearLayout>

View File

@ -43,4 +43,5 @@
android:textColor="@color/exo_white" android:textColor="@color/exo_white"
android:textDirection="locale" android:textDirection="locale"
android:textSize="@dimen/exo_settings_main_text_size"/> android:textSize="@dimen/exo_settings_main_text_size"/>
</LinearLayout> </LinearLayout>

View File

@ -27,7 +27,9 @@
<item name="exo_pause" type="id"/> <item name="exo_pause" type="id"/>
<item name="exo_play_pause" type="id"/> <item name="exo_play_pause" type="id"/>
<item name="exo_rew" type="id"/> <item name="exo_rew" type="id"/>
<item name="exo_rew_with_amount" type="id"/>
<item name="exo_ffwd" type="id"/> <item name="exo_ffwd" type="id"/>
<item name="exo_ffwd_with_amount" type="id"/>
<item name="exo_prev" type="id"/> <item name="exo_prev" type="id"/>
<item name="exo_next" type="id"/> <item name="exo_next" type="id"/>
<item name="exo_shuffle" type="id"/> <item name="exo_shuffle" type="id"/>
@ -41,4 +43,17 @@
<item name="exo_vr" type="id"/> <item name="exo_vr" type="id"/>
<item name="exo_subtitle" type="id"/> <item name="exo_subtitle" type="id"/>
<item name="exo_fullscreen" type="id"/> <item name="exo_fullscreen" type="id"/>
<item name="exo_settings" type="id"/>
<item name="exo_controls_background" type="id"/>
<item name="exo_basic_controls" type="id"/>
<item name="exo_center_controls" type="id"/>
<item name="exo_bottom_bar" type="id"/>
<item name="exo_time" type="id"/>
<item name="exo_overflow_show" type="id"/>
<item name="exo_overflow_hide" type="id"/>
<item name="exo_extra_controls" type="id"/>
<item name="exo_extra_controls_scroll_view" type="id"/>
<item name="exo_minimal_fullscreen" type="id"/>
<item name="exo_minimal_controls" type="id"/>
</resources> </resources>

View File

@ -11,8 +11,10 @@
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
if (project.ext.has("exoplayerPublishEnabled") if (project.ext.has("exoplayerPublishEnabled")
&& project.ext.exoplayerPublishEnabled) { && project.ext.exoplayerPublishEnabled) {
// For publishing to Bintray.
apply plugin: 'bintray-release' apply plugin: 'bintray-release'
publish { publish {
artifactId = releaseArtifact artifactId = releaseArtifact
@ -38,6 +40,47 @@ if (project.ext.has("exoplayerPublishEnabled")
} }
} }
} }
} else {
// For publishing to a Maven repository.
apply plugin: 'maven-publish'
afterEvaluate {
publishing {
repositories {
maven {
url = findProperty('mavenRepo') ?: "${buildDir}/repo"
}
}
publications {
release(MavenPublication) {
from components.release
artifact androidSourcesJar
groupId = 'com.google.android.exoplayer'
artifactId = releaseArtifact
version releaseVersion
pom {
name = releaseArtifact
description = releaseDescription
licenses {
license {
name = 'The Apache Software License, Version 2.0'
url = 'http://www.apache.org/licenses/LICENSE-2.0.txt'
distribution = 'repo'
}
}
developers {
developer {
name = 'The Android Open Source Project'
}
}
scm {
connection = 'scm:git:https://github.com/google/ExoPlayer.git'
url = 'https://github.com/google/ExoPlayer'
}
}
}
}
}
}
} }
def getBintrayRepo() { def getBintrayRepo() {
@ -66,3 +109,8 @@ static void addLicense(File pom) {
printer.print(xml) printer.print(xml)
writer.close() writer.close()
} }
task androidSourcesJar(type: Jar) {
archiveClassifier.set('sources')
from android.sourceSets.main.java.srcDirs
}

View File

@ -0,0 +1,18 @@
[Script Info]
Title: SSA/ASS Test
Original Script: Arnold Szabo
Script Type: V4.00+
PlayResX: 1280
PlayResY: 720
[V4+ Styles]
Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding
Style: FontSizeSmall ,Roboto,30, &H000000FF,&H000000FF,&H00000000,&H00000000,0,0,0,0,100,100,0,0,1,3,0,2,50,50,70,1
Style: FontSizeBig ,Roboto,72.2,&H000000FF,&H000000FF,&H00000000,&H00000000,0,0,0,0,100,100,0,0,1,3,0,2,50,50,70,1
[Events]
Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
Dialogue: 0,0:00:00.95,0:00:03.11,FontSizeSmall ,Arnold,0,0,0,,First line with font size 30.
Dialogue: 0,0:00:08.50,0:00:11.50,FontSizeBig ,Arnold,0,0,0,,Second line with font size 72.2.

View File

@ -147,7 +147,6 @@ public class HttpDataSourceTestEnv extends ExternalResource {
.setName(name) .setName(name)
.setUri(Uri.parse(server.url(resource.getPath()).toString())) .setUri(Uri.parse(server.url(resource.getPath()).toString()))
.setExpectedBytes(resource.getData()) .setExpectedBytes(resource.getData())
.setEndOfInputExpected(!resource.resolvesToUnknownLength())
.build(); .build();
} }
} }

View File

@ -33,6 +33,7 @@ import com.google.common.collect.Maps;
import java.lang.annotation.Documented; import java.lang.annotation.Documented;
import java.lang.annotation.Retention; import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy;
import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
@ -277,21 +278,19 @@ public class WebServerDispatcher extends Dispatcher {
if (!resource.supportsRangeRequests() || rangeHeader == null) { if (!resource.supportsRangeRequests() || rangeHeader == null) {
switch (preferredContentCoding) { switch (preferredContentCoding) {
case "gzip": case "gzip":
setResponseBody(
response, Util.gzip(resourceData), /* chunked= */ resource.resolvesToUnknownLength);
response response
.setBody(new Buffer().write(Util.gzip(resourceData)))
.setHeader("Content-Encoding", "gzip"); .setHeader("Content-Encoding", "gzip");
break; break;
case "identity": case "identity":
setResponseBody(response, resourceData, /* chunked= */ resource.resolvesToUnknownLength);
response response
.setBody(new Buffer().write(resourceData))
.setHeader("Content-Encoding", "identity"); .setHeader("Content-Encoding", "identity");
break; break;
default: default:
throw new IllegalStateException("Unexpected content coding: " + preferredContentCoding); throw new IllegalStateException("Unexpected content coding: " + preferredContentCoding);
} }
if (resource.resolvesToUnknownLength()) {
response.setHeader("Content-Length", "");
}
return response; return response;
} }
@ -328,11 +327,11 @@ public class WebServerDispatcher extends Dispatcher {
+ "-" + "-"
+ (resourceData.length - 1) + (resourceData.length - 1)
+ "/" + "/"
+ (resource.resolvesToUnknownLength() ? "*" : resourceData.length)) + (resource.resolvesToUnknownLength() ? "*" : resourceData.length));
.setBody(new Buffer().write(resourceData, start, resourceData.length - start)); setResponseBody(
if (resource.resolvesToUnknownLength()) { response,
response.setHeader("Content-Length", ""); Arrays.copyOfRange(resourceData, start, resourceData.length),
} /* chunked= */ resource.resolvesToUnknownLength);
return response; return response;
} }
@ -345,7 +344,7 @@ public class WebServerDispatcher extends Dispatcher {
} }
int end = min(range.second + 1, resourceData.length); int end = min(range.second + 1, resourceData.length);
return response response
.setResponseCode(206) .setResponseCode(206)
.setHeader( .setHeader(
"Content-Range", "Content-Range",
@ -354,8 +353,26 @@ public class WebServerDispatcher extends Dispatcher {
+ "-" + "-"
+ (end - 1) + (end - 1)
+ "/" + "/"
+ (resource.resolvesToUnknownLength() ? "*" : resourceData.length)) + (resource.resolvesToUnknownLength() ? "*" : resourceData.length));
.setBody(new Buffer().write(resourceData, range.first, end - range.first)); setResponseBody(
response, Arrays.copyOfRange(resourceData, range.first, end), /* chunked= */ false);
return response;
}
/**
* Populates a response with the specified body.
*
* @param response The response whose body should be populated.
* @param body The body data.
* @param chunked Whether to use chunked transfer encoding. Note that if set to {@code true}, the
* "Content-Length" header will not be set.
*/
private static void setResponseBody(MockResponse response, byte[] body, boolean chunked) {
if (chunked) {
response.setChunkedBody(new Buffer().write(body), /* maxChunkSize= */ Integer.MAX_VALUE);
} else {
response.setBody(new Buffer().write(body));
}
} }
/** /**

View File

@ -257,7 +257,7 @@ public class WebServerDispatcherTest {
try (Response response = client.newCall(request).execute()) { try (Response response = client.newCall(request).execute()) {
assertThat(response.code()).isEqualTo(200); assertThat(response.code()).isEqualTo(200);
assertThat(response.header("Accept-Ranges")).isEqualTo("bytes"); assertThat(response.header("Accept-Ranges")).isEqualTo("bytes");
assertThat(response.header("Content-Length")).isEmpty(); assertThat(response.header("Content-Length")).isNull();
assertThat(response.header("Content-Range")).isNull(); assertThat(response.header("Content-Range")).isNull();
assertThat(response.body().contentLength()).isEqualTo(-1); assertThat(response.body().contentLength()).isEqualTo(-1);
@ -298,7 +298,7 @@ public class WebServerDispatcherTest {
try (Response response = client.newCall(request).execute()) { try (Response response = client.newCall(request).execute()) {
assertThat(response.code()).isEqualTo(206); assertThat(response.code()).isEqualTo(206);
assertThat(response.header("Accept-Ranges")).isEqualTo("bytes"); assertThat(response.header("Accept-Ranges")).isEqualTo("bytes");
assertThat(response.header("Content-Length")).isEmpty(); assertThat(response.header("Content-Length")).isNull();
assertThat(response.header("Content-Range")).isEqualTo("bytes 5-19/*"); assertThat(response.header("Content-Range")).isEqualTo("bytes 5-19/*");
assertThat(response.body().contentLength()).isEqualTo(-1); assertThat(response.body().contentLength()).isEqualTo(-1);