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
cmake-build-debug
dist
jacoco.exec
tmp
# External native builds

View File

@ -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:

View File

@ -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.

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.
This build configuration has been tested on NDK r20.
This build configuration has been tested on NDK r21.
```
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.
This build configuration has been tested on NDK r20.
This build configuration has been tested on NDK r21.
```
NDK_PATH="<path to Android NDK>"

View File

@ -341,13 +341,27 @@ 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) {
@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();
}
}
}
/** Deactivates playback. */
public void deactivate() {
@ -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;

View File

@ -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;

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.
This build configuration has been tested on NDK r20.
This build configuration has been tested on NDK r21.
```
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.
This build configuration has been tested on NDK r20.
This build configuration has been tested on NDK r21.
```
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". */
// 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.

View File

@ -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;

View File

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

View File

@ -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;
}

View File

@ -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> {

View File

@ -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;

View File

@ -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;
}
}

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_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())

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.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 =

View File

@ -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();

View File

@ -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) {
AtomParsers.maybeSkipRemainingMetaAtomHeaderBytes(scratch);
input.skipFully(scratch.getPosition());
input.resetPeekPosition();
} else {
input.skipFully(4);
}
}
/** 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);
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();
}
}

View File

@ -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<>();

View File

@ -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();
}
}

View File

@ -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"

View File

@ -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"

View File

@ -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>

View File

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

View File

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

View File

@ -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>

View File

@ -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
}

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)
.setUri(Uri.parse(server.url(resource.getPath()).toString()))
.setExpectedBytes(resource.getData())
.setEndOfInputExpected(!resource.resolvesToUnknownLength())
.build();
}
}

View File

@ -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));
}
}
/**

View File

@ -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);