diff --git a/README.md b/README.md
index 13dfaddab3a..37967dd527a 100644
--- a/README.md
+++ b/README.md
@@ -28,13 +28,13 @@ repository and depend on the modules locally.
### From JCenter ###
The easiest way to get started using ExoPlayer is to add it as a gradle
-dependency. You need to make sure you have the JCenter and Google repositories
+dependency. You need to make sure you have the Google and JCenter repositories
included in the `build.gradle` file in the root of your project:
```gradle
repositories {
- jcenter()
google()
+ jcenter()
}
```
@@ -45,10 +45,20 @@ following will add a dependency to the full library:
implementation 'com.google.android.exoplayer:exoplayer:2.X.X'
```
-where `2.X.X` is your preferred version. Alternatively, you can depend on only
-the library modules that you actually need. For example the following will add
-dependencies on the Core, DASH and UI library modules, as might be required for
-an app that plays DASH content:
+where `2.X.X` is your preferred version. If not enabled already, you also need
+to turn on Java 8 support in all `build.gradle` files depending on ExoPlayer, by
+adding the following to the `android` section:
+
+```gradle
+compileOptions {
+ targetCompatibility JavaVersion.VERSION_1_8
+}
+```
+
+As an alternative to the full library, you can depend on only the library
+modules that you actually need. For example the following will add dependencies
+on the Core, DASH and UI library modules, as might be required for an app that
+plays DASH content:
```gradle
implementation 'com.google.android.exoplayer:exoplayer-core:2.X.X'
diff --git a/RELEASENOTES.md b/RELEASENOTES.md
index 12b4bb48727..9eae825c61f 100644
--- a/RELEASENOTES.md
+++ b/RELEASENOTES.md
@@ -1,10 +1,61 @@
# Release notes #
+### 2.9.1 ###
+
+* Add convenience methods `Player.next`, `Player.previous`, `Player.hasNext`
+ and `Player.hasPrevious`
+ ([#4863](https://github.com/google/ExoPlayer/issues/4863)).
+* Improve initial bandwidth meter estimates using the current country and
+ network type.
+* IMA extension:
+ * For preroll to live stream transitions, project forward the loading position
+ to avoid being behind the live window.
+ * Let apps specify whether to focus the skip button on ATV
+ ([#5019](https://github.com/google/ExoPlayer/issues/5019)).
+* MP3:
+ * Support seeking based on MLLT metadata
+ ([#3241](https://github.com/google/ExoPlayer/issues/3241)).
+ * Fix handling of streams with appended data
+ ([#4954](https://github.com/google/ExoPlayer/issues/4954)).
+* DASH: Parse ProgramInformation element if present in the manifest.
+* HLS:
+ * Add constructor to `DefaultHlsExtractorFactory` for adding TS payload
+ reader factory flags
+ * Fix bug in segment sniffing
+ ([#5039](https://github.com/google/ExoPlayer/issues/5039)).
+ ([#4861](https://github.com/google/ExoPlayer/issues/4861)).
+* SubRip: Add support for alignment tags, and remove tags from the displayed
+ captions ([#4306](https://github.com/google/ExoPlayer/issues/4306)).
+* Fix issue with blind seeking to windows with non-zero offset in a
+ `ConcatenatingMediaSource`
+ ([#4873](https://github.com/google/ExoPlayer/issues/4873)).
+* Fix logic for enabling next and previous actions in `TimelineQueueNavigator`
+ ([#5065](https://github.com/google/ExoPlayer/issues/5065)).
+* Fix issue where audio focus handling could not be disabled after enabling it
+ ([#5055](https://github.com/google/ExoPlayer/issues/5055)).
+* Fix issue where subtitles were positioned incorrectly if `SubtitleView` had a
+ non-zero position offset to its parent
+ ([#4788](https://github.com/google/ExoPlayer/issues/4788)).
+* Fix issue where the buffered position was not updated correctly when
+ transitioning between periods
+ ([#4899](https://github.com/google/ExoPlayer/issues/4899)).
+* Fix issue where a `NullPointerException` is thrown when removing an unprepared
+ media source from a `ConcatenatingMediaSource` with the `useLazyPreparation`
+ option enabled ([#4986](https://github.com/google/ExoPlayer/issues/4986)).
+* Work around an issue where a non-empty end-of-stream audio buffer would be
+ output with timestamp zero, causing the player position to jump backwards
+ ([#5045](https://github.com/google/ExoPlayer/issues/5045)).
+* Suppress a spurious assertion failure on some Samsung devices
+ ([#4532](https://github.com/google/ExoPlayer/issues/4532)).
+* Suppress spurious "references unknown class member" shrinking warning
+ ([#4890](https://github.com/google/ExoPlayer/issues/4890)).
+* Swap recommended order for google() and jcenter() in gradle config
+ ([#4997](https://github.com/google/ExoPlayer/issues/4997)).
+
### 2.9.0 ###
-* Turn on Java 8 compiler support for the ExoPlayer library. Apps that depend
- on ExoPlayer via its source code rather than an AAR may need to add
- `compileOptions { targetCompatibility JavaVersion.VERSION_1_8 }` to their
+* Turn on Java 8 compiler support for the ExoPlayer library. Apps may need to
+ add `compileOptions { targetCompatibility JavaVersion.VERSION_1_8 }` to their
gradle settings to ensure bytecode compatibility.
* Set `compileSdkVersion` and `targetSdkVersion` to 28.
* Support for automatic audio focus handling via
@@ -326,18 +377,18 @@
begins, and poll the audio timestamp less frequently once it starts
advancing ([#3841](https://github.com/google/ExoPlayer/issues/3841)).
* Add an option to skip silent audio in `PlaybackParameters`
- ((#2635)[https://github.com/google/ExoPlayer/issues/2635]).
+ ([#2635](https://github.com/google/ExoPlayer/issues/2635)).
* Fix an issue where playback of TrueHD streams would get stuck after seeking
due to not finding a syncframe
- ((#3845)[https://github.com/google/ExoPlayer/issues/3845]).
+ ([#3845](https://github.com/google/ExoPlayer/issues/3845)).
* Fix an issue with eac3-joc playback where a codec would fail to configure
- ((#4165)[https://github.com/google/ExoPlayer/issues/4165]).
+ ([#4165](https://github.com/google/ExoPlayer/issues/4165)).
* Handle non-empty end-of-stream buffers, to fix gapless playback of streams
with encoder padding when the decoder returns a non-empty final buffer.
* Allow trimming more than one sample when applying an elst audio edit via
gapless playback info.
* Allow overriding skipping/scaling with custom `AudioProcessor`s
- ((#3142)[https://github.com/google/ExoPlayer/issues/3142]).
+ ([#3142](https://github.com/google/ExoPlayer/issues/3142)).
* Caching:
* Add release method to the `Cache` interface, and prevent multiple instances
of `SimpleCache` using the same folder at the same time.
diff --git a/build.gradle b/build.gradle
index a013f4fb840..96eade1aa3d 100644
--- a/build.gradle
+++ b/build.gradle
@@ -13,8 +13,8 @@
// limitations under the License.
buildscript {
repositories {
- jcenter()
google()
+ jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.1.4'
@@ -32,8 +32,8 @@ buildscript {
}
allprojects {
repositories {
- jcenter()
google()
+ jcenter()
}
project.ext {
exoplayerPublishEnabled = true
diff --git a/constants.gradle b/constants.gradle
index 4ebd005c251..72f32062917 100644
--- a/constants.gradle
+++ b/constants.gradle
@@ -13,8 +13,8 @@
// limitations under the License.
project.ext {
// ExoPlayer version and version code.
- releaseVersion = '2.9.0'
- releaseVersionCode = 2009000
+ releaseVersion = '2.9.1'
+ releaseVersionCode = 2009001
// Important: ExoPlayer specifies a minSdkVersion of 14 because various
// components provided by the library may be of use on older devices.
// However, please note that the core media playback functionality provided
diff --git a/demos/main/src/main/res/drawable-xxhdpi/ic_download.png b/demos/main/src/main/res/drawable-xxhdpi/ic_download.png
index f02715177ad..4e04a30198d 100644
Binary files a/demos/main/src/main/res/drawable-xxhdpi/ic_download.png and b/demos/main/src/main/res/drawable-xxhdpi/ic_download.png differ
diff --git a/demos/main/src/main/res/drawable-xxxhdpi/ic_download.png b/demos/main/src/main/res/drawable-xxxhdpi/ic_download.png
index 6602791545f..f9bfb5edba7 100644
Binary files a/demos/main/src/main/res/drawable-xxxhdpi/ic_download.png and b/demos/main/src/main/res/drawable-xxxhdpi/ic_download.png differ
diff --git a/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/CastPlayer.java b/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/CastPlayer.java
index bd910010655..578a609ff39 100644
--- a/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/CastPlayer.java
+++ b/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/CastPlayer.java
@@ -18,6 +18,7 @@
import android.os.Looper;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
+import com.google.android.exoplayer2.BasePlayer;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ExoPlaybackException;
import com.google.android.exoplayer2.IllegalSeekPositionException;
@@ -32,7 +33,6 @@
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.Log;
import com.google.android.exoplayer2.util.MimeTypes;
-import com.google.android.exoplayer2.util.Util;
import com.google.android.gms.cast.CastStatusCodes;
import com.google.android.gms.cast.MediaInfo;
import com.google.android.gms.cast.MediaQueueItem;
@@ -64,7 +64,7 @@
*
*
Methods should be called on the application's main thread.
*/
-public final class CastPlayer implements Player {
+public final class CastPlayer extends BasePlayer {
/**
* Listener of changes in the cast session availability.
@@ -97,7 +97,6 @@ public interface SessionAvailabilityListener {
private final CastContext castContext;
// TODO: Allow custom implementations of CastTimelineTracker.
private final CastTimelineTracker timelineTracker;
- private final Timeline.Window window;
private final Timeline.Period period;
private RemoteMediaClient remoteMediaClient;
@@ -130,7 +129,6 @@ public interface SessionAvailabilityListener {
public CastPlayer(CastContext castContext) {
this.castContext = castContext;
timelineTracker = new CastTimelineTracker();
- window = new Timeline.Window();
period = new Timeline.Period();
statusListener = new StatusListener();
seekResultCallback = new SeekResultCallback();
@@ -350,21 +348,6 @@ public boolean getPlayWhenReady() {
return playWhenReady;
}
- @Override
- public void seekToDefaultPosition() {
- seekTo(0);
- }
-
- @Override
- public void seekToDefaultPosition(int windowIndex) {
- seekTo(windowIndex, 0);
- }
-
- @Override
- public void seekTo(long positionMs) {
- seekTo(getCurrentWindowIndex(), positionMs);
- }
-
@Override
public void seekTo(int windowIndex, long positionMs) {
MediaStatus mediaStatus = getMediaStatus();
@@ -404,11 +387,6 @@ public PlaybackParameters getPlaybackParameters() {
return PlaybackParameters.DEFAULT;
}
- @Override
- public void stop() {
- stop(/* reset= */ false);
- }
-
@Override
public void stop(boolean reset) {
playbackState = STATE_IDLE;
@@ -498,32 +476,11 @@ public int getCurrentWindowIndex() {
return pendingSeekWindowIndex != C.INDEX_UNSET ? pendingSeekWindowIndex : currentWindowIndex;
}
- @Override
- public int getNextWindowIndex() {
- return currentTimeline.isEmpty() ? C.INDEX_UNSET
- : currentTimeline.getNextWindowIndex(getCurrentWindowIndex(), repeatMode, false);
- }
-
- @Override
- public int getPreviousWindowIndex() {
- return currentTimeline.isEmpty() ? C.INDEX_UNSET
- : currentTimeline.getPreviousWindowIndex(getCurrentWindowIndex(), repeatMode, false);
- }
-
- @Override
- public @Nullable Object getCurrentTag() {
- int windowIndex = getCurrentWindowIndex();
- return windowIndex >= currentTimeline.getWindowCount()
- ? null
- : currentTimeline.getWindow(windowIndex, window, /* setTag= */ true).tag;
- }
-
// TODO: Fill the cast timeline information with ProgressListener's duration updates.
// See [Internal: b/65152553].
@Override
public long getDuration() {
- return currentTimeline.isEmpty() ? C.TIME_UNSET
- : currentTimeline.getWindow(getCurrentWindowIndex(), window).getDurationMs();
+ return getContentDuration();
}
@Override
@@ -540,15 +497,6 @@ public long getBufferedPosition() {
return getCurrentPosition();
}
- @Override
- public int getBufferedPercentage() {
- long position = getBufferedPosition();
- long duration = getDuration();
- return position == C.TIME_UNSET || duration == C.TIME_UNSET
- ? 0
- : duration == 0 ? 100 : Util.constrainValue((int) ((position * 100) / duration), 0, 100);
- }
-
@Override
public long getTotalBufferedDuration() {
long bufferedPosition = getBufferedPosition();
@@ -558,18 +506,6 @@ public long getTotalBufferedDuration() {
: bufferedPosition - currentPosition;
}
- @Override
- public boolean isCurrentWindowDynamic() {
- return !currentTimeline.isEmpty()
- && currentTimeline.getWindow(getCurrentWindowIndex(), window).isDynamic;
- }
-
- @Override
- public boolean isCurrentWindowSeekable() {
- return !currentTimeline.isEmpty()
- && currentTimeline.getWindow(getCurrentWindowIndex(), window).isSeekable;
- }
-
@Override
public boolean isPlayingAd() {
return false;
@@ -585,11 +521,6 @@ public int getCurrentAdIndexInAdGroup() {
return C.INDEX_UNSET;
}
- @Override
- public long getContentDuration() {
- return getDuration();
- }
-
@Override
public boolean isLoading() {
return false;
diff --git a/extensions/cronet/src/main/java/com/google/android/exoplayer2/ext/cronet/CronetDataSource.java b/extensions/cronet/src/main/java/com/google/android/exoplayer2/ext/cronet/CronetDataSource.java
index 9525983491b..af854011005 100644
--- a/extensions/cronet/src/main/java/com/google/android/exoplayer2/ext/cronet/CronetDataSource.java
+++ b/extensions/cronet/src/main/java/com/google/android/exoplayer2/ext/cronet/CronetDataSource.java
@@ -326,8 +326,12 @@ public long open(DataSpec dataSpec) throws HttpDataSourceException {
// Check for a valid response code.
int responseCode = responseInfo.getHttpStatusCode();
if (responseCode < 200 || responseCode > 299) {
- InvalidResponseCodeException exception = new InvalidResponseCodeException(responseCode,
- responseInfo.getAllHeaders(), currentDataSpec);
+ InvalidResponseCodeException exception =
+ new InvalidResponseCodeException(
+ responseCode,
+ responseInfo.getHttpStatusText(),
+ responseInfo.getAllHeaders(),
+ currentDataSpec);
if (responseCode == 416) {
exception.initCause(new DataSourceException(DataSourceException.POSITION_OUT_OF_RANGE));
}
@@ -611,7 +615,8 @@ public synchronized void onRedirectReceived(
// The industry standard is to disregard POST redirects when the status code is 307 or 308.
if (responseCode == 307 || responseCode == 308) {
exception =
- new InvalidResponseCodeException(responseCode, info.getAllHeaders(), currentDataSpec);
+ new InvalidResponseCodeException(
+ responseCode, info.getHttpStatusText(), info.getAllHeaders(), currentDataSpec);
operation.open();
return;
}
diff --git a/extensions/cronet/src/main/java/com/google/android/exoplayer2/ext/cronet/CronetEngineWrapper.java b/extensions/cronet/src/main/java/com/google/android/exoplayer2/ext/cronet/CronetEngineWrapper.java
index dd39ea2822b..829b53f863f 100644
--- a/extensions/cronet/src/main/java/com/google/android/exoplayer2/ext/cronet/CronetEngineWrapper.java
+++ b/extensions/cronet/src/main/java/com/google/android/exoplayer2/ext/cronet/CronetEngineWrapper.java
@@ -19,6 +19,7 @@
import android.support.annotation.IntDef;
import com.google.android.exoplayer2.util.Log;
import com.google.android.exoplayer2.util.Util;
+import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.reflect.Field;
@@ -43,6 +44,7 @@ public final class CronetEngineWrapper {
* Source of {@link CronetEngine}. One of {@link #SOURCE_NATIVE}, {@link #SOURCE_GMS}, {@link
* #SOURCE_UNKNOWN}, {@link #SOURCE_USER_PROVIDED} or {@link #SOURCE_UNAVAILABLE}.
*/
+ @Documented
@Retention(RetentionPolicy.SOURCE)
@IntDef({SOURCE_NATIVE, SOURCE_GMS, SOURCE_UNKNOWN, SOURCE_USER_PROVIDED, SOURCE_UNAVAILABLE})
public @interface CronetEngineSource {}
diff --git a/extensions/flac/README.md b/extensions/flac/README.md
index fda5f0085df..54701eea1db 100644
--- a/extensions/flac/README.md
+++ b/extensions/flac/README.md
@@ -28,18 +28,19 @@ EXOPLAYER_ROOT="$(pwd)"
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][] (version <= 17c) and set its location in an
+ environment variable:
```
NDK_PATH=""
```
-* Download and extract flac-1.3.1 as "${FLAC_EXT_PATH}/jni/flac" folder:
+* Download and extract flac-1.3.2 as "${FLAC_EXT_PATH}/jni/flac" folder:
```
cd "${FLAC_EXT_PATH}/jni" && \
-curl https://ftp.osuosl.org/pub/xiph/releases/flac/flac-1.3.1.tar.xz | tar xJ && \
-mv flac-1.3.1 flac
+curl https://ftp.osuosl.org/pub/xiph/releases/flac/flac-1.3.2.tar.xz | tar xJ && \
+mv flac-1.3.2 flac
```
* Build the JNI native libraries from the command line:
diff --git a/extensions/flac/src/main/java/com/google/android/exoplayer2/ext/flac/FlacExtractor.java b/extensions/flac/src/main/java/com/google/android/exoplayer2/ext/flac/FlacExtractor.java
index a1fbcc69d61..8f5dcef16b7 100644
--- a/extensions/flac/src/main/java/com/google/android/exoplayer2/ext/flac/FlacExtractor.java
+++ b/extensions/flac/src/main/java/com/google/android/exoplayer2/ext/flac/FlacExtractor.java
@@ -37,6 +37,7 @@
import com.google.android.exoplayer2.util.MimeTypes;
import com.google.android.exoplayer2.util.ParsableByteArray;
import java.io.IOException;
+import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.nio.ByteBuffer;
@@ -54,6 +55,7 @@ public final class FlacExtractor implements Extractor {
* Flags controlling the behavior of the extractor. Possible flag value is {@link
* #FLAG_DISABLE_ID3_METADATA}.
*/
+ @Documented
@Retention(RetentionPolicy.SOURCE)
@IntDef(
flag = true,
diff --git a/extensions/flac/src/main/jni/Android.mk b/extensions/flac/src/main/jni/Android.mk
index ff54c1b3c0e..69520a16e58 100644
--- a/extensions/flac/src/main/jni/Android.mk
+++ b/extensions/flac/src/main/jni/Android.mk
@@ -30,9 +30,9 @@ LOCAL_C_INCLUDES := \
$(LOCAL_PATH)/flac/src/libFLAC/include
LOCAL_SRC_FILES := $(FLAC_SOURCES)
-LOCAL_CFLAGS += '-DVERSION="1.3.1"' -DFLAC__NO_MD5 -DFLAC__INTEGER_ONLY_LIBRARY -DFLAC__NO_ASM
+LOCAL_CFLAGS += '-DPACKAGE_VERSION="1.3.2"' -DFLAC__NO_MD5 -DFLAC__INTEGER_ONLY_LIBRARY
LOCAL_CFLAGS += -D_REENTRANT -DPIC -DU_COMMON_IMPLEMENTATION -fPIC -DHAVE_SYS_PARAM_H
-LOCAL_CFLAGS += -O3 -funroll-loops -finline-functions
+LOCAL_CFLAGS += -O3 -funroll-loops -finline-functions -DFLAC__NO_ASM '-DFLAC__HAS_OGG=0'
LOCAL_LDLIBS := -llog -lz -lm
include $(BUILD_SHARED_LIBRARY)
diff --git a/extensions/flac/src/main/jni/Application.mk b/extensions/flac/src/main/jni/Application.mk
index 59bf5f8f870..eba20352f45 100644
--- a/extensions/flac/src/main/jni/Application.mk
+++ b/extensions/flac/src/main/jni/Application.mk
@@ -17,4 +17,4 @@
APP_OPTIM := release
APP_STL := gnustl_static
APP_CPPFLAGS := -frtti
-APP_PLATFORM := android-9
+APP_PLATFORM := android-14
diff --git a/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java b/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java
index 224f5aa6eef..6ca3bfd8817 100644
--- a/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java
+++ b/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java
@@ -40,6 +40,7 @@
import com.google.ads.interactivemedia.v3.api.CompanionAdSlot;
import com.google.ads.interactivemedia.v3.api.ImaSdkFactory;
import com.google.ads.interactivemedia.v3.api.ImaSdkSettings;
+import com.google.ads.interactivemedia.v3.api.UiElement;
import com.google.ads.interactivemedia.v3.api.player.ContentProgressProvider;
import com.google.ads.interactivemedia.v3.api.player.VideoAdPlayer;
import com.google.ads.interactivemedia.v3.api.player.VideoProgressUpdate;
@@ -60,14 +61,17 @@
import com.google.android.exoplayer2.util.MimeTypes;
import com.google.android.exoplayer2.util.Util;
import java.io.IOException;
+import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
+import java.util.HashSet;
import java.util.List;
import java.util.Map;
+import java.util.Set;
/** Loads ads using the IMA SDK. All methods are called on the main thread. */
public final class ImaAdsLoader
@@ -90,8 +94,11 @@ public static final class Builder {
private @Nullable ImaSdkSettings imaSdkSettings;
private @Nullable AdEventListener adEventListener;
+ private @Nullable Set adUiElements;
private int vastLoadTimeoutMs;
private int mediaLoadTimeoutMs;
+ private int mediaBitrate;
+ private boolean focusSkipButtonWhenAvailable;
private ImaFactory imaFactory;
/**
@@ -103,6 +110,8 @@ public Builder(Context context) {
this.context = Assertions.checkNotNull(context);
vastLoadTimeoutMs = TIMEOUT_UNSET;
mediaLoadTimeoutMs = TIMEOUT_UNSET;
+ mediaBitrate = BITRATE_UNSET;
+ focusSkipButtonWhenAvailable = true;
imaFactory = new DefaultImaFactory();
}
@@ -132,6 +141,18 @@ public Builder setAdEventListener(AdEventListener adEventListener) {
return this;
}
+ /**
+ * Sets the ad UI elements to be rendered by the IMA SDK.
+ *
+ * @param adUiElements The ad UI elements to be rendered by the IMA SDK.
+ * @return This builder, for convenience.
+ * @see AdsRenderingSettings#setUiElements(Set)
+ */
+ public Builder setAdUiElements(Set adUiElements) {
+ this.adUiElements = new HashSet<>(Assertions.checkNotNull(adUiElements));
+ return this;
+ }
+
/**
* Sets the VAST load timeout, in milliseconds.
*
@@ -140,7 +161,7 @@ public Builder setAdEventListener(AdEventListener adEventListener) {
* @see AdsRequest#setVastLoadTimeout(float)
*/
public Builder setVastLoadTimeoutMs(int vastLoadTimeoutMs) {
- Assertions.checkArgument(vastLoadTimeoutMs >= 0);
+ Assertions.checkArgument(vastLoadTimeoutMs > 0);
this.vastLoadTimeoutMs = vastLoadTimeoutMs;
return this;
}
@@ -153,11 +174,38 @@ public Builder setVastLoadTimeoutMs(int vastLoadTimeoutMs) {
* @see AdsRenderingSettings#setLoadVideoTimeout(int)
*/
public Builder setMediaLoadTimeoutMs(int mediaLoadTimeoutMs) {
- Assertions.checkArgument(mediaLoadTimeoutMs >= 0);
+ Assertions.checkArgument(mediaLoadTimeoutMs > 0);
this.mediaLoadTimeoutMs = mediaLoadTimeoutMs;
return this;
}
+ /**
+ * Sets the media maximum recommended bitrate for ads, in bps.
+ *
+ * @param bitrate The media maximum recommended bitrate for ads, in bps.
+ * @return This builder, for convenience.
+ * @see AdsRenderingSettings#setBitrateKbps(int)
+ */
+ public Builder setMaxMediaBitrate(int bitrate) {
+ Assertions.checkArgument(bitrate > 0);
+ this.mediaBitrate = bitrate;
+ return this;
+ }
+
+ /**
+ * Sets whether to focus the skip button (when available) on Android TV devices. The default
+ * setting is {@code true}.
+ *
+ * @param focusSkipButtonWhenAvailable Whether to focus the skip button (when available) on
+ * Android TV devices.
+ * @return This builder, for convenience.
+ * @see AdsRenderingSettings#setFocusSkipButtonWhenAvailable(boolean)
+ */
+ public Builder setFocusSkipButtonWhenAvailable(boolean focusSkipButtonWhenAvailable) {
+ this.focusSkipButtonWhenAvailable = focusSkipButtonWhenAvailable;
+ return this;
+ }
+
// @VisibleForTesting
/* package */ Builder setImaFactory(ImaFactory imaFactory) {
this.imaFactory = Assertions.checkNotNull(imaFactory);
@@ -180,6 +228,9 @@ public ImaAdsLoader buildForAdTag(Uri adTagUri) {
null,
vastLoadTimeoutMs,
mediaLoadTimeoutMs,
+ mediaBitrate,
+ focusSkipButtonWhenAvailable,
+ adUiElements,
adEventListener,
imaFactory);
}
@@ -199,6 +250,9 @@ public ImaAdsLoader buildForAdsResponse(String adsResponse) {
adsResponse,
vastLoadTimeoutMs,
mediaLoadTimeoutMs,
+ mediaBitrate,
+ focusSkipButtonWhenAvailable,
+ adUiElements,
adEventListener,
imaFactory);
}
@@ -228,8 +282,10 @@ public ImaAdsLoader buildForAdsResponse(String adsResponse) {
private static final long MAXIMUM_PRELOAD_DURATION_MS = 8000;
private static final int TIMEOUT_UNSET = -1;
+ private static final int BITRATE_UNSET = -1;
/** The state of ad playback. */
+ @Documented
@Retention(RetentionPolicy.SOURCE)
@IntDef({IMA_AD_STATE_NONE, IMA_AD_STATE_PLAYING, IMA_AD_STATE_PAUSED})
private @interface ImaAdState {}
@@ -250,6 +306,9 @@ public ImaAdsLoader buildForAdsResponse(String adsResponse) {
private final @Nullable String adsResponse;
private final int vastLoadTimeoutMs;
private final int mediaLoadTimeoutMs;
+ private final boolean focusSkipButtonWhenAvailable;
+ private final int mediaBitrate;
+ private final @Nullable Set adUiElements;
private final @Nullable AdEventListener adEventListener;
private final ImaFactory imaFactory;
private final Timeline.Period period;
@@ -336,6 +395,9 @@ public ImaAdsLoader(Context context, Uri adTagUri) {
/* adsResponse= */ null,
/* vastLoadTimeoutMs= */ TIMEOUT_UNSET,
/* mediaLoadTimeoutMs= */ TIMEOUT_UNSET,
+ /* mediaBitrate= */ BITRATE_UNSET,
+ /* focusSkipButtonWhenAvailable= */ true,
+ /* adUiElements= */ null,
/* adEventListener= */ null,
/* imaFactory= */ new DefaultImaFactory());
}
@@ -360,6 +422,9 @@ public ImaAdsLoader(Context context, Uri adTagUri, ImaSdkSettings imaSdkSettings
/* adsResponse= */ null,
/* vastLoadTimeoutMs= */ TIMEOUT_UNSET,
/* mediaLoadTimeoutMs= */ TIMEOUT_UNSET,
+ /* mediaBitrate= */ BITRATE_UNSET,
+ /* focusSkipButtonWhenAvailable= */ true,
+ /* adUiElements= */ null,
/* adEventListener= */ null,
/* imaFactory= */ new DefaultImaFactory());
}
@@ -371,6 +436,9 @@ private ImaAdsLoader(
@Nullable String adsResponse,
int vastLoadTimeoutMs,
int mediaLoadTimeoutMs,
+ int mediaBitrate,
+ boolean focusSkipButtonWhenAvailable,
+ @Nullable Set adUiElements,
@Nullable AdEventListener adEventListener,
ImaFactory imaFactory) {
Assertions.checkArgument(adTagUri != null || adsResponse != null);
@@ -378,6 +446,9 @@ private ImaAdsLoader(
this.adsResponse = adsResponse;
this.vastLoadTimeoutMs = vastLoadTimeoutMs;
this.mediaLoadTimeoutMs = mediaLoadTimeoutMs;
+ this.mediaBitrate = mediaBitrate;
+ this.focusSkipButtonWhenAvailable = focusSkipButtonWhenAvailable;
+ this.adUiElements = adUiElements;
this.adEventListener = adEventListener;
this.imaFactory = imaFactory;
if (imaSdkSettings == null) {
@@ -924,6 +995,13 @@ private void startAdPlayback() {
if (mediaLoadTimeoutMs != TIMEOUT_UNSET) {
adsRenderingSettings.setLoadVideoTimeout(mediaLoadTimeoutMs);
}
+ if (mediaBitrate != BITRATE_UNSET) {
+ adsRenderingSettings.setBitrateKbps(mediaBitrate / 1000);
+ }
+ adsRenderingSettings.setFocusSkipButtonWhenAvailable(focusSkipButtonWhenAvailable);
+ if (adUiElements != null) {
+ adsRenderingSettings.setUiElements(adUiElements);
+ }
// Set up the ad playback state, skipping ads based on the start position as required.
long[] adGroupTimesUs = getAdGroupTimesUs(adsManager.getAdCuePoints());
diff --git a/extensions/ima/src/test/java/com/google/android/exoplayer2/ext/ima/FakePlayer.java b/extensions/ima/src/test/java/com/google/android/exoplayer2/ext/ima/FakePlayer.java
index 0c35c9b66df..b8024d6534d 100644
--- a/extensions/ima/src/test/java/com/google/android/exoplayer2/ext/ima/FakePlayer.java
+++ b/extensions/ima/src/test/java/com/google/android/exoplayer2/ext/ima/FakePlayer.java
@@ -26,7 +26,6 @@
/* package */ final class FakePlayer extends StubExoPlayer {
private final ArrayList listeners;
- private final Timeline.Window window;
private final Timeline.Period period;
private final Timeline timeline;
@@ -41,7 +40,6 @@
public FakePlayer() {
listeners = new ArrayList<>();
- window = new Timeline.Window();
period = new Timeline.Period();
state = Player.STATE_IDLE;
playWhenReady = true;
@@ -151,16 +149,6 @@ public int getCurrentWindowIndex() {
return 0;
}
- @Override
- public int getNextWindowIndex() {
- return C.INDEX_UNSET;
- }
-
- @Override
- public int getPreviousWindowIndex() {
- return C.INDEX_UNSET;
- }
-
@Override
public long getDuration() {
if (timeline.isEmpty()) {
diff --git a/extensions/mediasession/src/main/java/com/google/android/exoplayer2/ext/mediasession/MediaSessionConnector.java b/extensions/mediasession/src/main/java/com/google/android/exoplayer2/ext/mediasession/MediaSessionConnector.java
index b814fd9222f..efad5a33bb7 100644
--- a/extensions/mediasession/src/main/java/com/google/android/exoplayer2/ext/mediasession/MediaSessionConnector.java
+++ b/extensions/mediasession/src/main/java/com/google/android/exoplayer2/ext/mediasession/MediaSessionConnector.java
@@ -259,6 +259,9 @@ public interface RatingCallback extends CommandReceiver {
/** See {@link MediaSessionCompat.Callback#onSetRating(RatingCompat)}. */
void onSetRating(Player player, RatingCompat rating);
+
+ /** See {@link MediaSessionCompat.Callback#onSetRating(RatingCompat, Bundle)}. */
+ void onSetRating(Player player, RatingCompat rating, Bundle extras);
}
/**
@@ -999,6 +1002,13 @@ public void onSetRating(RatingCompat rating) {
ratingCallback.onSetRating(player, rating);
}
}
+
+ @Override
+ public void onSetRating(RatingCompat rating, Bundle extras) {
+ if (canDispatchToRatingCallback(PlaybackStateCompat.ACTION_SET_RATING)) {
+ ratingCallback.onSetRating(player, rating, extras);
+ }
+ }
@Override
public void onAddQueueItem(MediaDescriptionCompat description) {
diff --git a/extensions/mediasession/src/main/java/com/google/android/exoplayer2/ext/mediasession/TimelineQueueNavigator.java b/extensions/mediasession/src/main/java/com/google/android/exoplayer2/ext/mediasession/TimelineQueueNavigator.java
index 6a1118b4123..4db3c23cfad 100644
--- a/extensions/mediasession/src/main/java/com/google/android/exoplayer2/ext/mediasession/TimelineQueueNavigator.java
+++ b/extensions/mediasession/src/main/java/com/google/android/exoplayer2/ext/mediasession/TimelineQueueNavigator.java
@@ -39,6 +39,7 @@ public abstract class TimelineQueueNavigator implements MediaSessionConnector.Qu
public static final int DEFAULT_MAX_QUEUE_SIZE = 10;
private final MediaSessionCompat mediaSession;
+ private final Timeline.Window window;
protected final int maxQueueSize;
private long activeQueueItemId;
@@ -68,6 +69,7 @@ public TimelineQueueNavigator(MediaSessionCompat mediaSession, int maxQueueSize)
this.mediaSession = mediaSession;
this.maxQueueSize = maxQueueSize;
activeQueueItemId = MediaSessionCompat.QueueItem.UNKNOWN_ID;
+ window = new Timeline.Window();
}
/**
@@ -81,25 +83,24 @@ public TimelineQueueNavigator(MediaSessionCompat mediaSession, int maxQueueSize)
@Override
public long getSupportedQueueNavigatorActions(Player player) {
- if (player == null || player.getCurrentTimeline().getWindowCount() < 2) {
+ if (player == null) {
return 0;
}
- if (player.getRepeatMode() != Player.REPEAT_MODE_OFF) {
- return PlaybackStateCompat.ACTION_SKIP_TO_NEXT | PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS
- | PlaybackStateCompat.ACTION_SKIP_TO_QUEUE_ITEM;
+ Timeline timeline = player.getCurrentTimeline();
+ if (timeline.isEmpty() || player.isPlayingAd()) {
+ return 0;
}
-
- int currentWindowIndex = player.getCurrentWindowIndex();
- long actions;
- if (currentWindowIndex == 0) {
- actions = PlaybackStateCompat.ACTION_SKIP_TO_NEXT;
- } else if (currentWindowIndex == player.getCurrentTimeline().getWindowCount() - 1) {
- actions = PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS;
- } else {
- actions = PlaybackStateCompat.ACTION_SKIP_TO_NEXT
- | PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS;
+ long actions = 0;
+ if (timeline.getWindowCount() > 1) {
+ actions |= PlaybackStateCompat.ACTION_SKIP_TO_QUEUE_ITEM;
+ }
+ if (window.isSeekable || !window.isDynamic || player.hasPrevious()) {
+ actions |= PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS;
}
- return actions | PlaybackStateCompat.ACTION_SKIP_TO_QUEUE_ITEM;
+ if (window.isDynamic || player.hasNext()) {
+ actions |= PlaybackStateCompat.ACTION_SKIP_TO_NEXT;
+ }
+ return actions;
}
@Override
@@ -125,22 +126,25 @@ public final long getActiveQueueItemId(@Nullable Player player) {
@Override
public void onSkipToPrevious(Player player) {
Timeline timeline = player.getCurrentTimeline();
- if (timeline.isEmpty()) {
+ if (timeline.isEmpty() || player.isPlayingAd()) {
return;
}
+ int windowIndex = player.getCurrentWindowIndex();
+ timeline.getWindow(windowIndex, window);
int previousWindowIndex = player.getPreviousWindowIndex();
- if (player.getCurrentPosition() > MAX_POSITION_FOR_SEEK_TO_PREVIOUS
- || previousWindowIndex == C.INDEX_UNSET) {
- player.seekTo(0);
- } else {
+ if (previousWindowIndex != C.INDEX_UNSET
+ && (player.getCurrentPosition() <= MAX_POSITION_FOR_SEEK_TO_PREVIOUS
+ || (window.isDynamic && !window.isSeekable))) {
player.seekTo(previousWindowIndex, C.TIME_UNSET);
+ } else {
+ player.seekTo(0);
}
}
@Override
public void onSkipToQueueItem(Player player, long id) {
Timeline timeline = player.getCurrentTimeline();
- if (timeline.isEmpty()) {
+ if (timeline.isEmpty() || player.isPlayingAd()) {
return;
}
int windowIndex = (int) id;
@@ -152,12 +156,15 @@ public void onSkipToQueueItem(Player player, long id) {
@Override
public void onSkipToNext(Player player) {
Timeline timeline = player.getCurrentTimeline();
- if (timeline.isEmpty()) {
+ if (timeline.isEmpty() || player.isPlayingAd()) {
return;
}
+ int windowIndex = player.getCurrentWindowIndex();
int nextWindowIndex = player.getNextWindowIndex();
if (nextWindowIndex != C.INDEX_UNSET) {
player.seekTo(nextWindowIndex, C.TIME_UNSET);
+ } else if (timeline.getWindow(windowIndex, window).isDynamic) {
+ player.seekTo(windowIndex, C.TIME_UNSET);
}
}
diff --git a/extensions/okhttp/src/main/java/com/google/android/exoplayer2/ext/okhttp/OkHttpDataSource.java b/extensions/okhttp/src/main/java/com/google/android/exoplayer2/ext/okhttp/OkHttpDataSource.java
index 2707f539bc2..778277fdbc0 100644
--- a/extensions/okhttp/src/main/java/com/google/android/exoplayer2/ext/okhttp/OkHttpDataSource.java
+++ b/extensions/okhttp/src/main/java/com/google/android/exoplayer2/ext/okhttp/OkHttpDataSource.java
@@ -172,8 +172,8 @@ public long open(DataSpec dataSpec) throws HttpDataSourceException {
if (!response.isSuccessful()) {
Map> headers = response.headers().toMultimap();
closeConnectionQuietly();
- InvalidResponseCodeException exception = new InvalidResponseCodeException(
- responseCode, headers, dataSpec);
+ InvalidResponseCodeException exception =
+ new InvalidResponseCodeException(responseCode, response.message(), headers, dataSpec);
if (responseCode == 416) {
exception.initCause(new DataSourceException(DataSourceException.POSITION_OUT_OF_RANGE));
}
diff --git a/extensions/vp9/src/main/java/com/google/android/exoplayer2/ext/vp9/LibvpxVideoRenderer.java b/extensions/vp9/src/main/java/com/google/android/exoplayer2/ext/vp9/LibvpxVideoRenderer.java
index c09d2fe55a4..e3081cd2d2e 100644
--- a/extensions/vp9/src/main/java/com/google/android/exoplayer2/ext/vp9/LibvpxVideoRenderer.java
+++ b/extensions/vp9/src/main/java/com/google/android/exoplayer2/ext/vp9/LibvpxVideoRenderer.java
@@ -45,6 +45,7 @@
import com.google.android.exoplayer2.video.VideoFrameMetadataListener;
import com.google.android.exoplayer2.video.VideoRendererEventListener;
import com.google.android.exoplayer2.video.VideoRendererEventListener.EventDispatcher;
+import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -64,9 +65,13 @@
*/
public class LibvpxVideoRenderer extends BaseRenderer {
+ @Documented
@Retention(RetentionPolicy.SOURCE)
- @IntDef({REINITIALIZATION_STATE_NONE, REINITIALIZATION_STATE_SIGNAL_END_OF_STREAM,
- REINITIALIZATION_STATE_WAIT_END_OF_STREAM})
+ @IntDef({
+ REINITIALIZATION_STATE_NONE,
+ REINITIALIZATION_STATE_SIGNAL_END_OF_STREAM,
+ REINITIALIZATION_STATE_WAIT_END_OF_STREAM
+ })
private @interface ReinitializationState {}
/**
* The decoder does not need to be re-initialized.
diff --git a/library/core/src/main/java/com/google/android/exoplayer2/BasePlayer.java b/library/core/src/main/java/com/google/android/exoplayer2/BasePlayer.java
new file mode 100644
index 00000000000..eb3bd4f91a6
--- /dev/null
+++ b/library/core/src/main/java/com/google/android/exoplayer2/BasePlayer.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.exoplayer2;
+
+import android.support.annotation.Nullable;
+import com.google.android.exoplayer2.util.Util;
+
+/** Abstract base {@link Player} which implements common implementation independent methods. */
+public abstract class BasePlayer implements Player {
+
+ protected final Timeline.Window window;
+
+ public BasePlayer() {
+ window = new Timeline.Window();
+ }
+
+ @Override
+ public final void seekToDefaultPosition() {
+ seekToDefaultPosition(getCurrentWindowIndex());
+ }
+
+ @Override
+ public final void seekToDefaultPosition(int windowIndex) {
+ seekTo(windowIndex, /* positionMs= */ C.TIME_UNSET);
+ }
+
+ @Override
+ public final void seekTo(long positionMs) {
+ seekTo(getCurrentWindowIndex(), positionMs);
+ }
+
+ @Override
+ public final boolean hasPrevious() {
+ return getPreviousWindowIndex() != C.INDEX_UNSET;
+ }
+
+ @Override
+ public final void previous() {
+ int previousWindowIndex = getPreviousWindowIndex();
+ if (previousWindowIndex != C.INDEX_UNSET) {
+ seekToDefaultPosition(previousWindowIndex);
+ }
+ }
+
+ @Override
+ public final boolean hasNext() {
+ return getNextWindowIndex() != C.INDEX_UNSET;
+ }
+
+ @Override
+ public final void next() {
+ int nextWindowIndex = getNextWindowIndex();
+ if (nextWindowIndex != C.INDEX_UNSET) {
+ seekToDefaultPosition(nextWindowIndex);
+ }
+ }
+
+ @Override
+ public final void stop() {
+ stop(/* reset= */ false);
+ }
+
+ @Override
+ public final int getNextWindowIndex() {
+ Timeline timeline = getCurrentTimeline();
+ return timeline.isEmpty()
+ ? C.INDEX_UNSET
+ : timeline.getNextWindowIndex(
+ getCurrentWindowIndex(), getRepeatModeForNavigation(), getShuffleModeEnabled());
+ }
+
+ @Override
+ public final int getPreviousWindowIndex() {
+ Timeline timeline = getCurrentTimeline();
+ return timeline.isEmpty()
+ ? C.INDEX_UNSET
+ : timeline.getPreviousWindowIndex(
+ getCurrentWindowIndex(), getRepeatModeForNavigation(), getShuffleModeEnabled());
+ }
+
+ @Override
+ @Nullable
+ public final Object getCurrentTag() {
+ int windowIndex = getCurrentWindowIndex();
+ Timeline timeline = getCurrentTimeline();
+ return windowIndex >= timeline.getWindowCount()
+ ? null
+ : timeline.getWindow(windowIndex, window, /* setTag= */ true).tag;
+ }
+
+ @Override
+ public final int getBufferedPercentage() {
+ long position = getBufferedPosition();
+ long duration = getDuration();
+ return position == C.TIME_UNSET || duration == C.TIME_UNSET
+ ? 0
+ : duration == 0 ? 100 : Util.constrainValue((int) ((position * 100) / duration), 0, 100);
+ }
+
+ @Override
+ public final boolean isCurrentWindowDynamic() {
+ Timeline timeline = getCurrentTimeline();
+ return !timeline.isEmpty() && timeline.getWindow(getCurrentWindowIndex(), window).isDynamic;
+ }
+
+ @Override
+ public final boolean isCurrentWindowSeekable() {
+ Timeline timeline = getCurrentTimeline();
+ return !timeline.isEmpty() && timeline.getWindow(getCurrentWindowIndex(), window).isSeekable;
+ }
+
+ @Override
+ public final long getContentDuration() {
+ Timeline timeline = getCurrentTimeline();
+ return timeline.isEmpty()
+ ? C.TIME_UNSET
+ : timeline.getWindow(getCurrentWindowIndex(), window).getDurationMs();
+ }
+
+ @RepeatMode
+ private int getRepeatModeForNavigation() {
+ @RepeatMode int repeatMode = getRepeatMode();
+ return repeatMode == REPEAT_MODE_ONE ? REPEAT_MODE_OFF : repeatMode;
+ }
+}
diff --git a/library/core/src/main/java/com/google/android/exoplayer2/C.java b/library/core/src/main/java/com/google/android/exoplayer2/C.java
index 0cbdc14b1ce..fac9818d9ec 100644
--- a/library/core/src/main/java/com/google/android/exoplayer2/C.java
+++ b/library/core/src/main/java/com/google/android/exoplayer2/C.java
@@ -28,6 +28,7 @@
import com.google.android.exoplayer2.util.Util;
import com.google.android.exoplayer2.video.VideoFrameMetadataListener;
import com.google.android.exoplayer2.video.spherical.CameraMotionListener;
+import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.UUID;
@@ -114,6 +115,7 @@ private C() {}
* Crypto modes for a codec. One of {@link #CRYPTO_MODE_UNENCRYPTED}, {@link #CRYPTO_MODE_AES_CTR}
* or {@link #CRYPTO_MODE_AES_CBC}.
*/
+ @Documented
@Retention(RetentionPolicy.SOURCE)
@IntDef({CRYPTO_MODE_UNENCRYPTED, CRYPTO_MODE_AES_CTR, CRYPTO_MODE_AES_CBC})
public @interface CryptoMode {}
@@ -144,6 +146,7 @@ private C() {}
* #ENCODING_E_AC3}, {@link #ENCODING_DTS}, {@link #ENCODING_DTS_HD} or {@link
* #ENCODING_DOLBY_TRUEHD}.
*/
+ @Documented
@Retention(RetentionPolicy.SOURCE)
@IntDef({
Format.NO_VALUE,
@@ -169,6 +172,7 @@ private C() {}
* #ENCODING_PCM_24BIT}, {@link #ENCODING_PCM_32BIT}, {@link #ENCODING_PCM_FLOAT}, {@link
* #ENCODING_PCM_MU_LAW} or {@link #ENCODING_PCM_A_LAW}.
*/
+ @Documented
@Retention(RetentionPolicy.SOURCE)
@IntDef({
Format.NO_VALUE,
@@ -215,6 +219,7 @@ private C() {}
* #STREAM_TYPE_RING}, {@link #STREAM_TYPE_SYSTEM}, {@link #STREAM_TYPE_VOICE_CALL} or {@link
* #STREAM_TYPE_USE_DEFAULT}.
*/
+ @Documented
@Retention(RetentionPolicy.SOURCE)
@IntDef({
STREAM_TYPE_ALARM,
@@ -269,6 +274,7 @@ private C() {}
* #CONTENT_TYPE_MOVIE}, {@link #CONTENT_TYPE_MUSIC}, {@link #CONTENT_TYPE_SONIFICATION}, {@link
* #CONTENT_TYPE_SPEECH} or {@link #CONTENT_TYPE_UNKNOWN}.
*/
+ @Documented
@Retention(RetentionPolicy.SOURCE)
@IntDef({
CONTENT_TYPE_MOVIE,
@@ -309,6 +315,7 @@ private C() {}
*
Note that {@code FLAG_HW_AV_SYNC} is not available because the player takes care of setting
* the flag when tunneling is enabled via a track selector.
*/
+ @Documented
@Retention(RetentionPolicy.SOURCE)
@IntDef(
flag = true,
@@ -331,6 +338,7 @@ private C() {}
* #USAGE_UNKNOWN}, {@link #USAGE_VOICE_COMMUNICATION} or {@link
* #USAGE_VOICE_COMMUNICATION_SIGNALLING}.
*/
+ @Documented
@Retention(RetentionPolicy.SOURCE)
@IntDef({
USAGE_ALARM,
@@ -427,6 +435,7 @@ private C() {}
* #AUDIOFOCUS_GAIN_TRANSIENT}, {@link #AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK} or {@link
* #AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE}.
*/
+ @Documented
@Retention(RetentionPolicy.SOURCE)
@IntDef({
AUDIOFOCUS_NONE,
@@ -454,6 +463,7 @@ private C() {}
* #BUFFER_FLAG_KEY_FRAME}, {@link #BUFFER_FLAG_END_OF_STREAM}, {@link #BUFFER_FLAG_ENCRYPTED} and
* {@link #BUFFER_FLAG_DECODE_ONLY}.
*/
+ @Documented
@Retention(RetentionPolicy.SOURCE)
@IntDef(
flag = true,
@@ -482,6 +492,7 @@ private C() {}
* Video scaling modes for {@link MediaCodec}-based {@link Renderer}s. One of {@link
* #VIDEO_SCALING_MODE_SCALE_TO_FIT} or {@link #VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING}.
*/
+ @Documented
@Retention(RetentionPolicy.SOURCE)
@IntDef(value = {VIDEO_SCALING_MODE_SCALE_TO_FIT, VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING})
public @interface VideoScalingMode {}
@@ -504,6 +515,7 @@ private C() {}
* Track selection flags. Possible flag values are {@link #SELECTION_FLAG_DEFAULT}, {@link
* #SELECTION_FLAG_FORCED} and {@link #SELECTION_FLAG_AUTOSELECT}.
*/
+ @Documented
@Retention(RetentionPolicy.SOURCE)
@IntDef(
flag = true,
@@ -530,6 +542,7 @@ private C() {}
* Represents a streaming or other media type. One of {@link #TYPE_DASH}, {@link #TYPE_SS}, {@link
* #TYPE_HLS} or {@link #TYPE_OTHER}.
*/
+ @Documented
@Retention(RetentionPolicy.SOURCE)
@IntDef({TYPE_DASH, TYPE_SS, TYPE_HLS, TYPE_OTHER})
public @interface ContentType {}
@@ -796,6 +809,7 @@ private C() {}
* #STEREO_MODE_MONO}, {@link #STEREO_MODE_TOP_BOTTOM}, {@link #STEREO_MODE_LEFT_RIGHT} or {@link
* #STEREO_MODE_STEREO_MESH}.
*/
+ @Documented
@Retention(RetentionPolicy.SOURCE)
@IntDef({
Format.NO_VALUE,
@@ -827,6 +841,7 @@ private C() {}
* Video colorspaces. One of {@link Format#NO_VALUE}, {@link #COLOR_SPACE_BT709}, {@link
* #COLOR_SPACE_BT601} or {@link #COLOR_SPACE_BT2020}.
*/
+ @Documented
@Retention(RetentionPolicy.SOURCE)
@IntDef({Format.NO_VALUE, COLOR_SPACE_BT709, COLOR_SPACE_BT601, COLOR_SPACE_BT2020})
public @interface ColorSpace {}
@@ -847,6 +862,7 @@ private C() {}
* Video color transfer characteristics. One of {@link Format#NO_VALUE}, {@link
* #COLOR_TRANSFER_SDR}, {@link #COLOR_TRANSFER_ST2084} or {@link #COLOR_TRANSFER_HLG}.
*/
+ @Documented
@Retention(RetentionPolicy.SOURCE)
@IntDef({Format.NO_VALUE, COLOR_TRANSFER_SDR, COLOR_TRANSFER_ST2084, COLOR_TRANSFER_HLG})
public @interface ColorTransfer {}
@@ -867,6 +883,7 @@ private C() {}
* Video color range. One of {@link Format#NO_VALUE}, {@link #COLOR_RANGE_LIMITED} or {@link
* #COLOR_RANGE_FULL}.
*/
+ @Documented
@Retention(RetentionPolicy.SOURCE)
@IntDef({Format.NO_VALUE, COLOR_RANGE_LIMITED, COLOR_RANGE_FULL})
public @interface ColorRange {}
@@ -899,6 +916,7 @@ private C() {}
* #NETWORK_TYPE_4G}, {@link #NETWORK_TYPE_CELLULAR_UNKNOWN}, {@link #NETWORK_TYPE_ETHERNET} or
* {@link #NETWORK_TYPE_OTHER}.
*/
+ @Documented
@Retention(RetentionPolicy.SOURCE)
@IntDef({
NETWORK_TYPE_UNKNOWN,
@@ -960,7 +978,10 @@ public static long msToUs(long timeMs) {
}
/**
- * Returns a newly generated {@link android.media.AudioTrack} session identifier.
+ * Returns a newly generated audio session identifier, or {@link AudioManager#ERROR} if an error
+ * occurred in which case audio playback may fail.
+ *
+ * @see AudioManager#generateAudioSessionId()
*/
@TargetApi(21)
public static int generateAudioSessionIdV21(Context context) {
diff --git a/library/core/src/main/java/com/google/android/exoplayer2/DefaultRenderersFactory.java b/library/core/src/main/java/com/google/android/exoplayer2/DefaultRenderersFactory.java
index 4e69bc316e2..cc16c43b057 100644
--- a/library/core/src/main/java/com/google/android/exoplayer2/DefaultRenderersFactory.java
+++ b/library/core/src/main/java/com/google/android/exoplayer2/DefaultRenderersFactory.java
@@ -36,6 +36,7 @@
import com.google.android.exoplayer2.video.MediaCodecVideoRenderer;
import com.google.android.exoplayer2.video.VideoRendererEventListener;
import com.google.android.exoplayer2.video.spherical.CameraMotionRenderer;
+import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.reflect.Constructor;
@@ -56,6 +57,7 @@ public class DefaultRenderersFactory implements RenderersFactory {
* Modes for using extension renderers. One of {@link #EXTENSION_RENDERER_MODE_OFF}, {@link
* #EXTENSION_RENDERER_MODE_ON} or {@link #EXTENSION_RENDERER_MODE_PREFER}.
*/
+ @Documented
@Retention(RetentionPolicy.SOURCE)
@IntDef({EXTENSION_RENDERER_MODE_OFF, EXTENSION_RENDERER_MODE_ON, EXTENSION_RENDERER_MODE_PREFER})
public @interface ExtensionRendererMode {}
diff --git a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlaybackException.java b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlaybackException.java
index d591876a51e..6b84245141a 100644
--- a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlaybackException.java
+++ b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlaybackException.java
@@ -19,6 +19,7 @@
import com.google.android.exoplayer2.source.MediaSource;
import com.google.android.exoplayer2.util.Assertions;
import java.io.IOException;
+import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -31,6 +32,7 @@ public final class ExoPlaybackException extends Exception {
* The type of source that produced the error. One of {@link #TYPE_SOURCE}, {@link #TYPE_RENDERER}
* or {@link #TYPE_UNEXPECTED}.
*/
+ @Documented
@Retention(RetentionPolicy.SOURCE)
@IntDef({TYPE_SOURCE, TYPE_RENDERER, TYPE_UNEXPECTED})
public @interface Type {}
diff --git a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java
index 027f316493d..ffdadb78f7b 100644
--- a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java
+++ b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java
@@ -40,10 +40,8 @@
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;
-/**
- * An {@link ExoPlayer} implementation. Instances can be obtained from {@link ExoPlayerFactory}.
- */
-/* package */ final class ExoPlayerImpl implements ExoPlayer {
+/** An {@link ExoPlayer} implementation. Instances can be obtained from {@link ExoPlayerFactory}. */
+/* package */ final class ExoPlayerImpl extends BasePlayer implements ExoPlayer {
private static final String TAG = "ExoPlayerImpl";
@@ -61,7 +59,6 @@
private final ExoPlayerImplInternal internalPlayer;
private final Handler internalPlayerHandler;
private final CopyOnWriteArraySet listeners;
- private final Timeline.Window window;
private final Timeline.Period period;
private final ArrayDeque pendingPlaybackInfoUpdates;
@@ -118,7 +115,6 @@ public ExoPlayerImpl(
new RendererConfiguration[renderers.length],
new TrackSelection[renderers.length],
null);
- window = new Timeline.Window();
period = new Timeline.Period();
playbackParameters = PlaybackParameters.DEFAULT;
seekParameters = SeekParameters.DEFAULT;
@@ -293,21 +289,6 @@ public boolean isLoading() {
return playbackInfo.isLoading;
}
- @Override
- public void seekToDefaultPosition() {
- seekToDefaultPosition(getCurrentWindowIndex());
- }
-
- @Override
- public void seekToDefaultPosition(int windowIndex) {
- seekTo(windowIndex, C.TIME_UNSET);
- }
-
- @Override
- public void seekTo(long positionMs) {
- seekTo(getCurrentWindowIndex(), positionMs);
- }
-
@Override
public void seekTo(int windowIndex, long positionMs) {
Timeline timeline = playbackInfo.timeline;
@@ -377,19 +358,6 @@ public SeekParameters getSeekParameters() {
return seekParameters;
}
- @Override
- public @Nullable Object getCurrentTag() {
- int windowIndex = getCurrentWindowIndex();
- return windowIndex >= playbackInfo.timeline.getWindowCount()
- ? null
- : playbackInfo.timeline.getWindow(windowIndex, window, /* setTag= */ true).tag;
- }
-
- @Override
- public void stop() {
- stop(/* reset= */ false);
- }
-
@Override
public void stop(boolean reset) {
if (reset) {
@@ -494,20 +462,6 @@ public int getCurrentWindowIndex() {
}
}
- @Override
- public int getNextWindowIndex() {
- Timeline timeline = playbackInfo.timeline;
- return timeline.isEmpty() ? C.INDEX_UNSET
- : timeline.getNextWindowIndex(getCurrentWindowIndex(), repeatMode, shuffleModeEnabled);
- }
-
- @Override
- public int getPreviousWindowIndex() {
- Timeline timeline = playbackInfo.timeline;
- return timeline.isEmpty() ? C.INDEX_UNSET
- : timeline.getPreviousWindowIndex(getCurrentWindowIndex(), repeatMode, shuffleModeEnabled);
- }
-
@Override
public long getDuration() {
if (isPlayingAd()) {
@@ -540,32 +494,11 @@ public long getBufferedPosition() {
return getContentBufferedPosition();
}
- @Override
- public int getBufferedPercentage() {
- long position = getBufferedPosition();
- long duration = getDuration();
- return position == C.TIME_UNSET || duration == C.TIME_UNSET
- ? 0
- : (duration == 0 ? 100 : Util.constrainValue((int) ((position * 100) / duration), 0, 100));
- }
-
@Override
public long getTotalBufferedDuration() {
return Math.max(0, C.usToMs(playbackInfo.totalBufferedDurationUs));
}
- @Override
- public boolean isCurrentWindowDynamic() {
- Timeline timeline = playbackInfo.timeline;
- return !timeline.isEmpty() && timeline.getWindow(getCurrentWindowIndex(), window).isDynamic;
- }
-
- @Override
- public boolean isCurrentWindowSeekable() {
- Timeline timeline = playbackInfo.timeline;
- return !timeline.isEmpty() && timeline.getWindow(getCurrentWindowIndex(), window).isSeekable;
- }
-
@Override
public boolean isPlayingAd() {
return !shouldMaskPosition() && playbackInfo.periodId.isAd();
@@ -581,13 +514,6 @@ public int getCurrentAdIndexInAdGroup() {
return isPlayingAd() ? playbackInfo.periodId.adIndexInAdGroup : C.INDEX_UNSET;
}
- @Override
- public long getContentDuration() {
- return playbackInfo.timeline.isEmpty()
- ? C.TIME_UNSET
- : playbackInfo.timeline.getWindow(getCurrentWindowIndex(), window).getDurationMs();
- }
-
@Override
public long getContentPosition() {
if (isPlayingAd()) {
@@ -692,7 +618,7 @@ private void handlePlaybackInfo(
if (playbackInfo.startPositionUs == C.TIME_UNSET) {
// Replace internal unset start position with externally visible start position of zero.
playbackInfo =
- playbackInfo.fromNewPosition(
+ playbackInfo.resetToNewPosition(
playbackInfo.periodId, /* startPositionUs= */ 0, playbackInfo.contentPositionUs);
}
if ((!this.playbackInfo.timeline.isEmpty() || hasPendingPrepare)
@@ -731,20 +657,26 @@ private PlaybackInfo getResetPlaybackInfo(
maskingPeriodIndex = getCurrentPeriodIndex();
maskingWindowPositionMs = getCurrentPosition();
}
+ MediaPeriodId mediaPeriodId =
+ resetPosition
+ ? playbackInfo.getDummyFirstMediaPeriodId(shuffleModeEnabled, window)
+ : playbackInfo.periodId;
+ long startPositionUs = resetPosition ? 0 : playbackInfo.positionUs;
+ long contentPositionUs = resetPosition ? C.TIME_UNSET : playbackInfo.contentPositionUs;
return new PlaybackInfo(
resetState ? Timeline.EMPTY : playbackInfo.timeline,
resetState ? null : playbackInfo.manifest,
- playbackInfo.periodId,
- playbackInfo.startPositionUs,
- playbackInfo.contentPositionUs,
+ mediaPeriodId,
+ startPositionUs,
+ contentPositionUs,
playbackState,
/* isLoading= */ false,
resetState ? TrackGroupArray.EMPTY : playbackInfo.trackGroups,
resetState ? emptyTrackSelectorResult : playbackInfo.trackSelectorResult,
- playbackInfo.periodId,
- playbackInfo.startPositionUs,
+ mediaPeriodId,
+ startPositionUs,
/* totalBufferedDurationUs= */ 0,
- playbackInfo.startPositionUs);
+ startPositionUs);
}
private void updatePlaybackInfo(
diff --git a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java
index d861020d26a..7f41719d1db 100644
--- a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java
+++ b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java
@@ -448,7 +448,11 @@ private void seekToCurrentPosition(boolean sendDiscontinuity) throws ExoPlayback
seekToPeriodPosition(periodId, playbackInfo.positionUs, /* forceDisableRenderers= */ true);
if (newPositionUs != playbackInfo.positionUs) {
playbackInfo =
- playbackInfo.fromNewPosition(periodId, newPositionUs, playbackInfo.contentPositionUs);
+ playbackInfo.copyWithNewPosition(
+ periodId,
+ newPositionUs,
+ playbackInfo.contentPositionUs,
+ getTotalBufferedDurationUs());
if (sendDiscontinuity) {
playbackInfoUpdate.setPositionDiscontinuity(Player.DISCONTINUITY_REASON_INTERNAL);
}
@@ -483,8 +487,12 @@ private void updatePlaybackPositions() throws ExoPlaybackException {
// A MediaPeriod may report a discontinuity at the current playback position to ensure the
// renderers are flushed. Only report the discontinuity externally if the position changed.
if (periodPositionUs != playbackInfo.positionUs) {
- playbackInfo = playbackInfo.fromNewPosition(playbackInfo.periodId, periodPositionUs,
- playbackInfo.contentPositionUs);
+ playbackInfo =
+ playbackInfo.copyWithNewPosition(
+ playbackInfo.periodId,
+ periodPositionUs,
+ playbackInfo.contentPositionUs,
+ getTotalBufferedDurationUs());
playbackInfoUpdate.setPositionDiscontinuity(Player.DISCONTINUITY_REASON_INTERNAL);
}
} else {
@@ -496,10 +504,8 @@ private void updatePlaybackPositions() throws ExoPlaybackException {
// Update the buffered position and total buffered duration.
MediaPeriodHolder loadingPeriod = queue.getLoadingPeriod();
- playbackInfo.bufferedPositionUs =
- loadingPeriod.getBufferedPositionUs(/* convertEosToDuration= */ true);
- playbackInfo.totalBufferedDurationUs =
- playbackInfo.bufferedPositionUs - loadingPeriod.toPeriodTime(rendererPositionUs);
+ playbackInfo.bufferedPositionUs = loadingPeriod.getBufferedPositionUs();
+ playbackInfo.totalBufferedDurationUs = getTotalBufferedDurationUs();
}
private void doSomeWork() throws ExoPlaybackException, IOException {
@@ -599,7 +605,7 @@ private void seekToInternal(SeekPosition seekPosition) throws ExoPlaybackExcepti
if (resolvedSeekPosition == null) {
// The seek position was valid for the timeline that it was performed into, but the
// timeline has changed or is not ready and a suitable seek position could not be resolved.
- periodId = getFirstMediaPeriodId();
+ periodId = playbackInfo.getDummyFirstMediaPeriodId(shuffleModeEnabled, window);
periodPositionUs = C.TIME_UNSET;
contentPositionUs = C.TIME_UNSET;
seekPositionAdjusted = true;
@@ -647,7 +653,9 @@ private void seekToInternal(SeekPosition seekPosition) throws ExoPlaybackExcepti
periodPositionUs = newPeriodPositionUs;
}
} finally {
- playbackInfo = playbackInfo.fromNewPosition(periodId, periodPositionUs, contentPositionUs);
+ playbackInfo =
+ playbackInfo.copyWithNewPosition(
+ periodId, periodPositionUs, contentPositionUs, getTotalBufferedDurationUs());
if (seekPositionAdjusted) {
playbackInfoUpdate.setPositionDiscontinuity(Player.DISCONTINUITY_REASON_SEEK_ADJUSTMENT);
}
@@ -752,17 +760,6 @@ private void releaseInternal() {
}
}
- private MediaPeriodId getFirstMediaPeriodId() {
- Timeline timeline = playbackInfo.timeline;
- if (timeline.isEmpty()) {
- return PlaybackInfo.DUMMY_MEDIA_PERIOD_ID;
- }
- int firstPeriodIndex =
- timeline.getWindow(timeline.getFirstWindowIndex(shuffleModeEnabled), window)
- .firstPeriodIndex;
- return new MediaPeriodId(timeline.getUidOfPeriod(firstPeriodIndex));
- }
-
private void resetInternal(
boolean releaseMediaSource, boolean resetPosition, boolean resetState) {
handler.removeMessages(MSG_DO_SOME_WORK);
@@ -791,8 +788,11 @@ private void resetInternal(
pendingMessages.clear();
nextPendingMessageIndex = 0;
}
+ MediaPeriodId mediaPeriodId =
+ resetPosition
+ ? playbackInfo.getDummyFirstMediaPeriodId(shuffleModeEnabled, window)
+ : playbackInfo.periodId;
// Set the start position to TIME_UNSET so that a subsequent seek to 0 isn't ignored.
- MediaPeriodId mediaPeriodId = resetPosition ? getFirstMediaPeriodId() : playbackInfo.periodId;
long startPositionUs = resetPosition ? C.TIME_UNSET : playbackInfo.positionUs;
long contentPositionUs = resetPosition ? C.TIME_UNSET : playbackInfo.contentPositionUs;
playbackInfo =
@@ -1020,8 +1020,12 @@ private void reselectTracksInternal() throws ExoPlaybackException {
playbackInfo.positionUs, recreateStreams, streamResetFlags);
if (playbackInfo.playbackState != Player.STATE_ENDED
&& periodPositionUs != playbackInfo.positionUs) {
- playbackInfo = playbackInfo.fromNewPosition(playbackInfo.periodId, periodPositionUs,
- playbackInfo.contentPositionUs);
+ playbackInfo =
+ playbackInfo.copyWithNewPosition(
+ playbackInfo.periodId,
+ periodPositionUs,
+ playbackInfo.contentPositionUs,
+ getTotalBufferedDurationUs());
playbackInfoUpdate.setPositionDiscontinuity(Player.DISCONTINUITY_REASON_INTERNAL);
resetRendererPosition(periodPositionUs);
}
@@ -1097,12 +1101,10 @@ private boolean shouldTransitionToReadyState(boolean renderersReadyOrEnded) {
}
// Renderers are ready and we're loading. Ask the LoadControl whether to transition.
MediaPeriodHolder loadingHolder = queue.getLoadingPeriod();
- long bufferedPositionUs = loadingHolder.getBufferedPositionUs(!loadingHolder.info.isFinal);
- return bufferedPositionUs == C.TIME_END_OF_SOURCE
+ boolean bufferedToEnd = loadingHolder.isFullyBuffered() && loadingHolder.info.isFinal;
+ return bufferedToEnd
|| loadControl.shouldStartPlayback(
- bufferedPositionUs - loadingHolder.toPeriodTime(rendererPositionUs),
- mediaClock.getPlaybackParameters().speed,
- rebuffering);
+ getTotalBufferedDurationUs(), mediaClock.getPlaybackParameters().speed, rebuffering);
}
private boolean isTimelineReady() {
@@ -1164,8 +1166,13 @@ private void handleSourceInfoRefreshed(MediaSourceRefreshInfo sourceRefreshInfo)
periodPosition =
resolveSeekPosition(pendingInitialSeekPosition, /* trySubsequentPeriods= */ true);
} catch (IllegalSeekPositionException e) {
+ MediaPeriodId firstMediaPeriodId =
+ playbackInfo.getDummyFirstMediaPeriodId(shuffleModeEnabled, window);
playbackInfo =
- playbackInfo.fromNewPosition(getFirstMediaPeriodId(), C.TIME_UNSET, C.TIME_UNSET);
+ playbackInfo.resetToNewPosition(
+ firstMediaPeriodId,
+ /* startPositionUs= */ C.TIME_UNSET,
+ /* contentPositionUs= */ C.TIME_UNSET);
throw e;
}
pendingInitialSeekPosition = null;
@@ -1178,7 +1185,7 @@ private void handleSourceInfoRefreshed(MediaSourceRefreshInfo sourceRefreshInfo)
long positionUs = periodPosition.second;
MediaPeriodId periodId = queue.resolveMediaPeriodIdForAds(periodUid, positionUs);
playbackInfo =
- playbackInfo.fromNewPosition(
+ playbackInfo.resetToNewPosition(
periodId, periodId.isAd() ? 0 : positionUs, /* contentPositionUs= */ positionUs);
}
} else if (playbackInfo.startPositionUs == C.TIME_UNSET) {
@@ -1192,7 +1199,7 @@ private void handleSourceInfoRefreshed(MediaSourceRefreshInfo sourceRefreshInfo)
long startPositionUs = defaultPosition.second;
MediaPeriodId periodId = queue.resolveMediaPeriodIdForAds(periodUid, startPositionUs);
playbackInfo =
- playbackInfo.fromNewPosition(
+ playbackInfo.resetToNewPosition(
periodId,
periodId.isAd() ? 0 : startPositionUs,
/* contentPositionUs= */ startPositionUs);
@@ -1211,7 +1218,7 @@ private void handleSourceInfoRefreshed(MediaSourceRefreshInfo sourceRefreshInfo)
long startPositionUs = defaultPosition.second;
MediaPeriodId periodId = queue.resolveMediaPeriodIdForAds(periodUid, startPositionUs);
playbackInfo =
- playbackInfo.fromNewPosition(
+ playbackInfo.resetToNewPosition(
periodId,
/* startPositionUs= */ periodId.isAd() ? 0 : startPositionUs,
/* contentPositionUs= */ startPositionUs);
@@ -1250,7 +1257,9 @@ private void handleSourceInfoRefreshed(MediaSourceRefreshInfo sourceRefreshInfo)
}
// Actually do the seek.
long seekPositionUs = seekToPeriodPosition(periodId, periodId.isAd() ? 0 : contentPositionUs);
- playbackInfo = playbackInfo.fromNewPosition(periodId, seekPositionUs, contentPositionUs);
+ playbackInfo =
+ playbackInfo.copyWithNewPosition(
+ periodId, seekPositionUs, contentPositionUs, getTotalBufferedDurationUs());
return;
}
@@ -1262,7 +1271,9 @@ private void handleSourceInfoRefreshed(MediaSourceRefreshInfo sourceRefreshInfo)
// The previously playing ad should no longer be played, so skip it.
long seekPositionUs =
seekToPeriodPosition(periodId, periodId.isAd() ? 0 : contentPositionUs);
- playbackInfo = playbackInfo.fromNewPosition(periodId, seekPositionUs, contentPositionUs);
+ playbackInfo =
+ playbackInfo.copyWithNewPosition(
+ periodId, seekPositionUs, contentPositionUs, getTotalBufferedDurationUs());
return;
}
}
@@ -1418,8 +1429,12 @@ private void updatePeriods() throws ExoPlaybackException, IOException {
MediaPeriodHolder oldPlayingPeriodHolder = playingPeriodHolder;
playingPeriodHolder = queue.advancePlayingPeriod();
updatePlayingPeriodRenderers(oldPlayingPeriodHolder);
- playbackInfo = playbackInfo.fromNewPosition(playingPeriodHolder.info.id,
- playingPeriodHolder.info.startPositionUs, playingPeriodHolder.info.contentPositionUs);
+ playbackInfo =
+ playbackInfo.copyWithNewPosition(
+ playingPeriodHolder.info.id,
+ playingPeriodHolder.info.startPositionUs,
+ playingPeriodHolder.info.contentPositionUs,
+ getTotalBufferedDurationUs());
playbackInfoUpdate.setPositionDiscontinuity(discontinuityReason);
updatePlaybackPositions();
advancedPlayingPeriod = true;
@@ -1571,7 +1586,7 @@ private void maybeContinueLoading() {
return;
}
long bufferedDurationUs =
- nextLoadPositionUs - loadingPeriodHolder.toPeriodTime(rendererPositionUs);
+ getTotalBufferedDurationUs(/* bufferedPositionInLoadingPeriodUs= */ nextLoadPositionUs);
boolean continueLoading =
loadControl.shouldContinueLoading(
bufferedDurationUs, mediaClock.getPlaybackParameters().speed);
@@ -1667,6 +1682,11 @@ private void handleLoadingMediaPeriodChanged(boolean loadingTrackSelectionChange
if (loadingMediaPeriodChanged) {
playbackInfo = playbackInfo.copyWithLoadingMediaPeriodId(loadingMediaPeriodId);
}
+ playbackInfo.bufferedPositionUs =
+ loadingMediaPeriodHolder == null
+ ? playbackInfo.positionUs
+ : loadingMediaPeriodHolder.getBufferedPositionUs();
+ playbackInfo.totalBufferedDurationUs = getTotalBufferedDurationUs();
if ((loadingMediaPeriodChanged || loadingTrackSelectionChanged)
&& loadingMediaPeriodHolder != null
&& loadingMediaPeriodHolder.prepared) {
@@ -1675,6 +1695,17 @@ private void handleLoadingMediaPeriodChanged(boolean loadingTrackSelectionChange
}
}
+ private long getTotalBufferedDurationUs() {
+ return getTotalBufferedDurationUs(playbackInfo.bufferedPositionUs);
+ }
+
+ private long getTotalBufferedDurationUs(long bufferedPositionInLoadingPeriodUs) {
+ MediaPeriodHolder loadingPeriodHolder = queue.getLoadingPeriod();
+ return loadingPeriodHolder == null
+ ? 0
+ : bufferedPositionInLoadingPeriodUs - loadingPeriodHolder.toPeriodTime(rendererPositionUs);
+ }
+
private void updateLoadControlTrackSelection(
TrackGroupArray trackGroups, TrackSelectorResult trackSelectorResult) {
loadControl.onTracksSelected(renderers, trackGroups, trackSelectorResult.selections);
diff --git a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerLibraryInfo.java b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerLibraryInfo.java
index f5ad677d779..c4dda9e9574 100644
--- a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerLibraryInfo.java
+++ b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerLibraryInfo.java
@@ -29,11 +29,11 @@ public final class ExoPlayerLibraryInfo {
/** The version of the library expressed as a string, for example "1.2.3". */
// Intentionally hardcoded. Do not derive from other constants (e.g. VERSION_INT) or vice versa.
- public static final String VERSION = "2.9.0";
+ public static final String VERSION = "2.9.1";
/** The version of the library expressed as {@code "ExoPlayerLib/" + VERSION}. */
// Intentionally hardcoded. Do not derive from other constants (e.g. VERSION) or vice versa.
- public static final String VERSION_SLASHY = "ExoPlayerLib/2.9.0";
+ public static final String VERSION_SLASHY = "ExoPlayerLib/2.9.1";
/**
* The version of the library expressed as an integer, for example 1002003.
@@ -43,7 +43,7 @@ public final class ExoPlayerLibraryInfo {
* integer version 123045006 (123-045-006).
*/
// Intentionally hardcoded. Do not derive from other constants (e.g. VERSION) or vice versa.
- public static final int VERSION_INT = 2009000;
+ public static final int VERSION_INT = 2009001;
/**
* Whether the library was compiled with {@link com.google.android.exoplayer2.util.Assertions}
diff --git a/library/core/src/main/java/com/google/android/exoplayer2/MediaPeriodHolder.java b/library/core/src/main/java/com/google/android/exoplayer2/MediaPeriodHolder.java
index 4941b4efc67..5925c8f3830 100644
--- a/library/core/src/main/java/com/google/android/exoplayer2/MediaPeriodHolder.java
+++ b/library/core/src/main/java/com/google/android/exoplayer2/MediaPeriodHolder.java
@@ -117,23 +117,18 @@ public long getDurationUs() {
}
/**
- * Returns the buffered position in microseconds. If the period is buffered to the end then
- * {@link C#TIME_END_OF_SOURCE} is returned unless {@code convertEosToDuration} is true, in which
- * case the period duration is returned.
+ * Returns the buffered position in microseconds. If the period is buffered to the end, then the
+ * period duration is returned.
*
- * @param convertEosToDuration Whether to return the period duration rather than
- * {@link C#TIME_END_OF_SOURCE} if the period is fully buffered.
* @return The buffered position in microseconds.
*/
- public long getBufferedPositionUs(boolean convertEosToDuration) {
+ public long getBufferedPositionUs() {
if (!prepared) {
return info.startPositionUs;
}
long bufferedPositionUs =
hasEnabledTracks ? mediaPeriod.getBufferedPositionUs() : C.TIME_END_OF_SOURCE;
- return bufferedPositionUs == C.TIME_END_OF_SOURCE && convertEosToDuration
- ? info.durationUs
- : bufferedPositionUs;
+ return bufferedPositionUs == C.TIME_END_OF_SOURCE ? info.durationUs : bufferedPositionUs;
}
public long getNextLoadPositionUs() {
diff --git a/library/core/src/main/java/com/google/android/exoplayer2/MediaPeriodQueue.java b/library/core/src/main/java/com/google/android/exoplayer2/MediaPeriodQueue.java
index 2edf7bb8c61..c51c1cc1492 100644
--- a/library/core/src/main/java/com/google/android/exoplayer2/MediaPeriodQueue.java
+++ b/library/core/src/main/java/com/google/android/exoplayer2/MediaPeriodQueue.java
@@ -532,6 +532,11 @@ private MediaPeriodInfo getFirstMediaPeriodInfo(PlaybackInfo playbackInfo) {
// until the timeline is updated. Store whether the next timeline period is ready when the
// timeline is updated, to avoid repeatedly checking the same timeline.
MediaPeriodInfo mediaPeriodInfo = mediaPeriodHolder.info;
+ // The expected delay until playback transitions to the new period is equal the duration of
+ // media that's currently buffered (assuming no interruptions). This is used to project forward
+ // the start position for transitions to new windows.
+ long bufferedDurationUs =
+ mediaPeriodHolder.getRendererOffset() + mediaPeriodInfo.durationUs - rendererPositionUs;
if (mediaPeriodInfo.isLastInTimelinePeriod) {
int currentPeriodIndex = timeline.getIndexOfPeriod(mediaPeriodInfo.id.periodUid);
int nextPeriodIndex =
@@ -549,19 +554,15 @@ private MediaPeriodInfo getFirstMediaPeriodInfo(PlaybackInfo playbackInfo) {
long windowSequenceNumber = mediaPeriodInfo.id.windowSequenceNumber;
if (timeline.getWindow(nextWindowIndex, window).firstPeriodIndex == nextPeriodIndex) {
// We're starting to buffer a new window. When playback transitions to this window we'll
- // want it to be from its default start position. The expected delay until playback
- // transitions is equal the duration of media that's currently buffered (assuming no
- // interruptions). Hence we project the default start position forward by the duration of
- // the buffer, and start buffering from this point.
- long defaultPositionProjectionUs =
- mediaPeriodHolder.getRendererOffset() + mediaPeriodInfo.durationUs - rendererPositionUs;
+ // want it to be from its default start position, so project the default start position
+ // forward by the duration of the buffer, and start buffering from this point.
Pair