mirror of
https://github.com/androidx/media.git
synced 2025-05-05 14:40:50 +08:00
commit
ce48a28aec
1
.gitignore
vendored
1
.gitignore
vendored
@ -47,6 +47,7 @@ bazel-testlogs
|
||||
.DS_Store
|
||||
cmake-build-debug
|
||||
dist
|
||||
jacoco.exec
|
||||
tmp
|
||||
|
||||
# External native builds
|
||||
|
@ -1,5 +1,38 @@
|
||||
# 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)
|
||||
|
||||
* Live streaming:
|
||||
|
@ -13,8 +13,8 @@
|
||||
// limitations under the License.
|
||||
project.ext {
|
||||
// ExoPlayer version and version code.
|
||||
releaseVersion = '2.13.1'
|
||||
releaseVersionCode = 2013001
|
||||
releaseVersion = '2.13.2'
|
||||
releaseVersionCode = 2013002
|
||||
minSdkVersion = 16
|
||||
appTargetSdkVersion = 29
|
||||
targetSdkVersion = 28 // TODO: Bump once b/143232359 is resolved. Also fix TODOs in UtilTest.
|
||||
|
@ -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.
|
||||
This build configuration has been tested on NDK r20.
|
||||
This build configuration has been tested on NDK r21.
|
||||
|
||||
```
|
||||
NDK_PATH="<path to Android NDK>"
|
||||
|
@ -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.
|
||||
This build configuration has been tested on NDK r20.
|
||||
This build configuration has been tested on NDK r21.
|
||||
|
||||
```
|
||||
NDK_PATH="<path to Android NDK>"
|
||||
|
@ -341,11 +341,25 @@ import java.util.Map;
|
||||
|
||||
boolean playWhenReady = player.getPlayWhenReady();
|
||||
onTimelineChanged(player.getCurrentTimeline(), Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE);
|
||||
if (!AdPlaybackState.NONE.equals(adPlaybackState)
|
||||
&& adsManager != null
|
||||
&& imaPausedContent
|
||||
&& playWhenReady) {
|
||||
adsManager.resume();
|
||||
@Nullable AdsManager adsManager = this.adsManager;
|
||||
if (!AdPlaybackState.NONE.equals(adPlaybackState) && adsManager != null && imaPausedContent) {
|
||||
// Check whether the current ad break matches the expected ad break based on the current
|
||||
// position. If not, discard the current ad break so that the correct ad break can load.
|
||||
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();
|
||||
if (!sentContentComplete && !timeline.isEmpty()) {
|
||||
long positionMs = getContentPeriodPositionMs(player, timeline, period);
|
||||
timeline.getPeriod(/* periodIndex= */ 0, period);
|
||||
timeline.getPeriod(player.getCurrentPeriodIndex(), period);
|
||||
int newAdGroupIndex = period.getAdGroupIndexForPositionUs(C.msToUs(positionMs));
|
||||
if (newAdGroupIndex != C.INDEX_UNSET) {
|
||||
sentPendingContentPositionMs = false;
|
||||
|
@ -98,8 +98,8 @@ public abstract class TimelineQueueNavigator implements MediaSessionConnector.Qu
|
||||
if (!timeline.isEmpty() && !player.isPlayingAd()) {
|
||||
timeline.getWindow(player.getCurrentWindowIndex(), window);
|
||||
enableSkipTo = timeline.getWindowCount() > 1;
|
||||
enablePrevious = window.isSeekable || !window.isDynamic || player.hasPrevious();
|
||||
enableNext = window.isDynamic || player.hasNext();
|
||||
enablePrevious = window.isSeekable || !window.isLive() || player.hasPrevious();
|
||||
enableNext = (window.isLive() && window.isDynamic) || player.hasNext();
|
||||
}
|
||||
|
||||
long actions = 0;
|
||||
|
@ -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.
|
||||
This build configuration has been tested on NDK r20.
|
||||
This build configuration has been tested on NDK r21.
|
||||
|
||||
```
|
||||
NDK_PATH="<path to Android NDK>"
|
||||
|
@ -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.
|
||||
This build configuration has been tested on NDK r20.
|
||||
This build configuration has been tested on NDK r21.
|
||||
|
||||
```
|
||||
NDK_PATH="<path to Android NDK>"
|
||||
|
@ -30,11 +30,11 @@ public final class ExoPlayerLibraryInfo {
|
||||
|
||||
/** The version of the library expressed as a string, for example "1.2.3". */
|
||||
// Intentionally hardcoded. Do not derive from other constants (e.g. VERSION_INT) or vice versa.
|
||||
public static final String VERSION = "2.13.1";
|
||||
public static final String VERSION = "2.13.2";
|
||||
|
||||
/** The version of the library expressed as {@code "ExoPlayerLib/" + VERSION}. */
|
||||
// Intentionally hardcoded. Do not derive from other constants (e.g. VERSION) or vice versa.
|
||||
public static final String VERSION_SLASHY = "ExoPlayerLib/2.13.1";
|
||||
public static final String VERSION_SLASHY = "ExoPlayerLib/2.13.2";
|
||||
|
||||
/**
|
||||
* 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).
|
||||
*/
|
||||
// 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.
|
||||
|
@ -79,11 +79,12 @@ public class DefaultControlDispatcher implements ControlDispatcher {
|
||||
int windowIndex = player.getCurrentWindowIndex();
|
||||
timeline.getWindow(windowIndex, window);
|
||||
int previousWindowIndex = player.getPreviousWindowIndex();
|
||||
boolean isUnseekableLiveStream = window.isLive() && !window.isSeekable;
|
||||
if (previousWindowIndex != C.INDEX_UNSET
|
||||
&& (player.getCurrentPosition() <= MAX_POSITION_FOR_SEEK_TO_PREVIOUS
|
||||
|| (window.isDynamic && !window.isSeekable))) {
|
||||
|| isUnseekableLiveStream)) {
|
||||
player.seekTo(previousWindowIndex, C.TIME_UNSET);
|
||||
} else {
|
||||
} else if (!isUnseekableLiveStream) {
|
||||
player.seekTo(windowIndex, /* positionMs= */ 0);
|
||||
}
|
||||
return true;
|
||||
@ -96,10 +97,11 @@ public class DefaultControlDispatcher implements ControlDispatcher {
|
||||
return true;
|
||||
}
|
||||
int windowIndex = player.getCurrentWindowIndex();
|
||||
timeline.getWindow(windowIndex, window);
|
||||
int nextWindowIndex = player.getNextWindowIndex();
|
||||
if (nextWindowIndex != C.INDEX_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);
|
||||
}
|
||||
return true;
|
||||
|
@ -2140,7 +2140,6 @@ public class SimpleExoPlayer extends BasePlayer
|
||||
analyticsCollector.onAudioDisabled(counters);
|
||||
audioFormat = null;
|
||||
audioDecoderCounters = null;
|
||||
audioSessionId = C.AUDIO_SESSION_ID_UNSET;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -305,11 +305,15 @@ public final class SilenceMediaSource extends BaseMediaSource {
|
||||
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);
|
||||
buffer.ensureSpaceForWrite(bytesToWrite);
|
||||
buffer.data.put(SILENCE_SAMPLE, /* offset= */ 0, bytesToWrite);
|
||||
buffer.timeUs = getAudioPositionUs(positionBytes);
|
||||
buffer.addFlag(C.BUFFER_FLAG_KEY_FRAME);
|
||||
positionBytes += bytesToWrite;
|
||||
return C.RESULT_BUFFER_READ;
|
||||
}
|
||||
|
@ -51,9 +51,9 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
|
||||
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
|
||||
/**
|
||||
* A {@link MediaSource} that inserts ads linearly with a provided content media source. This source
|
||||
* cannot be used as a child source in a composition. It must be the top-level source used to
|
||||
* prepare the player.
|
||||
* A {@link MediaSource} that inserts ads linearly into a provided content media source.
|
||||
*
|
||||
* <p>The wrapped content media source must contain a single {@link Timeline.Period}.
|
||||
*/
|
||||
public final class AdsMediaSource extends CompositeMediaSource<MediaPeriodId> {
|
||||
|
||||
|
@ -314,6 +314,10 @@ public final class SsaDecoder extends SimpleSubtitleDecoder {
|
||||
/* end= */ spannableText.length(),
|
||||
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;
|
||||
|
@ -27,6 +27,7 @@ import androidx.annotation.ColorInt;
|
||||
import androidx.annotation.IntDef;
|
||||
import androidx.annotation.Nullable;
|
||||
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.Log;
|
||||
import com.google.android.exoplayer2.util.Util;
|
||||
@ -90,12 +91,17 @@ import java.util.regex.Pattern;
|
||||
public final String name;
|
||||
@SsaAlignment public final int alignment;
|
||||
@Nullable @ColorInt public final Integer primaryColor;
|
||||
public final float fontSize;
|
||||
|
||||
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.alignment = alignment;
|
||||
this.primaryColor = primaryColor;
|
||||
this.fontSize = fontSize;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@ -114,7 +120,8 @@ import java.util.regex.Pattern;
|
||||
return new SsaStyle(
|
||||
styleValues[format.nameIndex].trim(),
|
||||
parseAlignment(styleValues[format.alignmentIndex].trim()),
|
||||
parseColor(styleValues[format.primaryColorIndex].trim()));
|
||||
parseColor(styleValues[format.primaryColorIndex].trim()),
|
||||
parseFontSize(styleValues[format.fontSizeIndex].trim()));
|
||||
} catch (RuntimeException e) {
|
||||
Log.w(TAG, "Skipping malformed 'Style:' line: '" + styleLine + "'", e);
|
||||
return null;
|
||||
@ -191,6 +198,15 @@ import java.util.regex.Pattern;
|
||||
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
|
||||
*
|
||||
@ -202,12 +218,15 @@ import java.util.regex.Pattern;
|
||||
public final int nameIndex;
|
||||
public final int alignmentIndex;
|
||||
public final int primaryColorIndex;
|
||||
public final int fontSizeIndex;
|
||||
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.alignmentIndex = alignmentIndex;
|
||||
this.primaryColorIndex = primaryColorIndex;
|
||||
this.fontSizeIndex = fontSizeIndex;
|
||||
this.length = length;
|
||||
}
|
||||
|
||||
@ -221,6 +240,7 @@ import java.util.regex.Pattern;
|
||||
int nameIndex = C.INDEX_UNSET;
|
||||
int alignmentIndex = C.INDEX_UNSET;
|
||||
int primaryColorIndex = C.INDEX_UNSET;
|
||||
int fontSizeIndex = C.INDEX_UNSET;
|
||||
String[] keys =
|
||||
TextUtils.split(styleFormatLine.substring(SsaDecoder.FORMAT_LINE_PREFIX.length()), ",");
|
||||
for (int i = 0; i < keys.length; i++) {
|
||||
@ -234,10 +254,13 @@ import java.util.regex.Pattern;
|
||||
case "primarycolour":
|
||||
primaryColorIndex = i;
|
||||
break;
|
||||
case "fontsize":
|
||||
fontSizeIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return nameIndex != C.INDEX_UNSET
|
||||
? new Format(nameIndex, alignmentIndex, primaryColorIndex, keys.length)
|
||||
? new Format(nameIndex, alignmentIndex, primaryColorIndex, fontSizeIndex, keys.length)
|
||||
: null;
|
||||
}
|
||||
}
|
||||
|
@ -47,7 +47,8 @@ public final class SsaDecoderTest {
|
||||
private static final String INVALID_TIMECODES = "media/ssa/invalid_timecodes";
|
||||
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 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
|
||||
public void decodeEmpty() throws IOException {
|
||||
@ -274,7 +275,7 @@ public final class SsaDecoderTest {
|
||||
@Test
|
||||
public void decodeColors() throws IOException {
|
||||
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);
|
||||
assertThat(subtitle.getEventTimeCount()).isEqualTo(14);
|
||||
// &H000000FF (AABBGGRR) -> #FFFF0000 (AARRGGBB)
|
||||
@ -319,6 +320,22 @@ public final class SsaDecoderTest {
|
||||
.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) {
|
||||
assertThat(subtitle.getEventTime(eventIndex)).isEqualTo(0);
|
||||
assertThat(subtitle.getCues(subtitle.getEventTime(eventIndex)).get(0).text.toString())
|
||||
|
@ -17,7 +17,6 @@ package com.google.android.exoplayer2.upstream.cache;
|
||||
|
||||
import static com.google.android.exoplayer2.testutil.CacheAsserts.assertCachedData;
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static java.lang.Math.min;
|
||||
import static org.junit.Assert.assertThrows;
|
||||
|
||||
import android.net.Uri;
|
||||
@ -36,65 +35,18 @@ import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Answers;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
/** Unit tests for {@link CacheWriter}. */
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
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 SimpleCache cache;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
mockCache.init();
|
||||
tempFolder =
|
||||
Util.createTempDirectory(ApplicationProvider.getApplicationContext(), "ExoPlayerTest");
|
||||
cache =
|
||||
|
@ -145,12 +145,11 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
|
||||
* Parses a udta atom.
|
||||
*
|
||||
* @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
|
||||
* any), and the metadata from the smta child atom as second value (if any).
|
||||
*/
|
||||
public static Pair<@NullableType Metadata, @NullableType Metadata> parseUdta(
|
||||
Atom.LeafAtom udtaAtom, boolean isQuickTime) {
|
||||
Atom.LeafAtom udtaAtom) {
|
||||
ParsableByteArray udtaData = udtaAtom.data;
|
||||
udtaData.setPosition(Atom.HEADER_SIZE);
|
||||
@Nullable Metadata metaMetadata = null;
|
||||
@ -159,8 +158,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
|
||||
int atomPosition = udtaData.getPosition();
|
||||
int atomSize = 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 && !isQuickTime) {
|
||||
if (atomType == Atom.TYPE_meta) {
|
||||
udtaData.setPosition(atomPosition);
|
||||
metaMetadata = parseUdtaMeta(udtaData, atomPosition + atomSize);
|
||||
} else if (atomType == Atom.TYPE_smta) {
|
||||
@ -227,6 +225,30 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
|
||||
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).
|
||||
*
|
||||
@ -677,7 +699,8 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
|
||||
|
||||
@Nullable
|
||||
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) {
|
||||
int atomPosition = meta.getPosition();
|
||||
int atomSize = meta.readInt();
|
||||
|
@ -470,7 +470,7 @@ public final class Mp4Extractor implements Extractor, SeekMap {
|
||||
@Nullable Atom.LeafAtom udta = moov.getLeafAtomOfType(Atom.TYPE_udta);
|
||||
if (udta != null) {
|
||||
Pair<@NullableType Metadata, @NullableType Metadata> udtaMetadata =
|
||||
AtomParsers.parseUdta(udta, isQuickTime);
|
||||
AtomParsers.parseUdta(udta);
|
||||
udtaMetaMetadata = udtaMetadata.first;
|
||||
smtaMetadata = udtaMetadata.second;
|
||||
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 {
|
||||
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);
|
||||
scratch.skipBytes(4);
|
||||
if (scratch.readInt() == Atom.TYPE_hdlr) {
|
||||
input.resetPeekPosition();
|
||||
} else {
|
||||
input.skipFully(4);
|
||||
}
|
||||
AtomParsers.maybeSkipRemainingMetaAtomHeaderBytes(scratch);
|
||||
input.skipFully(scratch.getPosition());
|
||||
input.resetPeekPosition();
|
||||
}
|
||||
|
||||
/** Processes an atom whose payload does not need to be parsed. */
|
||||
|
@ -913,10 +913,10 @@ public class PlayerControlView extends FrameLayout {
|
||||
timeline.getWindow(player.getCurrentWindowIndex(), window);
|
||||
boolean isSeekable = window.isSeekable;
|
||||
enableSeeking = isSeekable;
|
||||
enablePrevious = isSeekable || !window.isDynamic || player.hasPrevious();
|
||||
enablePrevious = isSeekable || !window.isLive() || player.hasPrevious();
|
||||
enableRewind = isSeekable && controlDispatcher.isRewindEnabled();
|
||||
enableFastForward = isSeekable && controlDispatcher.isFastForwardEnabled();
|
||||
enableNext = window.isDynamic || player.hasNext();
|
||||
enableNext = (window.isLive() && window.isDynamic) || player.hasNext();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1227,10 +1227,11 @@ public class PlayerNotificationManager {
|
||||
Timeline timeline = player.getCurrentTimeline();
|
||||
if (!timeline.isEmpty() && !player.isPlayingAd()) {
|
||||
timeline.getWindow(player.getCurrentWindowIndex(), window);
|
||||
enablePrevious = window.isSeekable || !window.isDynamic || player.hasPrevious();
|
||||
enableRewind = controlDispatcher.isRewindEnabled();
|
||||
enableFastForward = controlDispatcher.isFastForwardEnabled();
|
||||
enableNext = window.isDynamic || player.hasNext();
|
||||
boolean isSeekable = window.isSeekable;
|
||||
enablePrevious = isSeekable || !window.isLive() || player.hasPrevious();
|
||||
enableRewind = isSeekable && controlDispatcher.isRewindEnabled();
|
||||
enableFastForward = isSeekable && controlDispatcher.isFastForwardEnabled();
|
||||
enableNext = (window.isLive() && window.isDynamic) || player.hasNext();
|
||||
}
|
||||
|
||||
List<String> stringActions = new ArrayList<>();
|
||||
|
@ -1141,10 +1141,10 @@ public class StyledPlayerControlView extends FrameLayout {
|
||||
timeline.getWindow(player.getCurrentWindowIndex(), window);
|
||||
boolean isSeekable = window.isSeekable;
|
||||
enableSeeking = isSeekable;
|
||||
enablePrevious = isSeekable || !window.isDynamic || player.hasPrevious();
|
||||
enablePrevious = isSeekable || !window.isLive() || player.hasPrevious();
|
||||
enableRewind = isSeekable && controlDispatcher.isRewindEnabled();
|
||||
enableFastForward = isSeekable && controlDispatcher.isFastForwardEnabled();
|
||||
enableNext = window.isDynamic || player.hasNext();
|
||||
enableNext = (window.isLive() && window.isDynamic) || player.hasNext();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -22,7 +22,7 @@
|
||||
android:addStatesFromChildren="true"
|
||||
style="@style/ExoStyledControls.Button.Center">
|
||||
<!-- 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:layout_marginLeft="0dp"
|
||||
android:layout_marginRight="0dp"
|
||||
|
@ -22,7 +22,7 @@
|
||||
android:addStatesFromChildren="true"
|
||||
style="@style/ExoStyledControls.Button.Center">
|
||||
<!-- 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:layout_marginLeft="0dp"
|
||||
android:layout_marginRight="0dp"
|
||||
|
@ -15,13 +15,13 @@
|
||||
-->
|
||||
<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_height="match_parent"
|
||||
android:layout_gravity="center"
|
||||
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_height="@dimen/exo_styled_bottom_bar_height"
|
||||
android:layout_marginTop="@dimen/exo_styled_bottom_bar_margin_top"
|
||||
@ -29,7 +29,7 @@
|
||||
android:background="@color/exo_bottom_bar_background"
|
||||
android:layoutDirection="ltr">
|
||||
|
||||
<LinearLayout android:id="@+id/exo_time"
|
||||
<LinearLayout android:id="@id/exo_time"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingStart="@dimen/exo_styled_bottom_bar_time_padding"
|
||||
@ -39,58 +39,58 @@
|
||||
android:layout_gravity="center_vertical|start"
|
||||
android:layoutDirection="ltr">
|
||||
|
||||
<TextView android:id="@+id/exo_position"
|
||||
<TextView android:id="@id/exo_position"
|
||||
style="@style/ExoStyledControls.TimeText.Position"/>
|
||||
|
||||
<TextView
|
||||
style="@style/ExoStyledControls.TimeText.Separator"/>
|
||||
|
||||
<TextView android:id="@+id/exo_duration"
|
||||
<TextView android:id="@id/exo_duration"
|
||||
style="@style/ExoStyledControls.TimeText.Duration"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout android:id="@+id/exo_basic_controls"
|
||||
<LinearLayout android:id="@id/exo_basic_controls"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical|end"
|
||||
android:layoutDirection="ltr">
|
||||
|
||||
<ImageButton android:id="@+id/exo_vr"
|
||||
<ImageButton android:id="@id/exo_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"/>
|
||||
|
||||
<ImageButton android:id="@+id/exo_repeat_toggle"
|
||||
<ImageButton android:id="@id/exo_repeat_toggle"
|
||||
style="@style/ExoStyledControls.Button.Bottom.RepeatToggle"/>
|
||||
|
||||
<ImageButton android:id="@+id/exo_subtitle"
|
||||
<ImageButton android:id="@id/exo_subtitle"
|
||||
style="@style/ExoStyledControls.Button.Bottom.CC"/>
|
||||
|
||||
<ImageButton android:id="@+id/exo_settings"
|
||||
<ImageButton android:id="@id/exo_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"/>
|
||||
|
||||
<ImageButton android:id="@+id/exo_overflow_show"
|
||||
<ImageButton android:id="@id/exo_overflow_show"
|
||||
style="@style/ExoStyledControls.Button.Bottom.OverflowShow"/>
|
||||
|
||||
</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_height="wrap_content"
|
||||
android:layout_gravity="center_vertical|end"
|
||||
android:visibility="invisible">
|
||||
|
||||
<LinearLayout android:id="@+id/exo_extra_controls"
|
||||
<LinearLayout android:id="@id/exo_extra_controls"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layoutDirection="ltr">
|
||||
|
||||
<ImageButton android:id="@+id/exo_overflow_hide"
|
||||
<ImageButton android:id="@id/exo_overflow_hide"
|
||||
style="@style/ExoStyledControls.Button.Bottom.OverflowHide"/>
|
||||
|
||||
</LinearLayout>
|
||||
@ -99,13 +99,13 @@
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
<View android:id="@+id/exo_progress_placeholder"
|
||||
<View android:id="@id/exo_progress_placeholder"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/exo_styled_progress_layout_height"
|
||||
android:layout_gravity="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_height="wrap_content"
|
||||
android:layout_gravity="bottom|end"
|
||||
@ -114,13 +114,13 @@
|
||||
android:gravity="center_vertical"
|
||||
android:layoutDirection="ltr">
|
||||
|
||||
<ImageButton android:id="@+id/exo_minimal_fullscreen"
|
||||
<ImageButton android:id="@id/exo_minimal_fullscreen"
|
||||
style="@style/ExoStyledControls.Button.Bottom.FullScreen"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/exo_center_controls"
|
||||
android:id="@id/exo_center_controls"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
@ -128,18 +128,17 @@
|
||||
android:gravity="center"
|
||||
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"/>
|
||||
|
||||
<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"/>
|
||||
|
||||
<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"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
@ -58,4 +58,5 @@
|
||||
android:textDirection="locale"
|
||||
android:textSize="@dimen/exo_settings_sub_text_size"/>
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
@ -43,4 +43,5 @@
|
||||
android:textColor="@color/exo_white"
|
||||
android:textDirection="locale"
|
||||
android:textSize="@dimen/exo_settings_main_text_size"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
@ -27,7 +27,9 @@
|
||||
<item name="exo_pause" type="id"/>
|
||||
<item name="exo_play_pause" 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_with_amount" type="id"/>
|
||||
<item name="exo_prev" type="id"/>
|
||||
<item name="exo_next" type="id"/>
|
||||
<item name="exo_shuffle" type="id"/>
|
||||
@ -41,4 +43,17 @@
|
||||
<item name="exo_vr" type="id"/>
|
||||
<item name="exo_subtitle" 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>
|
||||
|
@ -11,8 +11,10 @@
|
||||
// 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.
|
||||
|
||||
if (project.ext.has("exoplayerPublishEnabled")
|
||||
&& project.ext.exoplayerPublishEnabled) {
|
||||
// For publishing to Bintray.
|
||||
apply plugin: 'bintray-release'
|
||||
publish {
|
||||
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() {
|
||||
@ -66,3 +109,8 @@ static void addLicense(File pom) {
|
||||
printer.print(xml)
|
||||
writer.close()
|
||||
}
|
||||
|
||||
task androidSourcesJar(type: Jar) {
|
||||
archiveClassifier.set('sources')
|
||||
from android.sourceSets.main.java.srcDirs
|
||||
}
|
||||
|
18
testdata/src/test/assets/media/ssa/style_font_size
vendored
Normal file
18
testdata/src/test/assets/media/ssa/style_font_size
vendored
Normal 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.
|
@ -147,7 +147,6 @@ public class HttpDataSourceTestEnv extends ExternalResource {
|
||||
.setName(name)
|
||||
.setUri(Uri.parse(server.url(resource.getPath()).toString()))
|
||||
.setExpectedBytes(resource.getData())
|
||||
.setEndOfInputExpected(!resource.resolvesToUnknownLength())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
@ -33,6 +33,7 @@ import com.google.common.collect.Maps;
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
@ -277,21 +278,19 @@ public class WebServerDispatcher extends Dispatcher {
|
||||
if (!resource.supportsRangeRequests() || rangeHeader == null) {
|
||||
switch (preferredContentCoding) {
|
||||
case "gzip":
|
||||
setResponseBody(
|
||||
response, Util.gzip(resourceData), /* chunked= */ resource.resolvesToUnknownLength);
|
||||
response
|
||||
.setBody(new Buffer().write(Util.gzip(resourceData)))
|
||||
.setHeader("Content-Encoding", "gzip");
|
||||
break;
|
||||
case "identity":
|
||||
setResponseBody(response, resourceData, /* chunked= */ resource.resolvesToUnknownLength);
|
||||
response
|
||||
.setBody(new Buffer().write(resourceData))
|
||||
.setHeader("Content-Encoding", "identity");
|
||||
break;
|
||||
default:
|
||||
throw new IllegalStateException("Unexpected content coding: " + preferredContentCoding);
|
||||
}
|
||||
if (resource.resolvesToUnknownLength()) {
|
||||
response.setHeader("Content-Length", "");
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
@ -328,11 +327,11 @@ public class WebServerDispatcher extends Dispatcher {
|
||||
+ "-"
|
||||
+ (resourceData.length - 1)
|
||||
+ "/"
|
||||
+ (resource.resolvesToUnknownLength() ? "*" : resourceData.length))
|
||||
.setBody(new Buffer().write(resourceData, start, resourceData.length - start));
|
||||
if (resource.resolvesToUnknownLength()) {
|
||||
response.setHeader("Content-Length", "");
|
||||
}
|
||||
+ (resource.resolvesToUnknownLength() ? "*" : resourceData.length));
|
||||
setResponseBody(
|
||||
response,
|
||||
Arrays.copyOfRange(resourceData, start, resourceData.length),
|
||||
/* chunked= */ resource.resolvesToUnknownLength);
|
||||
return response;
|
||||
}
|
||||
|
||||
@ -345,7 +344,7 @@ public class WebServerDispatcher extends Dispatcher {
|
||||
}
|
||||
|
||||
int end = min(range.second + 1, resourceData.length);
|
||||
return response
|
||||
response
|
||||
.setResponseCode(206)
|
||||
.setHeader(
|
||||
"Content-Range",
|
||||
@ -354,8 +353,26 @@ public class WebServerDispatcher extends Dispatcher {
|
||||
+ "-"
|
||||
+ (end - 1)
|
||||
+ "/"
|
||||
+ (resource.resolvesToUnknownLength() ? "*" : resourceData.length))
|
||||
.setBody(new Buffer().write(resourceData, range.first, end - range.first));
|
||||
+ (resource.resolvesToUnknownLength() ? "*" : resourceData.length));
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -257,7 +257,7 @@ public class WebServerDispatcherTest {
|
||||
try (Response response = client.newCall(request).execute()) {
|
||||
assertThat(response.code()).isEqualTo(200);
|
||||
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.body().contentLength()).isEqualTo(-1);
|
||||
|
||||
@ -298,7 +298,7 @@ public class WebServerDispatcherTest {
|
||||
try (Response response = client.newCall(request).execute()) {
|
||||
assertThat(response.code()).isEqualTo(206);
|
||||
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.body().contentLength()).isEqualTo(-1);
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user