Commit b5beb326 by ojw28 Committed by GitHub

Merge pull request #5037 from google/dev-v2-r2.9.1

r2.9.1
parents fa9d7d5e a02a75ba
Showing with 1517 additions and 462 deletions
......@@ -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'
......
# 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 may need to
......
......@@ -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
......
......@@ -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
......

303 Bytes | W: | H:

261 Bytes | W: | H:

demos/main/src/main/res/drawable-xxhdpi/ic_download.png
demos/main/src/main/res/drawable-xxhdpi/ic_download.png
demos/main/src/main/res/drawable-xxhdpi/ic_download.png
demos/main/src/main/res/drawable-xxhdpi/ic_download.png
  • 2-up
  • Swipe
  • Onion skin

304 Bytes | W: | H:

263 Bytes | W: | H:

demos/main/src/main/res/drawable-xxxhdpi/ic_download.png
demos/main/src/main/res/drawable-xxxhdpi/ic_download.png
demos/main/src/main/res/drawable-xxxhdpi/ic_download.png
demos/main/src/main/res/drawable-xxxhdpi/ic_download.png
  • 2-up
  • Swipe
  • Onion skin
......@@ -31,7 +31,7 @@ android {
}
dependencies {
api 'com.google.android.gms:play-services-cast-framework:16.0.1'
api 'com.google.android.gms:play-services-cast-framework:16.0.3'
implementation project(modulePrefix + 'library-core')
implementation project(modulePrefix + 'library-ui')
testImplementation project(modulePrefix + 'testutils')
......
......@@ -18,6 +18,7 @@ package com.google.android.exoplayer2.ext.cast;
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.PlaybackParameters;
......@@ -31,7 +32,6 @@ import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
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;
......@@ -62,7 +62,7 @@ import java.util.concurrent.CopyOnWriteArraySet;
*
* <p>Methods should be called on the application's main thread.</p>
*/
public final class CastPlayer implements Player {
public final class CastPlayer extends BasePlayer {
/**
* Listener of changes in the cast session availability.
......@@ -95,7 +95,6 @@ public final class CastPlayer implements Player {
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;
......@@ -128,7 +127,6 @@ public final class CastPlayer implements Player {
public CastPlayer(CastContext castContext) {
this.castContext = castContext;
timelineTracker = new CastTimelineTracker();
window = new Timeline.Window();
period = new Timeline.Period();
statusListener = new StatusListener();
seekResultCallback = new SeekResultCallback();
......@@ -342,21 +340,6 @@ public final class CastPlayer implements Player {
}
@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();
// We assume the default position is 0. There is no support for seeking to the default position
......@@ -393,11 +376,6 @@ public final class CastPlayer implements Player {
}
@Override
public void stop() {
stop(/* reset= */ false);
}
@Override
public void stop(boolean reset) {
playbackState = STATE_IDLE;
if (remoteMediaClient != null) {
......@@ -486,32 +464,11 @@ public final class CastPlayer implements Player {
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
......@@ -529,15 +486,6 @@ public final class CastPlayer implements Player {
}
@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();
long currentPosition = getCurrentPosition();
......@@ -547,18 +495,6 @@ public final class CastPlayer implements Player {
}
@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;
}
......@@ -574,11 +510,6 @@ public final class CastPlayer implements Player {
}
@Override
public long getContentDuration() {
return getDuration();
}
@Override
public boolean isLoading() {
return false;
}
......
......@@ -326,8 +326,12 @@ public class CronetDataSource extends BaseDataSource implements HttpDataSource {
// 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 class CronetDataSource extends BaseDataSource implements HttpDataSource {
// 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;
}
......
......@@ -19,6 +19,7 @@ import android.content.Context;
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 {}
......
......@@ -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="<path to Android NDK>"
```
* 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:
......
......@@ -37,6 +37,7 @@ import com.google.android.exoplayer2.util.FlacStreamInfo;
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,
......
......@@ -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)
......@@ -17,4 +17,4 @@
APP_OPTIM := release
APP_STL := gnustl_static
APP_CPPFLAGS := -frtti
APP_PLATFORM := android-9
APP_PLATFORM := android-14
......@@ -40,6 +40,7 @@ import com.google.ads.interactivemedia.v3.api.AdsRequest;
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.Log;
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 final class ImaAdsLoader
private @Nullable ImaSdkSettings imaSdkSettings;
private @Nullable AdEventListener adEventListener;
private @Nullable Set<UiElement> adUiElements;
private int vastLoadTimeoutMs;
private int mediaLoadTimeoutMs;
private int mediaBitrate;
private boolean focusSkipButtonWhenAvailable;
private ImaFactory imaFactory;
/**
......@@ -103,6 +110,8 @@ public final class ImaAdsLoader
this.context = Assertions.checkNotNull(context);
vastLoadTimeoutMs = TIMEOUT_UNSET;
mediaLoadTimeoutMs = TIMEOUT_UNSET;
mediaBitrate = BITRATE_UNSET;
focusSkipButtonWhenAvailable = true;
imaFactory = new DefaultImaFactory();
}
......@@ -133,6 +142,18 @@ public final class ImaAdsLoader
}
/**
* 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<UiElement> adUiElements) {
this.adUiElements = new HashSet<>(Assertions.checkNotNull(adUiElements));
return this;
}
/**
* Sets the VAST load timeout, in milliseconds.
*
* @param vastLoadTimeoutMs The VAST load timeout, in milliseconds.
......@@ -140,7 +161,7 @@ public final class ImaAdsLoader
* @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 final class ImaAdsLoader
* @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 final class ImaAdsLoader
null,
vastLoadTimeoutMs,
mediaLoadTimeoutMs,
mediaBitrate,
focusSkipButtonWhenAvailable,
adUiElements,
adEventListener,
imaFactory);
}
......@@ -199,6 +250,9 @@ public final class ImaAdsLoader
adsResponse,
vastLoadTimeoutMs,
mediaLoadTimeoutMs,
mediaBitrate,
focusSkipButtonWhenAvailable,
adUiElements,
adEventListener,
imaFactory);
}
......@@ -228,8 +282,10 @@ public final class ImaAdsLoader
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 final class ImaAdsLoader
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<UiElement> adUiElements;
private final @Nullable AdEventListener adEventListener;
private final ImaFactory imaFactory;
private final Timeline.Period period;
......@@ -336,6 +395,9 @@ public final class ImaAdsLoader
/* 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 final class ImaAdsLoader
/* adsResponse= */ null,
/* vastLoadTimeoutMs= */ TIMEOUT_UNSET,
/* mediaLoadTimeoutMs= */ TIMEOUT_UNSET,
/* mediaBitrate= */ BITRATE_UNSET,
/* focusSkipButtonWhenAvailable= */ true,
/* adUiElements= */ null,
/* adEventListener= */ null,
/* imaFactory= */ new DefaultImaFactory());
}
......@@ -371,6 +436,9 @@ public final class ImaAdsLoader
@Nullable String adsResponse,
int vastLoadTimeoutMs,
int mediaLoadTimeoutMs,
int mediaBitrate,
boolean focusSkipButtonWhenAvailable,
@Nullable Set<UiElement> adUiElements,
@Nullable AdEventListener adEventListener,
ImaFactory imaFactory) {
Assertions.checkArgument(adTagUri != null || adsResponse != null);
......@@ -378,6 +446,9 @@ public final class 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 @@ public final class ImaAdsLoader
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());
......
......@@ -26,7 +26,6 @@ import java.util.ArrayList;
/* package */ final class FakePlayer extends StubExoPlayer {
private final ArrayList<Player.EventListener> listeners;
private final Timeline.Window window;
private final Timeline.Period period;
private final Timeline timeline;
......@@ -41,7 +40,6 @@ import java.util.ArrayList;
public FakePlayer() {
listeners = new ArrayList<>();
window = new Timeline.Window();
period = new Timeline.Period();
state = Player.STATE_IDLE;
playWhenReady = true;
......@@ -152,16 +150,6 @@ import java.util.ArrayList;
}
@Override
public int getNextWindowIndex() {
return C.INDEX_UNSET;
}
@Override
public int getPreviousWindowIndex() {
return C.INDEX_UNSET;
}
@Override
public long getDuration() {
if (timeline.isEmpty()) {
return C.INDEX_UNSET;
......
......@@ -259,6 +259,9 @@ public final class MediaSessionConnector {
/** 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);
}
/**
......@@ -1002,6 +1005,13 @@ public final class MediaSessionConnector {
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) {
......
......@@ -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 abstract class TimelineQueueNavigator implements MediaSessionConnector.Qu
this.mediaSession = mediaSession;
this.maxQueueSize = maxQueueSize;
activeQueueItemId = MediaSessionCompat.QueueItem.UNKNOWN_ID;
window = new Timeline.Window();
}
/**
......@@ -81,25 +83,24 @@ public abstract class TimelineQueueNavigator implements MediaSessionConnector.Qu
@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 abstract class TimelineQueueNavigator implements MediaSessionConnector.Qu
@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 abstract class TimelineQueueNavigator implements MediaSessionConnector.Qu
@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);
}
}
......
......@@ -172,8 +172,8 @@ public class OkHttpDataSource extends BaseDataSource implements HttpDataSource {
if (!response.isSuccessful()) {
Map<String, List<String>> 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));
}
......
......@@ -45,6 +45,7 @@ import com.google.android.exoplayer2.util.Util;
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 @@ import java.lang.annotation.RetentionPolicy;
*/
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.
......
/*
* 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;
}
}
......@@ -28,6 +28,7 @@ import com.google.android.exoplayer2.audio.AuxEffectInfo;
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 @@ public final class 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 @@ public final class 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 @@ public final class 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 @@ public final class 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 @@ public final class 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 @@ public final class C {
* <p>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 @@ public final class C {
* #USAGE_UNKNOWN}, {@link #USAGE_VOICE_COMMUNICATION} or {@link
* #USAGE_VOICE_COMMUNICATION_SIGNALLING}.
*/
@Documented
@Retention(RetentionPolicy.SOURCE)
@IntDef({
USAGE_ALARM,
......@@ -427,6 +435,7 @@ public final class 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 @@ public final class 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 @@ public final class 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 @@ public final class 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 @@ public final class 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 @@ public final class 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 @@ public final class 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 @@ public final class 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 @@ public final class 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 @@ public final class 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 final class C {
}
/**
* 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) {
......
......@@ -36,6 +36,7 @@ import com.google.android.exoplayer2.util.Log;
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 {}
......
......@@ -19,6 +19,7 @@ import android.support.annotation.IntDef;
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 {}
......
......@@ -40,10 +40,8 @@ import java.util.List;
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 @@ import java.util.concurrent.CopyOnWriteArraySet;
private final ExoPlayerImplInternal internalPlayer;
private final Handler internalPlayerHandler;
private final CopyOnWriteArraySet<Player.EventListener> listeners;
private final Timeline.Window window;
private final Timeline.Period period;
private final ArrayDeque<PlaybackInfoUpdate> pendingPlaybackInfoUpdates;
......@@ -118,7 +115,6 @@ import java.util.concurrent.CopyOnWriteArraySet;
new RendererConfiguration[renderers.length],
new TrackSelection[renderers.length],
null);
window = new Timeline.Window();
period = new Timeline.Period();
playbackParameters = PlaybackParameters.DEFAULT;
seekParameters = SeekParameters.DEFAULT;
......@@ -294,21 +290,6 @@ import java.util.concurrent.CopyOnWriteArraySet;
}
@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;
if (windowIndex < 0 || (!timeline.isEmpty() && windowIndex >= timeline.getWindowCount())) {
......@@ -378,19 +359,6 @@ import java.util.concurrent.CopyOnWriteArraySet;
}
@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) {
playbackError = null;
......@@ -495,20 +463,6 @@ import java.util.concurrent.CopyOnWriteArraySet;
}
@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()) {
MediaPeriodId periodId = playbackInfo.periodId;
......@@ -541,32 +495,11 @@ import java.util.concurrent.CopyOnWriteArraySet;
}
@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();
}
......@@ -582,13 +515,6 @@ import java.util.concurrent.CopyOnWriteArraySet;
}
@Override
public long getContentDuration() {
return playbackInfo.timeline.isEmpty()
? C.TIME_UNSET
: playbackInfo.timeline.getWindow(getCurrentWindowIndex(), window).getDurationMs();
}
@Override
public long getContentPosition() {
if (isPlayingAd()) {
playbackInfo.timeline.getPeriodByUid(playbackInfo.periodId.periodUid, period);
......@@ -692,7 +618,7 @@ import java.util.concurrent.CopyOnWriteArraySet;
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 @@ import java.util.concurrent.CopyOnWriteArraySet;
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(
......
......@@ -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}
......
......@@ -117,23 +117,18 @@ import com.google.android.exoplayer2.util.Log;
}
/**
* 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() {
......
......@@ -532,6 +532,11 @@ import com.google.android.exoplayer2.util.Assertions;
// 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 @@ import com.google.android.exoplayer2.util.Assertions;
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<Object, Long> defaultPosition =
timeline.getPeriodPosition(
window,
period,
nextWindowIndex,
C.TIME_UNSET,
Math.max(0, defaultPositionProjectionUs));
/* windowPositionUs= */ C.TIME_UNSET,
/* defaultPositionProjectionUs= */ Math.max(0, bufferedDurationUs));
if (defaultPosition == null) {
return null;
}
......@@ -601,11 +602,27 @@ import com.google.android.exoplayer2.util.Assertions;
mediaPeriodInfo.contentPositionUs,
currentPeriodId.windowSequenceNumber);
} else {
// Play content from the ad group position.
// Play content from the ad group position. As a special case, if we're transitioning from a
// preroll ad group to content and there are no other ad groups, project the start position
// forward as if this were a transition to a new window. No attempt is made to handle
// midrolls in live streams, as it's unclear what content position should play after an ad
// (server-side dynamic ad insertion is more appropriate for this use case).
long startPositionUs = mediaPeriodInfo.contentPositionUs;
if (period.getAdGroupCount() == 1 && period.getAdGroupTimeUs(0) == 0) {
Pair<Object, Long> defaultPosition =
timeline.getPeriodPosition(
window,
period,
period.windowIndex,
/* windowPositionUs= */ C.TIME_UNSET,
/* defaultPositionProjectionUs= */ Math.max(0, bufferedDurationUs));
if (defaultPosition == null) {
return null;
}
startPositionUs = defaultPosition.second;
}
return getMediaPeriodInfoForContent(
currentPeriodId.periodUid,
mediaPeriodInfo.contentPositionUs,
currentPeriodId.windowSequenceNumber);
currentPeriodId.periodUid, startPositionUs, currentPeriodId.windowSequenceNumber);
}
} else if (mediaPeriodInfo.id.endPositionUs != C.TIME_END_OF_SOURCE) {
// Play the next ad group if it's available.
......
......@@ -15,6 +15,7 @@
*/
package com.google.android.exoplayer2;
import android.support.annotation.CheckResult;
import android.support.annotation.Nullable;
import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId;
import com.google.android.exoplayer2.source.TrackGroupArray;
......@@ -29,7 +30,7 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult;
* Dummy media period id used while the timeline is empty and no period id is specified. This id
* is used when playback infos are created with {@link #createDummy(long, TrackSelectorResult)}.
*/
public static final MediaPeriodId DUMMY_MEDIA_PERIOD_ID =
private static final MediaPeriodId DUMMY_MEDIA_PERIOD_ID =
new MediaPeriodId(/* periodUid= */ new Object());
/** The current {@link Timeline}. */
......@@ -40,7 +41,8 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult;
public final MediaPeriodId periodId;
/**
* The start position at which playback started in {@link #periodId} relative to the start of the
* associated period in the {@link #timeline}, in microseconds.
* associated period in the {@link #timeline}, in microseconds. Note that this value changes for
* each position discontinuity.
*/
public final long startPositionUs;
/**
......@@ -103,6 +105,23 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult;
startPositionUs);
}
/**
* Create playback info.
*
* @param timeline See {@link #timeline}.
* @param manifest See {@link #manifest}.
* @param periodId See {@link #periodId}.
* @param startPositionUs See {@link #startPositionUs}.
* @param contentPositionUs See {@link #contentPositionUs}.
* @param playbackState See {@link #playbackState}.
* @param isLoading See {@link #isLoading}.
* @param trackGroups See {@link #trackGroups}.
* @param trackSelectorResult See {@link #trackSelectorResult}.
* @param loadingMediaPeriodId See {@link #loadingMediaPeriodId}.
* @param bufferedPositionUs See {@link #bufferedPositionUs}.
* @param totalBufferedDurationUs See {@link #totalBufferedDurationUs}.
* @param positionUs See {@link #positionUs}.
*/
public PlaybackInfo(
Timeline timeline,
@Nullable Object manifest,
......@@ -132,7 +151,35 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult;
this.positionUs = positionUs;
}
public PlaybackInfo fromNewPosition(
/**
* Returns dummy media period id for the first-to-be-played period of the current timeline.
*
* @param shuffleModeEnabled Whether shuffle mode is enabled.
* @param window A writable {@link Timeline.Window}.
* @return A dummy media period id for the first-to-be-played period of the current timeline.
*/
public MediaPeriodId getDummyFirstMediaPeriodId(
boolean shuffleModeEnabled, Timeline.Window window) {
if (timeline.isEmpty()) {
return DUMMY_MEDIA_PERIOD_ID;
}
int firstPeriodIndex =
timeline.getWindow(timeline.getFirstWindowIndex(shuffleModeEnabled), window)
.firstPeriodIndex;
return new MediaPeriodId(timeline.getUidOfPeriod(firstPeriodIndex));
}
/**
* Copies playback info and resets playing and loading position.
*
* @param periodId New playing and loading {@link MediaPeriodId}.
* @param startPositionUs New start position. See {@link #startPositionUs}.
* @param contentPositionUs New content position. See {@link #contentPositionUs}. Value is ignored
* if {@code periodId.isAd()} is true.
* @return Copied playback info with reset position.
*/
@CheckResult
public PlaybackInfo resetToNewPosition(
MediaPeriodId periodId, long startPositionUs, long contentPositionUs) {
return new PlaybackInfo(
timeline,
......@@ -150,6 +197,46 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult;
startPositionUs);
}
/**
* Copied playback info with new playing position.
*
* @param periodId New playing media period. See {@link #periodId}.
* @param positionUs New position. See {@link #positionUs}.
* @param contentPositionUs New content position. See {@link #contentPositionUs}. Value is ignored
* if {@code periodId.isAd()} is true.
* @param totalBufferedDurationUs New buffered duration. See {@link #totalBufferedDurationUs}.
* @return Copied playback info with new playing position.
*/
@CheckResult
public PlaybackInfo copyWithNewPosition(
MediaPeriodId periodId,
long positionUs,
long contentPositionUs,
long totalBufferedDurationUs) {
return new PlaybackInfo(
timeline,
manifest,
periodId,
positionUs,
periodId.isAd() ? contentPositionUs : C.TIME_UNSET,
playbackState,
isLoading,
trackGroups,
trackSelectorResult,
loadingMediaPeriodId,
bufferedPositionUs,
totalBufferedDurationUs,
positionUs);
}
/**
* Copies playback info with new timeline and manifest.
*
* @param timeline New timeline. See {@link #timeline}.
* @param manifest New manifest. See {@link #manifest}.
* @return Copied playback info with new timeline and manifest.
*/
@CheckResult
public PlaybackInfo copyWithTimeline(Timeline timeline, Object manifest) {
return new PlaybackInfo(
timeline,
......@@ -167,6 +254,13 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult;
positionUs);
}
/**
* Copies playback info with new playback state.
*
* @param playbackState New playback state. See {@link #playbackState}.
* @return Copied playback info with new playback state.
*/
@CheckResult
public PlaybackInfo copyWithPlaybackState(int playbackState) {
return new PlaybackInfo(
timeline,
......@@ -184,6 +278,13 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult;
positionUs);
}
/**
* Copies playback info with new loading state.
*
* @param isLoading New loading state. See {@link #isLoading}.
* @return Copied playback info with new loading state.
*/
@CheckResult
public PlaybackInfo copyWithIsLoading(boolean isLoading) {
return new PlaybackInfo(
timeline,
......@@ -201,6 +302,14 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult;
positionUs);
}
/**
* Copies playback info with new track information.
*
* @param trackGroups New track groups. See {@link #trackGroups}.
* @param trackSelectorResult New track selector result. See {@link #trackSelectorResult}.
* @return Copied playback info with new track information.
*/
@CheckResult
public PlaybackInfo copyWithTrackInfo(
TrackGroupArray trackGroups, TrackSelectorResult trackSelectorResult) {
return new PlaybackInfo(
......@@ -219,6 +328,13 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult;
positionUs);
}
/**
* Copies playback info with new loading media period.
*
* @param loadingMediaPeriodId New loading media period id. See {@link #loadingMediaPeriodId}.
* @return Copied playback info with new loading media period.
*/
@CheckResult
public PlaybackInfo copyWithLoadingMediaPeriodId(MediaPeriodId loadingMediaPeriodId) {
return new PlaybackInfo(
timeline,
......
......@@ -33,6 +33,7 @@ import com.google.android.exoplayer2.util.Util;
import com.google.android.exoplayer2.video.VideoFrameMetadataListener;
import com.google.android.exoplayer2.video.VideoListener;
import com.google.android.exoplayer2.video.spherical.CameraMotionListener;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
......@@ -446,6 +447,7 @@ public interface Player {
* Repeat modes for playback. One of {@link #REPEAT_MODE_OFF}, {@link #REPEAT_MODE_ONE} or {@link
* #REPEAT_MODE_ALL}.
*/
@Documented
@Retention(RetentionPolicy.SOURCE)
@IntDef({REPEAT_MODE_OFF, REPEAT_MODE_ONE, REPEAT_MODE_ALL})
@interface RepeatMode {}
......@@ -467,6 +469,7 @@ public interface Player {
* {@link #DISCONTINUITY_REASON_SEEK}, {@link #DISCONTINUITY_REASON_SEEK_ADJUSTMENT}, {@link
* #DISCONTINUITY_REASON_AD_INSERTION} or {@link #DISCONTINUITY_REASON_INTERNAL}.
*/
@Documented
@Retention(RetentionPolicy.SOURCE)
@IntDef({
DISCONTINUITY_REASON_PERIOD_TRANSITION,
......@@ -497,6 +500,7 @@ public interface Player {
* Reasons for timeline and/or manifest changes. One of {@link #TIMELINE_CHANGE_REASON_PREPARED},
* {@link #TIMELINE_CHANGE_REASON_RESET} or {@link #TIMELINE_CHANGE_REASON_DYNAMIC}.
*/
@Documented
@Retention(RetentionPolicy.SOURCE)
@IntDef({
TIMELINE_CHANGE_REASON_PREPARED,
......@@ -656,6 +660,32 @@ public interface Player {
void seekTo(int windowIndex, long positionMs);
/**
* Returns whether a previous window exists, which may depend on the current repeat mode and
* whether shuffle mode is enabled.
*/
boolean hasPrevious();
/**
* Seeks to the default position of the previous window in the timeline, which may depend on the
* current repeat mode and whether shuffle mode is enabled. Does nothing if {@link #hasPrevious()}
* is {@code false}.
*/
void previous();
/**
* Returns whether a next window exists, which may depend on the current repeat mode and whether
* shuffle mode is enabled.
*/
boolean hasNext();
/**
* Seeks to the default position of the next window in the timeline, which may depend on the
* current repeat mode and whether shuffle mode is enabled. Does nothing if {@link #hasNext()} is
* {@code false}.
*/
void next();
/**
* Attempts to set the playback parameters. Passing {@code null} sets the parameters to the
* default, {@link PlaybackParameters#DEFAULT}, which means there is no speed or pitch adjustment.
* <p>
......
......@@ -19,6 +19,7 @@ import android.support.annotation.IntDef;
import com.google.android.exoplayer2.source.SampleStream;
import com.google.android.exoplayer2.util.MediaClock;
import java.io.IOException;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
......@@ -38,6 +39,7 @@ public interface Renderer extends PlayerMessage.Target {
* The renderer states. One of {@link #STATE_DISABLED}, {@link #STATE_ENABLED} or {@link
* #STATE_STARTED}.
*/
@Documented
@Retention(RetentionPolicy.SOURCE)
@IntDef({STATE_DISABLED, STATE_ENABLED, STATE_STARTED})
@interface State {}
......
......@@ -64,7 +64,7 @@ import java.util.concurrent.CopyOnWriteArraySet;
* be obtained from {@link ExoPlayerFactory}.
*/
@TargetApi(16)
public class SimpleExoPlayer
public class SimpleExoPlayer extends BasePlayer
implements ExoPlayer, Player.AudioComponent, Player.VideoComponent, Player.TextComponent {
/** @deprecated Use {@link com.google.android.exoplayer2.video.VideoListener}. */
......@@ -928,27 +928,6 @@ public class SimpleExoPlayer
}
@Override
public void seekToDefaultPosition() {
verifyApplicationThread();
analyticsCollector.notifySeekStarted();
player.seekToDefaultPosition();
}
@Override
public void seekToDefaultPosition(int windowIndex) {
verifyApplicationThread();
analyticsCollector.notifySeekStarted();
player.seekToDefaultPosition(windowIndex);
}
@Override
public void seekTo(long positionMs) {
verifyApplicationThread();
analyticsCollector.notifySeekStarted();
player.seekTo(positionMs);
}
@Override
public void seekTo(int windowIndex, long positionMs) {
verifyApplicationThread();
analyticsCollector.notifySeekStarted();
......@@ -980,17 +959,6 @@ public class SimpleExoPlayer
}
@Override
public @Nullable Object getCurrentTag() {
verifyApplicationThread();
return player.getCurrentTag();
}
@Override
public void stop() {
stop(/* reset= */ false);
}
@Override
public void stop(boolean reset) {
verifyApplicationThread();
player.stop(reset);
......@@ -1093,18 +1061,6 @@ public class SimpleExoPlayer
}
@Override
public int getNextWindowIndex() {
verifyApplicationThread();
return player.getNextWindowIndex();
}
@Override
public int getPreviousWindowIndex() {
verifyApplicationThread();
return player.getPreviousWindowIndex();
}
@Override
public long getDuration() {
verifyApplicationThread();
return player.getDuration();
......@@ -1123,30 +1079,12 @@ public class SimpleExoPlayer
}
@Override
public int getBufferedPercentage() {
verifyApplicationThread();
return player.getBufferedPercentage();
}
@Override
public long getTotalBufferedDuration() {
verifyApplicationThread();
return player.getTotalBufferedDuration();
}
@Override
public boolean isCurrentWindowDynamic() {
verifyApplicationThread();
return player.isCurrentWindowDynamic();
}
@Override
public boolean isCurrentWindowSeekable() {
verifyApplicationThread();
return player.isCurrentWindowSeekable();
}
@Override
public boolean isPlayingAd() {
verifyApplicationThread();
return player.isPlayingAd();
......@@ -1165,12 +1103,6 @@ public class SimpleExoPlayer
}
@Override
public long getContentDuration() {
verifyApplicationThread();
return player.getContentDuration();
}
@Override
public long getContentPosition() {
verifyApplicationThread();
return player.getContentPosition();
......
......@@ -23,6 +23,7 @@ import com.google.android.exoplayer2.drm.DrmInitData;
import com.google.android.exoplayer2.util.MimeTypes;
import com.google.android.exoplayer2.util.ParsableBitArray;
import com.google.android.exoplayer2.util.ParsableByteArray;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.nio.ByteBuffer;
......@@ -40,6 +41,7 @@ public final class Ac3Util {
* AC3 stream types. See also ETSI TS 102 366 E.1.3.1.1. One of {@link #STREAM_TYPE_UNDEFINED},
* {@link #STREAM_TYPE_TYPE0}, {@link #STREAM_TYPE_TYPE1} or {@link #STREAM_TYPE_TYPE2}.
*/
@Documented
@Retention(RetentionPolicy.SOURCE)
@IntDef({STREAM_TYPE_UNDEFINED, STREAM_TYPE_TYPE0, STREAM_TYPE_TYPE1, STREAM_TYPE_TYPE2})
public @interface StreamType {}
......
......@@ -28,6 +28,7 @@ import com.google.android.exoplayer2.source.MediaSource;
import com.google.android.exoplayer2.util.Assertions;
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 org.checkerframework.checker.nullness.qual.MonotonicNonNull;
......@@ -56,6 +57,7 @@ public final class AudioFocusManager {
* Player commands. One of {@link #PLAYER_COMMAND_DO_NOT_PLAY}, {@link
* #PLAYER_COMMAND_WAIT_FOR_CALLBACK} or {@link #PLAYER_COMMAND_PLAY_WHEN_READY}.
*/
@Documented
@Retention(RetentionPolicy.SOURCE)
@IntDef({
PLAYER_COMMAND_DO_NOT_PLAY,
......@@ -71,6 +73,7 @@ public final class AudioFocusManager {
public static final int PLAYER_COMMAND_PLAY_WHEN_READY = 1;
/** Audio focus state. */
@Documented
@Retention(RetentionPolicy.SOURCE)
@IntDef({
AUDIO_FOCUS_STATE_LOST_FOCUS,
......@@ -141,8 +144,8 @@ public final class AudioFocusManager {
*/
public @PlayerCommand int setAudioAttributes(
@Nullable AudioAttributes audioAttributes, boolean playWhenReady, int playerState) {
if (audioAttributes == null) {
return PLAYER_COMMAND_PLAY_WHEN_READY;
if (this.audioAttributes == null && audioAttributes == null) {
return playWhenReady ? PLAYER_COMMAND_PLAY_WHEN_READY : PLAYER_COMMAND_DO_NOT_PLAY;
}
Assertions.checkNotNull(
......@@ -160,11 +163,9 @@ public final class AudioFocusManager {
}
}
if (playerState == Player.STATE_IDLE) {
return PLAYER_COMMAND_WAIT_FOR_CALLBACK;
} else {
return handlePrepare(playWhenReady);
}
return playerState == Player.STATE_IDLE
? handleIdle(playWhenReady)
: handlePrepare(playWhenReady);
}
/**
......@@ -196,12 +197,9 @@ public final class AudioFocusManager {
if (!playWhenReady) {
abandonAudioFocus();
return PLAYER_COMMAND_DO_NOT_PLAY;
} else if (playerState != Player.STATE_IDLE) {
return requestAudioFocus();
}
return focusGain != C.AUDIOFOCUS_NONE
? PLAYER_COMMAND_WAIT_FOR_CALLBACK
: PLAYER_COMMAND_PLAY_WHEN_READY;
return playerState == Player.STATE_IDLE ? handleIdle(playWhenReady) : requestAudioFocus();
}
/** Called by the player as part of {@link ExoPlayer#stop(boolean)}. */
......@@ -215,6 +213,11 @@ public final class AudioFocusManager {
// Internal methods.
@PlayerCommand
private int handleIdle(boolean playWhenReady) {
return playWhenReady ? PLAYER_COMMAND_PLAY_WHEN_READY : PLAYER_COMMAND_DO_NOT_PLAY;
}
private @PlayerCommand int requestAudioFocus() {
int focusRequestResult;
......
......@@ -22,6 +22,7 @@ import android.support.annotation.IntDef;
import android.support.annotation.Nullable;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.util.Util;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
......@@ -45,6 +46,7 @@ import java.lang.annotation.RetentionPolicy;
/* package */ final class AudioTimestampPoller {
/** Timestamp polling states. */
@Documented
@Retention(RetentionPolicy.SOURCE)
@IntDef({
STATE_INITIALIZING,
......
......@@ -25,6 +25,7 @@ import android.support.annotation.Nullable;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.util.Assertions;
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.Method;
......@@ -97,6 +98,7 @@ import java.lang.reflect.Method;
}
/** {@link AudioTrack} playback states. */
@Documented
@Retention(RetentionPolicy.SOURCE)
@IntDef({PLAYSTATE_STOPPED, PLAYSTATE_PAUSED, PLAYSTATE_PLAYING})
private @interface PlayState {}
......
......@@ -30,6 +30,7 @@ import com.google.android.exoplayer2.PlaybackParameters;
import com.google.android.exoplayer2.util.Assertions;
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.nio.ByteBuffer;
......@@ -171,6 +172,9 @@ public final class DefaultAudioSink implements AudioSink {
*/
private static final int BUFFER_MULTIPLICATION_FACTOR = 4;
/** To avoid underruns on some devices (e.g., Broadcom 7271), scale up the AC3 buffer duration. */
private static final int AC3_BUFFER_MULTIPLICATION_FACTOR = 2;
/**
* @see AudioTrack#ERROR_BAD_VALUE
*/
......@@ -195,12 +199,12 @@ public final class DefaultAudioSink implements AudioSink {
private static final String TAG = "AudioTrack";
/**
* Represents states of the {@link #startMediaTimeUs} value.
*/
/** Represents states of the {@link #startMediaTimeUs} value. */
@Documented
@Retention(RetentionPolicy.SOURCE)
@IntDef({START_NOT_SET, START_IN_SYNC, START_NEED_SYNC})
private @interface StartMediaTimeState {}
private static final int START_NOT_SET = 0;
private static final int START_IN_SYNC = 1;
private static final int START_NEED_SYNC = 2;
......@@ -483,6 +487,9 @@ public final class DefaultAudioSink implements AudioSink {
return Util.constrainValue(multipliedBufferSize, minAppBufferSize, maxAppBufferSize);
} else {
int rate = getMaximumEncodedRateBytesPerSecond(outputEncoding);
if (outputEncoding == C.ENCODING_AC3) {
rate *= AC3_BUFFER_MULTIPLICATION_FACTOR;
}
return (int) (PASSTHROUGH_BUFFER_DURATION_US * rate / C.MICROS_PER_SECOND);
}
}
......
......@@ -24,6 +24,7 @@ import android.media.MediaCrypto;
import android.media.MediaFormat;
import android.media.audiofx.Virtualizer;
import android.os.Handler;
import android.support.annotation.CallSuper;
import android.support.annotation.Nullable;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ExoPlaybackException;
......@@ -86,6 +87,7 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
private int codecMaxInputSize;
private boolean passthroughEnabled;
private boolean codecNeedsDiscardChannelsWorkaround;
private boolean codecNeedsEosBufferTimestampWorkaround;
private android.media.MediaFormat passthroughMediaFormat;
private @C.Encoding int pcmEncoding;
private int channelCount;
......@@ -345,6 +347,7 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
float codecOperatingRate) {
codecMaxInputSize = getCodecMaxInputSize(codecInfo, format, getStreamFormats());
codecNeedsDiscardChannelsWorkaround = codecNeedsDiscardChannelsWorkaround(codecInfo.name);
codecNeedsEosBufferTimestampWorkaround = codecNeedsEosBufferTimestampWorkaround(codecInfo.name);
passthroughEnabled = codecInfo.passthrough;
String codecMimeType = codecInfo.mimeType == null ? MimeTypes.AUDIO_RAW : codecInfo.mimeType;
MediaFormat mediaFormat =
......@@ -583,9 +586,9 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
lastInputTimeUs = Math.max(buffer.timeUs, lastInputTimeUs);
}
@CallSuper
@Override
protected void onProcessedOutputBuffer(long presentationTimeUs) {
super.onProcessedOutputBuffer(presentationTimeUs);
while (pendingStreamChangeCount != 0 && presentationTimeUs >= pendingStreamChangeTimesUs[0]) {
audioSink.handleDiscontinuity();
pendingStreamChangeCount--;
......@@ -610,6 +613,13 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
boolean shouldSkip,
Format format)
throws ExoPlaybackException {
if (codecNeedsEosBufferTimestampWorkaround
&& bufferPresentationTimeUs == 0
&& (bufferFlags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0
&& lastInputTimeUs != C.TIME_UNSET) {
bufferPresentationTimeUs = lastInputTimeUs;
}
if (passthroughEnabled && (bufferFlags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) {
// Discard output buffers from the passthrough (raw) decoder containing codec specific data.
codec.releaseOutputBuffer(bufferIndex, false);
......@@ -777,6 +787,24 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
|| Util.DEVICE.startsWith("heroqlte"));
}
/**
* Returns whether the decoder may output a non-empty buffer with timestamp 0 as the end of stream
* buffer.
*
* <p>See <a href="https://github.com/google/ExoPlayer/issues/5045">GitHub issue #5045</a>.
*/
private static boolean codecNeedsEosBufferTimestampWorkaround(String codecName) {
return Util.SDK_INT < 21
&& "OMX.SEC.mp3.dec".equals(codecName)
&& "samsung".equals(Util.MANUFACTURER)
&& (Util.DEVICE.startsWith("baffin")
|| Util.DEVICE.startsWith("grand")
|| Util.DEVICE.startsWith("fortuna")
|| Util.DEVICE.startsWith("gprimelte")
|| Util.DEVICE.startsWith("j2y18lte")
|| Util.DEVICE.startsWith("ms01"));
}
private final class AudioSinkListener implements AudioSink.Listener {
@Override
......
......@@ -19,6 +19,7 @@ import android.support.annotation.IntDef;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.util.Util;
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 SilenceSkippingAudioProcessor implements AudioProcessor {
private static final byte SILENCE_THRESHOLD_LEVEL_MSB = (SILENCE_THRESHOLD_LEVEL + 128) >> 8;
/** Trimming states. */
@Documented
@Retention(RetentionPolicy.SOURCE)
@IntDef({
STATE_NOISY,
......
......@@ -43,6 +43,7 @@ import com.google.android.exoplayer2.util.MediaClock;
import com.google.android.exoplayer2.util.MimeTypes;
import com.google.android.exoplayer2.util.TraceUtil;
import com.google.android.exoplayer2.util.Util;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
......@@ -65,9 +66,13 @@ import java.lang.annotation.RetentionPolicy;
*/
public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements MediaClock {
@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.
......
......@@ -17,6 +17,7 @@ package com.google.android.exoplayer2.decoder;
import android.support.annotation.IntDef;
import com.google.android.exoplayer2.C;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.nio.ByteBuffer;
......@@ -31,6 +32,7 @@ public class DecoderInputBuffer extends Buffer {
* #BUFFER_REPLACEMENT_MODE_DISABLED}, {@link #BUFFER_REPLACEMENT_MODE_NORMAL} or {@link
* #BUFFER_REPLACEMENT_MODE_DIRECT}.
*/
@Documented
@Retention(RetentionPolicy.SOURCE)
@IntDef({
BUFFER_REPLACEMENT_MODE_DISABLED,
......
......@@ -32,6 +32,7 @@ import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.EventDispatcher;
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.util.ArrayList;
......@@ -70,6 +71,7 @@ public class DefaultDrmSessionManager<T extends ExoMediaCrypto> implements DrmSe
* Determines the action to be done after a session acquired. One of {@link #MODE_PLAYBACK},
* {@link #MODE_QUERY}, {@link #MODE_DOWNLOAD} or {@link #MODE_RELEASE}.
*/
@Documented
@Retention(RetentionPolicy.SOURCE)
@IntDef({MODE_PLAYBACK, MODE_QUERY, MODE_DOWNLOAD, MODE_RELEASE})
public @interface Mode {}
......
......@@ -18,6 +18,7 @@ package com.google.android.exoplayer2.drm;
import android.annotation.TargetApi;
import android.media.MediaDrm;
import android.support.annotation.IntDef;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Map;
......@@ -43,6 +44,7 @@ public interface DrmSession<T extends ExoMediaCrypto> {
* The state of the DRM session. One of {@link #STATE_RELEASED}, {@link #STATE_ERROR}, {@link
* #STATE_OPENING}, {@link #STATE_OPENED} or {@link #STATE_OPENED_WITH_KEYS}.
*/
@Documented
@Retention(RetentionPolicy.SOURCE)
@IntDef({STATE_RELEASED, STATE_ERROR, STATE_OPENING, STATE_OPENED, STATE_OPENED_WITH_KEYS})
@interface State {}
......
......@@ -16,6 +16,7 @@
package com.google.android.exoplayer2.drm;
import android.support.annotation.IntDef;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
......@@ -28,6 +29,7 @@ public final class UnsupportedDrmException extends Exception {
* The reason for the exception. One of {@link #REASON_UNSUPPORTED_SCHEME} or {@link
* #REASON_INSTANTIATION_ERROR}.
*/
@Documented
@Retention(RetentionPolicy.SOURCE)
@IntDef({REASON_UNSUPPORTED_SCHEME, REASON_INSTANTIATION_ERROR})
public @interface Reason {}
......
......@@ -21,6 +21,7 @@ import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.util.Assertions;
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.nio.ByteBuffer;
......@@ -437,6 +438,7 @@ public abstract class BinarySearchSeeker {
public static final int RESULT_POSITION_UNDERESTIMATED = -2;
public static final int RESULT_NO_TIMESTAMP = -3;
@Documented
@Retention(RetentionPolicy.SOURCE)
@IntDef({
RESULT_TARGET_TIMESTAMP_FOUND,
......
......@@ -130,16 +130,16 @@ public final class DefaultExtractorInput implements ExtractorInput {
public boolean advancePeekPosition(int length, boolean allowEndOfInput)
throws IOException, InterruptedException {
ensureSpaceForPeek(length);
int bytesPeeked = Math.min(peekBufferLength - peekBufferPosition, length);
int bytesPeeked = peekBufferLength - peekBufferPosition;
while (bytesPeeked < length) {
bytesPeeked = readFromDataSource(peekBuffer, peekBufferPosition, length, bytesPeeked,
allowEndOfInput);
if (bytesPeeked == C.RESULT_END_OF_INPUT) {
return false;
}
peekBufferLength = peekBufferPosition + bytesPeeked;
}
peekBufferPosition += length;
peekBufferLength = Math.max(peekBufferLength, peekBufferPosition);
return true;
}
......
......@@ -18,6 +18,7 @@ package com.google.android.exoplayer2.extractor;
import android.support.annotation.IntDef;
import com.google.android.exoplayer2.C;
import java.io.IOException;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
......@@ -48,6 +49,7 @@ public interface Extractor {
* Result values that can be returned by {@link #read(ExtractorInput, PositionHolder)}. One of
* {@link #RESULT_CONTINUE}, {@link #RESULT_SEEK} or {@link #RESULT_END_OF_INPUT}.
*/
@Documented
@Retention(RetentionPolicy.SOURCE)
@IntDef(value = {RESULT_CONTINUE, RESULT_SEEK, RESULT_END_OF_INPUT})
@interface ReadResult {}
......
......@@ -18,7 +18,6 @@ package com.google.android.exoplayer2.extractor;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.metadata.Metadata;
import com.google.android.exoplayer2.metadata.id3.CommentFrame;
import com.google.android.exoplayer2.metadata.id3.Id3Decoder.FramePredicate;
import com.google.android.exoplayer2.metadata.id3.InternalFrame;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
......@@ -28,15 +27,6 @@ import java.util.regex.Pattern;
*/
public final class GaplessInfoHolder {
/**
* A {@link FramePredicate} suitable for use when decoding {@link Metadata} that will be passed to
* {@link #setFromMetadata(Metadata)}. Only frames that might contain gapless playback information
* are decoded.
*/
public static final FramePredicate GAPLESS_INFO_ID3_FRAME_PREDICATE =
(majorVersion, id0, id1, id2, id3) ->
id0 == 'C' && id1 == 'O' && id2 == 'M' && (id3 == 'M' || majorVersion == 2);
private static final String GAPLESS_DOMAIN = "com.apple.iTunes";
private static final String GAPLESS_DESCRIPTION = "iTunSMPB";
private static final Pattern GAPLESS_COMMENT_PATTERN =
......
......@@ -32,6 +32,7 @@ import com.google.android.exoplayer2.util.MimeTypes;
import com.google.android.exoplayer2.util.Util;
import java.io.EOFException;
import java.io.IOException;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Arrays;
......@@ -51,6 +52,7 @@ public final class AmrExtractor implements Extractor {
* Flags controlling the behavior of the extractor. Possible flag value is {@link
* #FLAG_ENABLE_CONSTANT_BITRATE_SEEKING}.
*/
@Documented
@Retention(RetentionPolicy.SOURCE)
@IntDef(
flag = true,
......
......@@ -26,6 +26,7 @@ import com.google.android.exoplayer2.extractor.SeekMap;
import com.google.android.exoplayer2.util.ParsableByteArray;
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;
......@@ -37,13 +38,17 @@ public final class FlvExtractor implements Extractor {
/** Factory for {@link FlvExtractor} instances. */
public static final ExtractorsFactory FACTORY = () -> new Extractor[] {new FlvExtractor()};
/**
* Extractor states.
*/
/** Extractor states. */
@Documented
@Retention(RetentionPolicy.SOURCE)
@IntDef({STATE_READING_FLV_HEADER, STATE_SKIPPING_TO_TAG_HEADER, STATE_READING_TAG_HEADER,
STATE_READING_TAG_DATA})
@IntDef({
STATE_READING_FLV_HEADER,
STATE_SKIPPING_TO_TAG_HEADER,
STATE_READING_TAG_HEADER,
STATE_READING_TAG_DATA
})
private @interface States {}
private static final int STATE_READING_FLV_HEADER = 1;
private static final int STATE_SKIPPING_TO_TAG_HEADER = 2;
private static final int STATE_READING_TAG_HEADER = 3;
......
......@@ -22,6 +22,7 @@ import com.google.android.exoplayer2.extractor.ExtractorInput;
import com.google.android.exoplayer2.util.Assertions;
import java.io.EOFException;
import java.io.IOException;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayDeque;
......@@ -31,6 +32,7 @@ import java.util.ArrayDeque;
*/
/* package */ final class DefaultEbmlReader implements EbmlReader {
@Documented
@Retention(RetentionPolicy.SOURCE)
@IntDef({ELEMENT_STATE_READ_ID, ELEMENT_STATE_READ_CONTENT_SIZE, ELEMENT_STATE_READ_CONTENT})
private @interface ElementState {}
......
......@@ -19,6 +19,7 @@ import android.support.annotation.IntDef;
import com.google.android.exoplayer2.ParserException;
import com.google.android.exoplayer2.extractor.ExtractorInput;
import java.io.IOException;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
......@@ -31,6 +32,7 @@ import java.lang.annotation.RetentionPolicy;
* EBML element types. One of {@link #TYPE_UNKNOWN}, {@link #TYPE_MASTER}, {@link
* #TYPE_UNSIGNED_INT}, {@link #TYPE_STRING}, {@link #TYPE_BINARY} or {@link #TYPE_FLOAT}.
*/
@Documented
@Retention(RetentionPolicy.SOURCE)
@IntDef({TYPE_UNKNOWN, TYPE_MASTER, TYPE_UNSIGNED_INT, TYPE_STRING, TYPE_BINARY, TYPE_FLOAT})
@interface ElementType {}
......
......@@ -45,6 +45,7 @@ import com.google.android.exoplayer2.video.AvcConfig;
import com.google.android.exoplayer2.video.ColorInfo;
import com.google.android.exoplayer2.video.HevcConfig;
import java.io.IOException;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.nio.ByteBuffer;
......@@ -68,6 +69,7 @@ public final class MatroskaExtractor implements Extractor {
* Flags controlling the behavior of the extractor. Possible flag value is {@link
* #FLAG_DISABLE_SEEK_FOR_CUES}.
*/
@Documented
@Retention(RetentionPolicy.SOURCE)
@IntDef(
flag = true,
......@@ -155,6 +157,7 @@ public final class MatroskaExtractor implements Extractor {
private static final int ID_FLAG_DEFAULT = 0x88;
private static final int ID_FLAG_FORCED = 0x55AA;
private static final int ID_DEFAULT_DURATION = 0x23E383;
private static final int ID_NAME = 0x536E;
private static final int ID_CODEC_ID = 0x86;
private static final int ID_CODEC_PRIVATE = 0x63A2;
private static final int ID_CODEC_DELAY = 0x56AA;
......@@ -813,6 +816,9 @@ public final class MatroskaExtractor implements Extractor {
throw new ParserException("DocType " + value + " not supported");
}
break;
case ID_NAME:
currentTrack.name = value;
break;
case ID_CODEC_ID:
currentTrack.codecId = value;
break;
......@@ -1461,6 +1467,7 @@ public final class MatroskaExtractor implements Extractor {
case ID_MAX_FALL:
return TYPE_UNSIGNED_INT;
case ID_DOC_TYPE:
case ID_NAME:
case ID_CODEC_ID:
case ID_LANGUAGE:
return TYPE_STRING;
......@@ -1607,6 +1614,7 @@ public final class MatroskaExtractor implements Extractor {
private static final int DEFAULT_MAX_FALL = 200; // nits.
// Common elements.
public String name;
public String codecId;
public int number;
public int type;
......@@ -1831,10 +1839,34 @@ public final class MatroskaExtractor implements Extractor {
byte[] hdrStaticInfo = getHdrStaticInfo();
colorInfo = new ColorInfo(colorSpace, colorRange, colorTransfer, hdrStaticInfo);
}
format = Format.createVideoSampleFormat(Integer.toString(trackId), mimeType, null,
Format.NO_VALUE, maxInputSize, width, height, Format.NO_VALUE, initializationData,
Format.NO_VALUE, pixelWidthHeightRatio, projectionData, stereoMode, colorInfo,
drmInitData);
int rotationDegrees = Format.NO_VALUE;
// Some HTC devices signal rotation in track names.
if ("htc_video_rotA-000".equals(name)) {
rotationDegrees = 0;
} else if ("htc_video_rotA-090".equals(name)) {
rotationDegrees = 90;
} else if ("htc_video_rotA-180".equals(name)) {
rotationDegrees = 180;
} else if ("htc_video_rotA-270".equals(name)) {
rotationDegrees = 270;
}
format =
Format.createVideoSampleFormat(
Integer.toString(trackId),
mimeType,
/* codecs= */ null,
/* bitrate= */ Format.NO_VALUE,
maxInputSize,
width,
height,
/* frameRate= */ Format.NO_VALUE,
initializationData,
rotationDegrees,
pixelWidthHeightRatio,
projectionData,
stereoMode,
colorInfo,
drmInitData);
} else if (MimeTypes.APPLICATION_SUBRIP.equals(mimeType)) {
type = C.TRACK_TYPE_TEXT;
format = Format.createTextSampleFormat(Integer.toString(trackId), mimeType, selectionFlags,
......
......@@ -39,4 +39,9 @@ import com.google.android.exoplayer2.extractor.MpegAudioHeader;
public long getTimeUs(long position) {
return getTimeUsAtPosition(position);
}
@Override
public long getDataEndPosition() {
return C.POSITION_UNSET;
}
}
/*
* 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.extractor.mp3;
import android.util.Pair;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.extractor.SeekPoint;
import com.google.android.exoplayer2.metadata.id3.MlltFrame;
import com.google.android.exoplayer2.util.Util;
/** MP3 seeker that uses metadata from an {@link MlltFrame}. */
/* package */ final class MlltSeeker implements Mp3Extractor.Seeker {
/**
* Returns an {@link MlltSeeker} for seeking in the stream.
*
* @param firstFramePosition The position of the start of the first frame in the stream.
* @param mlltFrame The MLLT frame with seeking metadata.
* @return An {@link MlltSeeker} for seeking in the stream.
*/
public static MlltSeeker create(long firstFramePosition, MlltFrame mlltFrame) {
int referenceCount = mlltFrame.bytesDeviations.length;
long[] referencePositions = new long[1 + referenceCount];
long[] referenceTimesMs = new long[1 + referenceCount];
referencePositions[0] = firstFramePosition;
referenceTimesMs[0] = 0;
long position = firstFramePosition;
long timeMs = 0;
for (int i = 1; i <= referenceCount; i++) {
position += mlltFrame.bytesBetweenReference + mlltFrame.bytesDeviations[i - 1];
timeMs += mlltFrame.millisecondsBetweenReference + mlltFrame.millisecondsDeviations[i - 1];
referencePositions[i] = position;
referenceTimesMs[i] = timeMs;
}
return new MlltSeeker(referencePositions, referenceTimesMs);
}
private final long[] referencePositions;
private final long[] referenceTimesMs;
private final long durationUs;
private MlltSeeker(long[] referencePositions, long[] referenceTimesMs) {
this.referencePositions = referencePositions;
this.referenceTimesMs = referenceTimesMs;
// Use the last reference point as the duration, as extrapolating variable bitrate at the end of
// the stream may give a large error.
durationUs = C.msToUs(referenceTimesMs[referenceTimesMs.length - 1]);
}
@Override
public boolean isSeekable() {
return true;
}
@Override
public SeekPoints getSeekPoints(long timeUs) {
timeUs = Util.constrainValue(timeUs, 0, durationUs);
Pair<Long, Long> timeMsAndPosition =
linearlyInterpolate(C.usToMs(timeUs), referenceTimesMs, referencePositions);
timeUs = C.msToUs(timeMsAndPosition.first);
long position = timeMsAndPosition.second;
return new SeekPoints(new SeekPoint(timeUs, position));
}
@Override
public long getTimeUs(long position) {
Pair<Long, Long> positionAndTimeMs =
linearlyInterpolate(position, referencePositions, referenceTimesMs);
return C.msToUs(positionAndTimeMs.second);
}
@Override
public long getDurationUs() {
return durationUs;
}
/**
* Given a set of reference points as coordinates in {@code xReferences} and {@code yReferences}
* and an x-axis value, linearly interpolates between corresponding reference points to give a
* y-axis value.
*
* @param x The x-axis value for which a y-axis value is needed.
* @param xReferences x coordinates of reference points.
* @param yReferences y coordinates of reference points.
* @return The linearly interpolated y-axis value.
*/
private static Pair<Long, Long> linearlyInterpolate(
long x, long[] xReferences, long[] yReferences) {
int previousReferenceIndex =
Util.binarySearchFloor(xReferences, x, /* inclusive= */ true, /* stayInBounds= */ true);
long xPreviousReference = xReferences[previousReferenceIndex];
long yPreviousReference = yReferences[previousReferenceIndex];
int nextReferenceIndex = previousReferenceIndex + 1;
if (nextReferenceIndex == xReferences.length) {
return Pair.create(xPreviousReference, yPreviousReference);
} else {
long xNextReference = xReferences[nextReferenceIndex];
long yNextReference = yReferences[nextReferenceIndex];
double proportion =
xNextReference == xPreviousReference
? 0.0
: ((double) x - xPreviousReference) / (xNextReference - xPreviousReference);
long y = (long) (proportion * (yNextReference - yPreviousReference)) + yPreviousReference;
return Pair.create(x, y);
}
}
@Override
public long getDataEndPosition() {
return C.POSITION_UNSET;
}
}
......@@ -16,6 +16,7 @@
package com.google.android.exoplayer2.extractor.mp3;
import android.support.annotation.IntDef;
import android.support.annotation.Nullable;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.ParserException;
......@@ -31,10 +32,13 @@ import com.google.android.exoplayer2.extractor.SeekMap;
import com.google.android.exoplayer2.extractor.TrackOutput;
import com.google.android.exoplayer2.metadata.Metadata;
import com.google.android.exoplayer2.metadata.id3.Id3Decoder;
import com.google.android.exoplayer2.metadata.id3.Id3Decoder.FramePredicate;
import com.google.android.exoplayer2.metadata.id3.MlltFrame;
import com.google.android.exoplayer2.util.ParsableByteArray;
import com.google.android.exoplayer2.util.Util;
import java.io.EOFException;
import java.io.IOException;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
......@@ -50,6 +54,7 @@ public final class Mp3Extractor implements Extractor {
* Flags controlling the behavior of the extractor. Possible flag values are {@link
* #FLAG_ENABLE_CONSTANT_BITRATE_SEEKING} and {@link #FLAG_DISABLE_ID3_METADATA}.
*/
@Documented
@Retention(RetentionPolicy.SOURCE)
@IntDef(
flag = true,
......@@ -66,6 +71,12 @@ public final class Mp3Extractor implements Extractor {
*/
public static final int FLAG_DISABLE_ID3_METADATA = 2;
/** Predicate that matches ID3 frames containing only required gapless/seeking metadata. */
private static final FramePredicate REQUIRED_ID3_FRAME_PREDICATE =
(majorVersion, id0, id1, id2, id3) ->
((id0 == 'C' && id1 == 'O' && id2 == 'M' && (id3 == 'M' || majorVersion == 2))
|| (id0 == 'M' && id1 == 'L' && id2 == 'L' && (id3 == 'T' || majorVersion == 2)));
/**
* The maximum number of bytes to search when synchronizing, before giving up.
*/
......@@ -172,7 +183,15 @@ public final class Mp3Extractor implements Extractor {
}
}
if (seeker == null) {
seeker = maybeReadSeekFrame(input);
// Read past any seek frame and set the seeker based on metadata or a seek frame. Metadata
// takes priority as it can provide greater precision.
Seeker seekFrameSeeker = maybeReadSeekFrame(input);
Seeker metadataSeeker = maybeHandleSeekMetadata(metadata, input.getPosition());
if (metadataSeeker != null) {
seeker = metadataSeeker;
} else if (seekFrameSeeker != null) {
seeker = seekFrameSeeker;
}
if (seeker == null
|| (!seeker.isSeekable() && (flags & FLAG_ENABLE_CONSTANT_BITRATE_SEEKING) != 0)) {
seeker = getConstantBitrateSeeker(input);
......@@ -204,7 +223,7 @@ public final class Mp3Extractor implements Extractor {
private int readSample(ExtractorInput extractorInput) throws IOException, InterruptedException {
if (sampleBytesRemaining == 0) {
extractorInput.resetPeekPosition();
if (!extractorInput.peekFully(scratch.data, 0, 4, true)) {
if (peekEndOfStreamOrHeader(extractorInput)) {
return RESULT_END_OF_INPUT;
}
scratch.setPosition(0);
......@@ -251,11 +270,11 @@ public final class Mp3Extractor implements Extractor {
int searchLimitBytes = sniffing ? MAX_SNIFF_BYTES : MAX_SYNC_BYTES;
input.resetPeekPosition();
if (input.getPosition() == 0) {
// We need to parse enough ID3 metadata to retrieve any gapless playback information even
// if ID3 metadata parsing is disabled.
boolean onlyDecodeGaplessInfoFrames = (flags & FLAG_DISABLE_ID3_METADATA) != 0;
// We need to parse enough ID3 metadata to retrieve any gapless/seeking playback information
// even if ID3 metadata parsing is disabled.
boolean parseAllId3Frames = (flags & FLAG_DISABLE_ID3_METADATA) == 0;
Id3Decoder.FramePredicate id3FramePredicate =
onlyDecodeGaplessInfoFrames ? GaplessInfoHolder.GAPLESS_INFO_ID3_FRAME_PREDICATE : null;
parseAllId3Frames ? null : REQUIRED_ID3_FRAME_PREDICATE;
metadata = id3Peeker.peekId3Data(input, id3FramePredicate);
if (metadata != null) {
gaplessInfoHolder.setFromMetadata(metadata);
......@@ -266,9 +285,12 @@ public final class Mp3Extractor implements Extractor {
}
}
while (true) {
if (!input.peekFully(scratch.data, 0, 4, validFrameCount > 0)) {
// We reached the end of the stream but found at least one valid frame.
break;
if (peekEndOfStreamOrHeader(input)) {
if (validFrameCount > 0) {
// We reached the end of the stream but found at least one valid frame.
break;
}
throw new EOFException();
}
scratch.setPosition(0);
int headerData = scratch.readInt();
......@@ -314,6 +336,17 @@ public final class Mp3Extractor implements Extractor {
}
/**
* Returns whether the extractor input is peeking the end of the stream. If {@code false},
* populates the scratch buffer with the next four bytes.
*/
private boolean peekEndOfStreamOrHeader(ExtractorInput extractorInput)
throws IOException, InterruptedException {
return (seeker != null && extractorInput.getPeekPosition() == seeker.getDataEndPosition())
|| !extractorInput.peekFully(
scratch.data, /* offset= */ 0, /* length= */ 4, /* allowEndOfInput= */ true);
}
/**
* Consumes the next frame from the {@code input} if it contains VBRI or Xing seeking metadata,
* returning a {@link Seeker} if the metadata was present and valid, or {@code null} otherwise.
* After this method returns, the input position is the start of the first frame of audio.
......@@ -399,9 +432,24 @@ public final class Mp3Extractor implements Extractor {
return SEEK_HEADER_UNSET;
}
@Nullable
private static MlltSeeker maybeHandleSeekMetadata(Metadata metadata, long firstFramePosition) {
if (metadata != null) {
int length = metadata.length();
for (int i = 0; i < length; i++) {
Metadata.Entry entry = metadata.get(i);
if (entry instanceof MlltFrame) {
return MlltSeeker.create(firstFramePosition, (MlltFrame) entry);
}
}
}
return null;
}
/**
* {@link SeekMap} that also allows mapping from position (byte offset) back to time, which can be
* used to work out the new sample basis timestamp after seeking and resynchronization.
* {@link SeekMap} that provides the end position of audio data and also allows mapping from
* position (byte offset) back to time, which can be used to work out the new sample basis
* timestamp after seeking and resynchronization.
*/
/* package */ interface Seeker extends SeekMap {
......@@ -413,6 +461,11 @@ public final class Mp3Extractor implements Extractor {
*/
long getTimeUs(long position);
/**
* Returns the position (byte offset) in the stream that is immediately after audio data, or
* {@link C#POSITION_UNSET} if not known.
*/
long getDataEndPosition();
}
}
......@@ -89,17 +89,19 @@ import com.google.android.exoplayer2.util.Util;
if (inputLength != C.LENGTH_UNSET && inputLength != position) {
Log.w(TAG, "VBRI data size mismatch: " + inputLength + ", " + position);
}
return new VbriSeeker(timesUs, positions, durationUs);
return new VbriSeeker(timesUs, positions, durationUs, /* dataEndPosition= */ position);
}
private final long[] timesUs;
private final long[] positions;
private final long durationUs;
private final long dataEndPosition;
private VbriSeeker(long[] timesUs, long[] positions, long durationUs) {
private VbriSeeker(long[] timesUs, long[] positions, long durationUs, long dataEndPosition) {
this.timesUs = timesUs;
this.positions = positions;
this.durationUs = durationUs;
this.dataEndPosition = dataEndPosition;
}
@Override
......@@ -129,4 +131,8 @@ import com.google.android.exoplayer2.util.Util;
return durationUs;
}
@Override
public long getDataEndPosition() {
return dataEndPosition;
}
}
......@@ -75,17 +75,17 @@ import com.google.android.exoplayer2.util.Util;
if (inputLength != C.LENGTH_UNSET && inputLength != position + dataSize) {
Log.w(TAG, "XING data size mismatch: " + inputLength + ", " + (position + dataSize));
}
return new XingSeeker(position, mpegAudioHeader.frameSize, durationUs, dataSize,
tableOfContents);
return new XingSeeker(
position, mpegAudioHeader.frameSize, durationUs, dataSize, tableOfContents);
}
private final long dataStartPosition;
private final int xingFrameSize;
private final long durationUs;
/**
* Data size, including the XING frame.
*/
/** Data size, including the XING frame. */
private final long dataSize;
private final long dataEndPosition;
/**
* Entries are in the range [0, 255], but are stored as long integers for convenience. Null if the
* table of contents was missing from the header, in which case seeking is not be supported.
......@@ -93,7 +93,12 @@ import com.google.android.exoplayer2.util.Util;
private final @Nullable long[] tableOfContents;
private XingSeeker(long dataStartPosition, int xingFrameSize, long durationUs) {
this(dataStartPosition, xingFrameSize, durationUs, C.LENGTH_UNSET, null);
this(
dataStartPosition,
xingFrameSize,
durationUs,
/* dataSize= */ C.LENGTH_UNSET,
/* tableOfContents= */ null);
}
private XingSeeker(
......@@ -105,8 +110,9 @@ import com.google.android.exoplayer2.util.Util;
this.dataStartPosition = dataStartPosition;
this.xingFrameSize = xingFrameSize;
this.durationUs = durationUs;
this.dataSize = dataSize;
this.tableOfContents = tableOfContents;
this.dataSize = dataSize;
dataEndPosition = dataSize == C.LENGTH_UNSET ? C.POSITION_UNSET : dataStartPosition + dataSize;
}
@Override
......@@ -166,6 +172,11 @@ import com.google.android.exoplayer2.util.Util;
return durationUs;
}
@Override
public long getDataEndPosition() {
return dataEndPosition;
}
/**
* Returns the time in microseconds for a given table index.
*
......
......@@ -221,11 +221,22 @@ import java.util.List;
for (int i = 0; i < sampleCount; i++) {
// Advance to the next chunk if necessary.
while (remainingSamplesInChunk == 0) {
Assertions.checkState(chunkIterator.moveNext());
boolean chunkDataComplete = true;
while (remainingSamplesInChunk == 0 && (chunkDataComplete = chunkIterator.moveNext())) {
offset = chunkIterator.offset;
remainingSamplesInChunk = chunkIterator.numSamples;
}
if (!chunkDataComplete) {
Log.w(TAG, "Unexpected end of chunk data");
sampleCount = i;
offsets = Arrays.copyOf(offsets, sampleCount);
sizes = Arrays.copyOf(sizes, sampleCount);
timestamps = Arrays.copyOf(timestamps, sampleCount);
flags = Arrays.copyOf(flags, sampleCount);
remainingSamplesAtTimestampOffset = 0;
remainingTimestampOffsetChanges = 0;
break;
}
// Add on the timestamp offset if ctts is present.
if (ctts != null) {
......
......@@ -43,6 +43,7 @@ import com.google.android.exoplayer2.util.ParsableByteArray;
import com.google.android.exoplayer2.util.TimestampAdjuster;
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.ArrayDeque;
......@@ -67,6 +68,7 @@ public final class FragmentedMp4Extractor implements Extractor {
* {@link #FLAG_ENABLE_EMSG_TRACK}, {@link #FLAG_SIDELOADED} and {@link
* #FLAG_WORKAROUND_IGNORE_EDIT_LISTS}.
*/
@Documented
@Retention(RetentionPolicy.SOURCE)
@IntDef(
flag = true,
......
......@@ -35,6 +35,7 @@ import com.google.android.exoplayer2.util.NalUnitUtil;
import com.google.android.exoplayer2.util.ParsableByteArray;
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.ArrayDeque;
......@@ -53,6 +54,7 @@ public final class Mp4Extractor implements Extractor, SeekMap {
* Flags controlling the behavior of the extractor. Possible flag value is {@link
* #FLAG_WORKAROUND_IGNORE_EDIT_LISTS}.
*/
@Documented
@Retention(RetentionPolicy.SOURCE)
@IntDef(
flag = true,
......@@ -63,12 +65,12 @@ public final class Mp4Extractor implements Extractor, SeekMap {
*/
public static final int FLAG_WORKAROUND_IGNORE_EDIT_LISTS = 1;
/**
* Parser states.
*/
/** Parser states. */
@Documented
@Retention(RetentionPolicy.SOURCE)
@IntDef({STATE_READING_ATOM_HEADER, STATE_READING_ATOM_PAYLOAD, STATE_READING_SAMPLE})
private @interface State {}
private static final int STATE_READING_ATOM_HEADER = 0;
private static final int STATE_READING_ATOM_PAYLOAD = 1;
private static final int STATE_READING_SAMPLE = 2;
......
......@@ -19,6 +19,7 @@ import android.support.annotation.IntDef;
import android.support.annotation.Nullable;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
......@@ -31,6 +32,7 @@ public final class Track {
* The transformation to apply to samples in the track, if any. One of {@link
* #TRANSFORMATION_NONE} or {@link #TRANSFORMATION_CEA608_CDAT}.
*/
@Documented
@Retention(RetentionPolicy.SOURCE)
@IntDef({TRANSFORMATION_NONE, TRANSFORMATION_CEA608_CDAT})
public @interface Transformation {}
......
......@@ -25,6 +25,7 @@ import com.google.android.exoplayer2.extractor.TrackOutput;
import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.TrackIdGenerator;
import com.google.android.exoplayer2.util.ParsableBitArray;
import com.google.android.exoplayer2.util.ParsableByteArray;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
......@@ -33,9 +34,11 @@ import java.lang.annotation.RetentionPolicy;
*/
public final class Ac3Reader implements ElementaryStreamReader {
@Documented
@Retention(RetentionPolicy.SOURCE)
@IntDef({STATE_FINDING_SYNC, STATE_READING_HEADER, STATE_READING_SAMPLE})
private @interface State {}
private static final int STATE_FINDING_SYNC = 0;
private static final int STATE_READING_HEADER = 1;
private static final int STATE_READING_SAMPLE = 2;
......
......@@ -32,6 +32,7 @@ import com.google.android.exoplayer2.util.ParsableBitArray;
import com.google.android.exoplayer2.util.ParsableByteArray;
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;
......@@ -47,6 +48,7 @@ public final class AdtsExtractor implements Extractor {
* Flags controlling the behavior of the extractor. Possible flag value is {@link
* #FLAG_ENABLE_CONSTANT_BITRATE_SEEKING}.
*/
@Documented
@Retention(RetentionPolicy.SOURCE)
@IntDef(
flag = true,
......
......@@ -22,6 +22,7 @@ import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.EsInfo;
import com.google.android.exoplayer2.text.cea.Cea708InitializationData;
import com.google.android.exoplayer2.util.MimeTypes;
import com.google.android.exoplayer2.util.ParsableByteArray;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
......@@ -39,6 +40,7 @@ public final class DefaultTsPayloadReaderFactory implements TsPayloadReader.Fact
* #FLAG_IGNORE_H264_STREAM}, {@link #FLAG_DETECT_ACCESS_UNITS}, {@link
* #FLAG_IGNORE_SPLICE_INFO_STREAM} and {@link #FLAG_OVERRIDE_CAPTION_DESCRIPTORS}.
*/
@Documented
@Retention(RetentionPolicy.SOURCE)
@IntDef(
flag = true,
......@@ -52,11 +54,37 @@ public final class DefaultTsPayloadReaderFactory implements TsPayloadReader.Fact
})
public @interface Flags {}
/**
* When extracting H.264 samples, whether to treat samples consisting of non-IDR I slices as
* synchronization samples (key-frames).
*/
public static final int FLAG_ALLOW_NON_IDR_KEYFRAMES = 1;
/**
* Prevents the creation of {@link AdtsReader} and {@link LatmReader} instances. This flag should
* be enabled if the transport stream contains no packets for an AAC elementary stream that is
* declared in the PMT.
*/
public static final int FLAG_IGNORE_AAC_STREAM = 1 << 1;
/**
* Prevents the creation of {@link H264Reader} instances. This flag should be enabled if the
* transport stream contains no packets for an H.264 elementary stream that is declared in the
* PMT.
*/
public static final int FLAG_IGNORE_H264_STREAM = 1 << 2;
/**
* When extracting H.264 samples, whether to split the input stream into access units (samples)
* based on slice headers. This flag should be disabled if the stream contains access unit
* delimiters (AUDs).
*/
public static final int FLAG_DETECT_ACCESS_UNITS = 1 << 3;
/** Prevents the creation of {@link SpliceInfoSectionReader} instances. */
public static final int FLAG_IGNORE_SPLICE_INFO_STREAM = 1 << 4;
/**
* Whether the list of {@code closedCaptionFormats} passed to {@link
* DefaultTsPayloadReaderFactory#DefaultTsPayloadReaderFactory(int, List)} should be used in spite
* of any closed captions service descriptors. If this flag is disabled, {@code
* closedCaptionFormats} will be ignored if the PMT contains closed captions service descriptors.
*/
public static final int FLAG_OVERRIDE_CAPTION_DESCRIPTORS = 1 << 5;
private static final int DESCRIPTOR_TAG_CAPTION_SERVICE = 0x86;
......
......@@ -38,6 +38,7 @@ import com.google.android.exoplayer2.util.ParsableByteArray;
import com.google.android.exoplayer2.util.TimestampAdjuster;
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;
......@@ -57,6 +58,7 @@ public final class TsExtractor implements Extractor {
* Modes for the extractor. One of {@link #MODE_MULTI_PMT}, {@link #MODE_SINGLE_PMT} or {@link
* #MODE_HLS}.
*/
@Documented
@Retention(RetentionPolicy.SOURCE)
@IntDef({MODE_MULTI_PMT, MODE_SINGLE_PMT, MODE_HLS})
public @interface Mode {}
......
......@@ -46,6 +46,7 @@ import com.google.android.exoplayer2.util.NalUnitUtil;
import com.google.android.exoplayer2.util.TimedValueQueue;
import com.google.android.exoplayer2.util.TraceUtil;
import com.google.android.exoplayer2.util.Util;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.nio.ByteBuffer;
......@@ -182,6 +183,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
* The possible return values for {@link #canKeepCodec(MediaCodec, MediaCodecInfo, Format,
* Format)}.
*/
@Documented
@Retention(RetentionPolicy.SOURCE)
@IntDef({
KEEP_CODEC_RESULT_NO,
......@@ -199,9 +201,13 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
*/
protected static final int KEEP_CODEC_RESULT_YES_WITH_RECONFIGURATION = 3;
@Documented
@Retention(RetentionPolicy.SOURCE)
@IntDef({RECONFIGURATION_STATE_NONE, RECONFIGURATION_STATE_WRITE_PENDING,
RECONFIGURATION_STATE_QUEUE_PENDING})
@IntDef({
RECONFIGURATION_STATE_NONE,
RECONFIGURATION_STATE_WRITE_PENDING,
RECONFIGURATION_STATE_QUEUE_PENDING
})
private @interface ReconfigurationState {}
/**
* There is no pending adaptive reconfiguration work.
......@@ -217,9 +223,13 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
*/
private static final int RECONFIGURATION_STATE_QUEUE_PENDING = 2;
@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 codec does not need to be re-initialized.
......@@ -238,9 +248,13 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
*/
private static final int REINITIALIZATION_STATE_WAIT_END_OF_STREAM = 2;
@Documented
@Retention(RetentionPolicy.SOURCE)
@IntDef({ADAPTATION_WORKAROUND_MODE_NEVER, ADAPTATION_WORKAROUND_MODE_SAME_RESOLUTION,
ADAPTATION_WORKAROUND_MODE_ALWAYS})
@IntDef({
ADAPTATION_WORKAROUND_MODE_NEVER,
ADAPTATION_WORKAROUND_MODE_SAME_RESOLUTION,
ADAPTATION_WORKAROUND_MODE_ALWAYS
})
private @interface AdaptationWorkaroundMode {}
/**
* The adaptation workaround is never used.
......
......@@ -21,6 +21,7 @@ import com.google.android.exoplayer2.metadata.Metadata;
import com.google.android.exoplayer2.metadata.MetadataDecoder;
import com.google.android.exoplayer2.metadata.MetadataInputBuffer;
import com.google.android.exoplayer2.util.Log;
import com.google.android.exoplayer2.util.ParsableBitArray;
import com.google.android.exoplayer2.util.ParsableByteArray;
import com.google.android.exoplayer2.util.Util;
import java.io.UnsupportedEncodingException;
......@@ -382,6 +383,8 @@ public final class Id3Decoder implements MetadataDecoder {
} else if (frameId0 == 'C' && frameId1 == 'T' && frameId2 == 'O' && frameId3 == 'C') {
frame = decodeChapterTOCFrame(id3Data, frameSize, majorVersion, unsignedIntFrameSizeHack,
frameHeaderSize, framePredicate);
} else if (frameId0 == 'M' && frameId1 == 'L' && frameId2 == 'L' && frameId3 == 'T') {
frame = decodeMlltFrame(id3Data, frameSize);
} else {
String id = getFrameId(majorVersion, frameId0, frameId1, frameId2, frameId3);
frame = decodeBinaryFrame(id3Data, frameSize, id);
......@@ -662,6 +665,36 @@ public final class Id3Decoder implements MetadataDecoder {
return new ChapterTocFrame(elementId, isRoot, isOrdered, children, subFrameArray);
}
private static MlltFrame decodeMlltFrame(ParsableByteArray id3Data, int frameSize) {
// See ID3v2.4.0 native frames subsection 4.6.
int mpegFramesBetweenReference = id3Data.readUnsignedShort();
int bytesBetweenReference = id3Data.readUnsignedInt24();
int millisecondsBetweenReference = id3Data.readUnsignedInt24();
int bitsForBytesDeviation = id3Data.readUnsignedByte();
int bitsForMillisecondsDeviation = id3Data.readUnsignedByte();
ParsableBitArray references = new ParsableBitArray();
references.reset(id3Data);
int referencesBits = 8 * (frameSize - 10);
int bitsPerReference = bitsForBytesDeviation + bitsForMillisecondsDeviation;
int referencesCount = referencesBits / bitsPerReference;
int[] bytesDeviations = new int[referencesCount];
int[] millisecondsDeviations = new int[referencesCount];
for (int i = 0; i < referencesCount; i++) {
int bytesDeviation = references.readBits(bitsForBytesDeviation);
int millisecondsDeviation = references.readBits(bitsForMillisecondsDeviation);
bytesDeviations[i] = bytesDeviation;
millisecondsDeviations[i] = millisecondsDeviation;
}
return new MlltFrame(
mpegFramesBetweenReference,
bytesBetweenReference,
millisecondsBetweenReference,
bytesDeviations,
millisecondsDeviations);
}
private static BinaryFrame decodeBinaryFrame(ParsableByteArray id3Data, int frameSize,
String id) {
byte[] frame = new byte[frameSize];
......
/*
* 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.metadata.id3;
import android.os.Parcel;
import android.support.annotation.Nullable;
import java.util.Arrays;
/** MPEG location lookup table frame. */
public final class MlltFrame extends Id3Frame {
public static final String ID = "MLLT";
public final int mpegFramesBetweenReference;
public final int bytesBetweenReference;
public final int millisecondsBetweenReference;
public final int[] bytesDeviations;
public final int[] millisecondsDeviations;
public MlltFrame(
int mpegFramesBetweenReference,
int bytesBetweenReference,
int millisecondsBetweenReference,
int[] bytesDeviations,
int[] millisecondsDeviations) {
super(ID);
this.mpegFramesBetweenReference = mpegFramesBetweenReference;
this.bytesBetweenReference = bytesBetweenReference;
this.millisecondsBetweenReference = millisecondsBetweenReference;
this.bytesDeviations = bytesDeviations;
this.millisecondsDeviations = millisecondsDeviations;
}
/* package */ MlltFrame(Parcel in) {
super(ID);
this.mpegFramesBetweenReference = in.readInt();
this.bytesBetweenReference = in.readInt();
this.millisecondsBetweenReference = in.readInt();
this.bytesDeviations = in.createIntArray();
this.millisecondsDeviations = in.createIntArray();
}
@Override
public boolean equals(@Nullable Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
MlltFrame other = (MlltFrame) obj;
return mpegFramesBetweenReference == other.mpegFramesBetweenReference
&& bytesBetweenReference == other.bytesBetweenReference
&& millisecondsBetweenReference == other.millisecondsBetweenReference
&& Arrays.equals(bytesDeviations, other.bytesDeviations)
&& Arrays.equals(millisecondsDeviations, other.millisecondsDeviations);
}
@Override
public int hashCode() {
int result = 17;
result = 31 * result + mpegFramesBetweenReference;
result = 31 * result + bytesBetweenReference;
result = 31 * result + millisecondsBetweenReference;
result = 31 * result + Arrays.hashCode(bytesDeviations);
result = 31 * result + Arrays.hashCode(millisecondsDeviations);
return result;
}
// Parcelable implementation.
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(mpegFramesBetweenReference);
dest.writeInt(bytesBetweenReference);
dest.writeInt(millisecondsBetweenReference);
dest.writeIntArray(bytesDeviations);
dest.writeIntArray(millisecondsDeviations);
}
@Override
public int describeContents() {
return 0;
}
public static final Creator<MlltFrame> CREATOR =
new Creator<MlltFrame>() {
@Override
public MlltFrame createFromParcel(Parcel in) {
return new MlltFrame(in);
}
@Override
public MlltFrame[] newArray(int size) {
return new MlltFrame[size];
}
};
}
......@@ -37,6 +37,7 @@ import com.google.android.exoplayer2.util.Util;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
......@@ -532,6 +533,7 @@ public final class DownloadManager {
* -&gt; failed
* </pre>
*/
@Documented
@Retention(RetentionPolicy.SOURCE)
@IntDef({STATE_QUEUED, STATE_STARTED, STATE_COMPLETED, STATE_CANCELED, STATE_FAILED})
public @interface State {}
......@@ -621,6 +623,7 @@ public final class DownloadManager {
* +-----------+------+-------+---------+-----------+-----------+--------+--------+------+
* </pre>
*/
@Documented
@Retention(RetentionPolicy.SOURCE)
@IntDef({
STATE_QUEUED,
......
......@@ -27,6 +27,7 @@ import android.os.PowerManager;
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;
......@@ -39,6 +40,7 @@ public final class Requirements {
* Network types. One of {@link #NETWORK_TYPE_NONE}, {@link #NETWORK_TYPE_ANY}, {@link
* #NETWORK_TYPE_UNMETERED}, {@link #NETWORK_TYPE_NOT_ROAMING} or {@link #NETWORK_TYPE_METERED}.
*/
@Documented
@Retention(RetentionPolicy.SOURCE)
@IntDef({
NETWORK_TYPE_NONE,
......@@ -185,7 +187,7 @@ public final class Requirements {
}
PowerManager powerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
return Util.SDK_INT >= 23
? !powerManager.isDeviceIdleMode()
? powerManager.isDeviceIdleMode()
: Util.SDK_INT >= 20 ? !powerManager.isInteractive() : !powerManager.isScreenOn();
}
......
......@@ -24,6 +24,7 @@ import com.google.android.exoplayer2.upstream.Allocator;
import com.google.android.exoplayer2.upstream.TransferListener;
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;
import java.util.ArrayList;
......@@ -41,6 +42,7 @@ public final class ClippingMediaSource extends CompositeMediaSource<Void> {
* The reason clipping failed. One of {@link #REASON_INVALID_PERIOD_COUNT}, {@link
* #REASON_NOT_SEEKABLE_TO_START} or {@link #REASON_START_EXCEEDS_END}.
*/
@Documented
@Retention(RetentionPolicy.SOURCE)
@IntDef({REASON_INVALID_PERIOD_COUNT, REASON_NOT_SEEKABLE_TO_START, REASON_START_EXCEEDS_END})
public @interface Reason {}
......@@ -346,7 +348,7 @@ public final class ClippingMediaSource extends CompositeMediaSource<Void> {
if (timeline.getPeriodCount() != 1) {
throw new IllegalClippingException(IllegalClippingException.REASON_INVALID_PERIOD_COUNT);
}
Window window = timeline.getWindow(0, new Window(), false);
Window window = timeline.getWindow(0, new Window());
startUs = Math.max(0, startUs);
long resolvedEndUs = endUs == C.TIME_END_OF_SOURCE ? window.durationUs : Math.max(0, endUs);
if (window.durationUs != C.TIME_UNSET) {
......
......@@ -18,6 +18,7 @@ package com.google.android.exoplayer2.source;
import android.os.Handler;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.Pair;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ExoPlaybackException;
import com.google.android.exoplayer2.ExoPlayer;
......@@ -65,6 +66,7 @@ public class ConcatenatingMediaSource extends CompositeMediaSource<MediaSourceHo
private final boolean isAtomic;
private final boolean useLazyPreparation;
private final Timeline.Window window;
private final Timeline.Period period;
private @Nullable ExoPlayer player;
private @Nullable Handler playerApplicationHandler;
......@@ -131,6 +133,7 @@ public class ConcatenatingMediaSource extends CompositeMediaSource<MediaSourceHo
this.isAtomic = isAtomic;
this.useLazyPreparation = useLazyPreparation;
window = new Timeline.Window();
period = new Timeline.Period();
addMediaSources(Arrays.asList(mediaSources));
}
......@@ -499,9 +502,7 @@ public class ConcatenatingMediaSource extends CompositeMediaSource<MediaSourceHo
Assertions.checkNotNull(mediaSourceByMediaPeriod.remove(mediaPeriod));
((DeferredMediaPeriod) mediaPeriod).releasePeriod();
holder.activeMediaPeriods.remove(mediaPeriod);
if (holder.activeMediaPeriods.isEmpty() && holder.isRemoved) {
releaseChildSource(holder);
}
maybeReleaseChildSource(holder);
}
@Override
......@@ -684,21 +685,53 @@ public class ConcatenatingMediaSource extends CompositeMediaSource<MediaSourceHo
windowOffsetUpdate,
periodOffsetUpdate);
}
mediaSourceHolder.timeline = deferredTimeline.cloneWithNewTimeline(timeline);
if (!mediaSourceHolder.isPrepared && !timeline.isEmpty()) {
timeline.getWindow(/* windowIndex= */ 0, window);
long defaultPeriodPositionUs =
window.getPositionInFirstPeriodUs() + window.getDefaultPositionUs();
for (int i = 0; i < mediaSourceHolder.activeMediaPeriods.size(); i++) {
DeferredMediaPeriod deferredMediaPeriod = mediaSourceHolder.activeMediaPeriods.get(i);
deferredMediaPeriod.setDefaultPreparePositionUs(defaultPeriodPositionUs);
if (mediaSourceHolder.isPrepared) {
mediaSourceHolder.timeline = deferredTimeline.cloneWithUpdatedTimeline(timeline);
} else if (timeline.isEmpty()) {
mediaSourceHolder.timeline =
DeferredTimeline.createWithRealTimeline(timeline, DeferredTimeline.DUMMY_ID);
} else {
// We should have at most one deferred media period for the DummyTimeline because the duration
// is unset and we don't load beyond periods with unset duration. We need to figure out how to
// handle the prepare positions of multiple deferred media periods, should that ever change.
Assertions.checkState(mediaSourceHolder.activeMediaPeriods.size() <= 1);
DeferredMediaPeriod deferredMediaPeriod =
mediaSourceHolder.activeMediaPeriods.isEmpty()
? null
: mediaSourceHolder.activeMediaPeriods.get(0);
// Determine first period and the start position.
// This will be:
// 1. The default window start position if no deferred period has been created yet.
// 2. The non-zero prepare position of the deferred period under the assumption that this is
// a non-zero initial seek position in the window.
// 3. The default window start position if the deferred period has a prepare position of zero
// under the assumption that the prepare position of zero was used because it's the
// default position of the DummyTimeline window. Note that this will override an
// intentional seek to zero for a window with a non-zero default position. This is
// unlikely to be a problem as a non-zero default position usually only occurs for live
// playbacks and seeking to zero in a live window would cause BehindLiveWindowExceptions
// anyway.
long windowStartPositionUs = window.getDefaultPositionUs();
if (deferredMediaPeriod != null) {
long periodPreparePositionUs = deferredMediaPeriod.getPreparePositionUs();
if (periodPreparePositionUs != 0) {
windowStartPositionUs = periodPreparePositionUs;
}
}
Pair<Object, Long> periodPosition =
timeline.getPeriodPosition(window, period, /* windowIndex= */ 0, windowStartPositionUs);
Object periodUid = periodPosition.first;
long periodPositionUs = periodPosition.second;
mediaSourceHolder.timeline = DeferredTimeline.createWithRealTimeline(timeline, periodUid);
if (deferredMediaPeriod != null) {
deferredMediaPeriod.overridePreparePositionUs(periodPositionUs);
MediaPeriodId idInSource =
deferredMediaPeriod.id.copyWithPeriodUid(
getChildPeriodUid(mediaSourceHolder, deferredMediaPeriod.id.periodUid));
deferredMediaPeriod.createPeriod(idInSource);
}
mediaSourceHolder.isPrepared = true;
}
mediaSourceHolder.isPrepared = true;
scheduleListenerNotification(/* actionOnCompletion= */ null);
}
......@@ -712,9 +745,7 @@ public class ConcatenatingMediaSource extends CompositeMediaSource<MediaSourceHo
-oldTimeline.getWindowCount(),
-oldTimeline.getPeriodCount());
holder.isRemoved = true;
if (holder.activeMediaPeriods.isEmpty()) {
releaseChildSource(holder);
}
maybeReleaseChildSource(holder);
}
private void moveMediaSourceInternal(int currentIndex, int newIndex) {
......@@ -743,6 +774,16 @@ public class ConcatenatingMediaSource extends CompositeMediaSource<MediaSourceHo
}
}
private void maybeReleaseChildSource(MediaSourceHolder mediaSourceHolder) {
// Release if the source has been removed from the playlist, but only if it has been previously
// prepared and only if we are not waiting for an existing media period to be released.
if (mediaSourceHolder.isRemoved
&& mediaSourceHolder.hasStartedPreparing
&& mediaSourceHolder.activeMediaPeriods.isEmpty()) {
releaseChildSource(mediaSourceHolder);
}
}
/** Return uid of media source holder from period uid of concatenated source. */
private static Object getMediaSourceHolderUid(Object periodUid) {
return ConcatenatedTimeline.getChildTimelineUidFromConcatenatedUid(periodUid);
......@@ -897,18 +938,32 @@ public class ConcatenatingMediaSource extends CompositeMediaSource<MediaSourceHo
}
/**
* Timeline used as placeholder for an unprepared media source. After preparation, a copy of the
* DeferredTimeline is used to keep the originally assigned first period ID.
* Timeline used as placeholder for an unprepared media source. After preparation, a
* DeferredTimeline is used to keep the originally assigned dummy period ID.
*/
private static final class DeferredTimeline extends ForwardingTimeline {
private static final Object DUMMY_ID = new Object();
private static final DummyTimeline dummyTimeline = new DummyTimeline();
private static final DummyTimeline DUMMY_TIMELINE = new DummyTimeline();
private final Object replacedId;
/**
* Returns an instance with a real timeline, replacing the provided period ID with the already
* assigned dummy period ID.
*
* @param timeline The real timeline.
* @param firstPeriodUid The period UID in the timeline which will be replaced by the already
* assigned dummy period UID.
*/
public static DeferredTimeline createWithRealTimeline(
Timeline timeline, Object firstPeriodUid) {
return new DeferredTimeline(timeline, firstPeriodUid);
}
/** Creates deferred timeline exposing a {@link DummyTimeline}. */
public DeferredTimeline() {
this(dummyTimeline, DUMMY_ID);
this(DUMMY_TIMELINE, DUMMY_ID);
}
private DeferredTimeline(Timeline timeline, Object replacedId) {
......@@ -916,14 +971,16 @@ public class ConcatenatingMediaSource extends CompositeMediaSource<MediaSourceHo
this.replacedId = replacedId;
}
public DeferredTimeline cloneWithNewTimeline(Timeline timeline) {
return new DeferredTimeline(
timeline,
replacedId == DUMMY_ID && timeline.getPeriodCount() > 0
? timeline.getUidOfPeriod(0)
: replacedId);
/**
* Returns a copy with an updated timeline. This keeps the existing period replacement.
*
* @param timeline The new timeline.
*/
public DeferredTimeline cloneWithUpdatedTimeline(Timeline timeline) {
return new DeferredTimeline(timeline, replacedId);
}
/** Returns wrapped timeline. */
public Timeline getTimeline() {
return timeline;
}
......
......@@ -79,23 +79,19 @@ public final class DeferredMediaPeriod implements MediaPeriod, MediaPeriod.Callb
this.listener = listener;
}
/** Returns the position at which the deferred media period was prepared, in microseconds. */
public long getPreparePositionUs() {
return preparePositionUs;
}
/**
* Sets the default prepare position at which to prepare the media period. This value is only used
* if the call to {@link MediaPeriod#prepare(Callback, long)} is being deferred and the call was
* made with a (presumably default) prepare position of 0.
* Overrides the default prepare position at which to prepare the media period. This value is only
* used if the call to {@link MediaPeriod#prepare(Callback, long)} is being deferred.
*
* <p>Note that this will override an intentional seek to zero in the corresponding non-seekable
* timeline window. This is unlikely to be a problem as a non-zero default position usually only
* occurs for live playbacks and seeking to zero in a live window would cause
* BehindLiveWindowExceptions anyway.
*
* @param defaultPreparePositionUs The actual default prepare position, in microseconds.
* @param defaultPreparePositionUs The default prepare position to use, in microseconds.
*/
public void setDefaultPreparePositionUs(long defaultPreparePositionUs) {
if (preparePositionUs == 0 && defaultPreparePositionUs != 0) {
preparePositionOverrideUs = defaultPreparePositionUs;
preparePositionUs = defaultPreparePositionUs;
}
public void overridePreparePositionUs(long defaultPreparePositionUs) {
preparePositionOverrideUs = defaultPreparePositionUs;
}
/**
......@@ -108,6 +104,10 @@ public final class DeferredMediaPeriod implements MediaPeriod, MediaPeriod.Callb
public void createPeriod(MediaPeriodId id) {
mediaPeriod = mediaSource.createPeriod(id, allocator);
if (callback != null) {
long preparePositionUs =
preparePositionOverrideUs != C.TIME_UNSET
? preparePositionOverrideUs
: this.preparePositionUs;
mediaPeriod.prepare(this, preparePositionUs);
}
}
......@@ -157,7 +157,7 @@ public final class DeferredMediaPeriod implements MediaPeriod, MediaPeriod.Callb
@Override
public long selectTracks(TrackSelection[] selections, boolean[] mayRetainStreamFlags,
SampleStream[] streams, boolean[] streamResetFlags, long positionUs) {
if (preparePositionOverrideUs != C.TIME_UNSET && positionUs == 0) {
if (preparePositionOverrideUs != C.TIME_UNSET && positionUs == preparePositionUs) {
positionUs = preparePositionOverrideUs;
preparePositionOverrideUs = C.TIME_UNSET;
}
......
......@@ -22,6 +22,7 @@ import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.upstream.Allocator;
import com.google.android.exoplayer2.upstream.TransferListener;
import java.io.IOException;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
......@@ -41,6 +42,7 @@ public final class MergingMediaSource extends CompositeMediaSource<Integer> {
public static final class IllegalMergeException extends IOException {
/** The reason the merge failed. One of {@link #REASON_PERIOD_COUNT_MISMATCH}. */
@Documented
@Retention(RetentionPolicy.SOURCE)
@IntDef({REASON_PERIOD_COUNT_MISMATCH})
public @interface Reason {}
......
......@@ -20,6 +20,7 @@ import android.support.annotation.CheckResult;
import android.support.annotation.IntDef;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.util.Assertions;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Arrays;
......@@ -239,6 +240,7 @@ public final class AdPlaybackState {
* #AD_STATE_AVAILABLE}, {@link #AD_STATE_SKIPPED}, {@link #AD_STATE_PLAYED} or {@link
* #AD_STATE_ERROR}.
*/
@Documented
@Retention(RetentionPolicy.SOURCE)
@IntDef({
AD_STATE_UNAVAILABLE,
......
......@@ -39,6 +39,7 @@ import com.google.android.exoplayer2.upstream.DataSpec;
import com.google.android.exoplayer2.upstream.TransferListener;
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;
import java.util.ArrayList;
......@@ -87,6 +88,7 @@ public final class AdsMediaSource extends CompositeMediaSource<MediaPeriodId> {
* Types of ad load exceptions. One of {@link #TYPE_AD}, {@link #TYPE_AD_GROUP}, {@link
* #TYPE_ALL_ADS} or {@link #TYPE_UNEXPECTED}.
*/
@Documented
@Retention(RetentionPolicy.SOURCE)
@IntDef({TYPE_AD, TYPE_AD_GROUP, TYPE_ALL_ADS, TYPE_UNEXPECTED})
public @interface Type {}
......
......@@ -308,7 +308,7 @@ public class ChunkSampleStream<T extends ChunkSource> implements SampleStream, S
// chunk even if the sample timestamps are slightly offset from the chunk start times.
seekInsideBuffer =
primarySampleQueue.setReadPosition(seekToMediaChunk.getFirstSampleIndex(0));
decodeOnlyUntilPositionUs = Long.MIN_VALUE;
decodeOnlyUntilPositionUs = 0;
} else {
seekInsideBuffer =
primarySampleQueue.advanceTo(
......@@ -583,7 +583,7 @@ public class ChunkSampleStream<T extends ChunkSource> implements SampleStream, S
if (pendingReset) {
boolean resetToMediaChunk = mediaChunk.startTimeUs == pendingResetPositionUs;
// Only enable setting of the decode only flag if we're not resetting to a chunk boundary.
decodeOnlyUntilPositionUs = resetToMediaChunk ? Long.MIN_VALUE : pendingResetPositionUs;
decodeOnlyUntilPositionUs = resetToMediaChunk ? 0 : pendingResetPositionUs;
pendingResetPositionUs = C.TIME_UNSET;
}
mediaChunk.init(mediaChunkOutput);
......
......@@ -22,6 +22,7 @@ import android.support.annotation.IntDef;
import android.view.accessibility.CaptioningManager;
import android.view.accessibility.CaptioningManager.CaptionStyle;
import com.google.android.exoplayer2.util.Util;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
......@@ -35,6 +36,7 @@ public final class CaptionStyleCompat {
* #EDGE_TYPE_OUTLINE}, {@link #EDGE_TYPE_DROP_SHADOW}, {@link #EDGE_TYPE_RAISED} or {@link
* #EDGE_TYPE_DEPRESSED}.
*/
@Documented
@Retention(RetentionPolicy.SOURCE)
@IntDef({
EDGE_TYPE_NONE,
......
......@@ -19,6 +19,7 @@ import android.graphics.Bitmap;
import android.graphics.Color;
import android.support.annotation.IntDef;
import android.text.Layout.Alignment;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
......@@ -36,6 +37,7 @@ public class Cue {
* The type of anchor, which may be unset. One of {@link #TYPE_UNSET}, {@link #ANCHOR_TYPE_START},
* {@link #ANCHOR_TYPE_MIDDLE} or {@link #ANCHOR_TYPE_END}.
*/
@Documented
@Retention(RetentionPolicy.SOURCE)
@IntDef({TYPE_UNSET, ANCHOR_TYPE_START, ANCHOR_TYPE_MIDDLE, ANCHOR_TYPE_END})
public @interface AnchorType {}
......@@ -66,6 +68,7 @@ public class Cue {
* The type of line, which may be unset. One of {@link #TYPE_UNSET}, {@link #LINE_TYPE_FRACTION}
* or {@link #LINE_TYPE_NUMBER}.
*/
@Documented
@Retention(RetentionPolicy.SOURCE)
@IntDef({TYPE_UNSET, LINE_TYPE_FRACTION, LINE_TYPE_NUMBER})
public @interface LineType {}
......@@ -85,6 +88,7 @@ public class Cue {
* {@link #TEXT_SIZE_TYPE_FRACTIONAL}, {@link #TEXT_SIZE_TYPE_FRACTIONAL_IGNORE_PADDING} or {@link
* #TEXT_SIZE_TYPE_ABSOLUTE}.
*/
@Documented
@Retention(RetentionPolicy.SOURCE)
@IntDef({
TYPE_UNSET,
......
......@@ -29,6 +29,7 @@ import com.google.android.exoplayer2.FormatHolder;
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.MimeTypes;
import com.google.android.exoplayer2.util.Util;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Collections;
......@@ -49,9 +50,13 @@ public final class TextRenderer extends BaseRenderer implements Callback {
@Deprecated
public interface Output extends TextOutput {}
@Documented
@Retention(RetentionPolicy.SOURCE)
@IntDef({REPLACEMENT_STATE_NONE, REPLACEMENT_STATE_SIGNAL_END_OF_STREAM,
REPLACEMENT_STATE_WAIT_END_OF_STREAM})
@IntDef({
REPLACEMENT_STATE_NONE,
REPLACEMENT_STATE_SIGNAL_END_OF_STREAM,
REPLACEMENT_STATE_WAIT_END_OF_STREAM
})
private @interface ReplacementState {}
/**
* The decoder does not need to be replaced.
......
......@@ -272,7 +272,10 @@ public final class Cea708Decoder extends CeaDecoder {
if (serviceNumber == 7) {
// extended service numbers
serviceBlockPacket.skipBits(2);
serviceNumber += serviceBlockPacket.readBits(6);
serviceNumber = serviceBlockPacket.readBits(6);
if (serviceNumber < 7) {
Log.w(TAG, "Invalid extended service number: " + serviceNumber);
}
}
// Ignore packets in which blockSize is 0
......
......@@ -15,6 +15,7 @@
*/
package com.google.android.exoplayer2.text.subrip;
import android.support.annotation.Nullable;
import android.text.Html;
import android.text.Spanned;
import android.text.TextUtils;
......@@ -32,17 +33,38 @@ import java.util.regex.Pattern;
*/
public final class SubripDecoder extends SimpleSubtitleDecoder {
// Fractional positions for use when alignment tags are present.
/* package */ static final float START_FRACTION = 0.08f;
/* package */ static final float END_FRACTION = 1 - START_FRACTION;
/* package */ static final float MID_FRACTION = 0.5f;
private static final String TAG = "SubripDecoder";
private static final String SUBRIP_TIMECODE = "(?:(\\d+):)?(\\d+):(\\d+),(\\d+)";
private static final Pattern SUBRIP_TIMING_LINE =
Pattern.compile("\\s*(" + SUBRIP_TIMECODE + ")\\s*-->\\s*(" + SUBRIP_TIMECODE + ")?\\s*");
private static final Pattern SUBRIP_TAG_PATTERN = Pattern.compile("\\{\\\\.*?\\}");
private static final String SUBRIP_ALIGNMENT_TAG = "\\{\\\\an[1-9]\\}";
// Alignment tags for SSA V4+.
private static final String ALIGN_BOTTOM_LEFT = "{\\an1}";
private static final String ALIGN_BOTTOM_MID = "{\\an2}";
private static final String ALIGN_BOTTOM_RIGHT = "{\\an3}";
private static final String ALIGN_MID_LEFT = "{\\an4}";
private static final String ALIGN_MID_MID = "{\\an5}";
private static final String ALIGN_MID_RIGHT = "{\\an6}";
private static final String ALIGN_TOP_LEFT = "{\\an7}";
private static final String ALIGN_TOP_MID = "{\\an8}";
private static final String ALIGN_TOP_RIGHT = "{\\an9}";
private final StringBuilder textBuilder;
private final ArrayList<String> tags;
public SubripDecoder() {
super("SubripDecoder");
textBuilder = new StringBuilder();
tags = new ArrayList<>();
}
@Override
......@@ -86,17 +108,29 @@ public final class SubripDecoder extends SimpleSubtitleDecoder {
continue;
}
// Read and parse the text.
// Read and parse the text and tags.
textBuilder.setLength(0);
tags.clear();
while (!TextUtils.isEmpty(currentLine = subripData.readLine())) {
if (textBuilder.length() > 0) {
textBuilder.append("<br>");
}
textBuilder.append(currentLine.trim());
textBuilder.append(processLine(currentLine, tags));
}
Spanned text = Html.fromHtml(textBuilder.toString());
cues.add(new Cue(text));
String alignmentTag = null;
for (int i = 0; i < tags.size(); i++) {
String tag = tags.get(i);
if (tag.matches(SUBRIP_ALIGNMENT_TAG)) {
alignmentTag = tag;
// Subsequent alignment tags should be ignored.
break;
}
}
cues.add(buildCue(text, alignmentTag));
if (haveEndTimecode) {
cues.add(null);
}
......@@ -108,6 +142,96 @@ public final class SubripDecoder extends SimpleSubtitleDecoder {
return new SubripSubtitle(cuesArray, cueTimesUsArray);
}
/**
* Trims and removes tags from the given line. The removed tags are added to {@code tags}.
*
* @param line The line to process.
* @param tags A list to which removed tags will be added.
* @return The processed line.
*/
private String processLine(String line, ArrayList<String> tags) {
line = line.trim();
int removedCharacterCount = 0;
StringBuilder processedLine = new StringBuilder(line);
Matcher matcher = SUBRIP_TAG_PATTERN.matcher(line);
while (matcher.find()) {
String tag = matcher.group();
tags.add(tag);
int start = matcher.start() - removedCharacterCount;
int tagLength = tag.length();
processedLine.replace(start, /* end= */ start + tagLength, /* str= */ "");
removedCharacterCount += tagLength;
}
return processedLine.toString();
}
/**
* Build a {@link Cue} based on the given text and alignment tag.
*
* @param text The text.
* @param alignmentTag The alignment tag, or {@code null} if no alignment tag is available.
* @return Built cue
*/
private Cue buildCue(Spanned text, @Nullable String alignmentTag) {
if (alignmentTag == null) {
return new Cue(text);
}
// Horizontal alignment.
@Cue.AnchorType int positionAnchor;
switch (alignmentTag) {
case ALIGN_BOTTOM_LEFT:
case ALIGN_MID_LEFT:
case ALIGN_TOP_LEFT:
positionAnchor = Cue.ANCHOR_TYPE_START;
break;
case ALIGN_BOTTOM_RIGHT:
case ALIGN_MID_RIGHT:
case ALIGN_TOP_RIGHT:
positionAnchor = Cue.ANCHOR_TYPE_END;
break;
case ALIGN_BOTTOM_MID:
case ALIGN_MID_MID:
case ALIGN_TOP_MID:
default:
positionAnchor = Cue.ANCHOR_TYPE_MIDDLE;
break;
}
// Vertical alignment.
@Cue.AnchorType int lineAnchor;
switch (alignmentTag) {
case ALIGN_BOTTOM_LEFT:
case ALIGN_BOTTOM_MID:
case ALIGN_BOTTOM_RIGHT:
lineAnchor = Cue.ANCHOR_TYPE_END;
break;
case ALIGN_TOP_LEFT:
case ALIGN_TOP_MID:
case ALIGN_TOP_RIGHT:
lineAnchor = Cue.ANCHOR_TYPE_START;
break;
case ALIGN_MID_LEFT:
case ALIGN_MID_MID:
case ALIGN_MID_RIGHT:
default:
lineAnchor = Cue.ANCHOR_TYPE_MIDDLE;
break;
}
return new Cue(
text,
/* textAlignment= */ null,
getFractionalPositionForAnchorType(lineAnchor),
Cue.LINE_TYPE_FRACTION,
lineAnchor,
getFractionalPositionForAnchorType(positionAnchor),
positionAnchor,
Cue.DIMEN_UNSET);
}
private static long parseTimecode(Matcher matcher, int groupOffset) {
long timestampMs = Long.parseLong(matcher.group(groupOffset + 1)) * 60 * 60 * 1000;
timestampMs += Long.parseLong(matcher.group(groupOffset + 2)) * 60 * 1000;
......@@ -116,4 +240,15 @@ public final class SubripDecoder extends SimpleSubtitleDecoder {
return timestampMs * 1000;
}
/* package */ static float getFractionalPositionForAnchorType(@Cue.AnchorType int anchorType) {
switch (anchorType) {
case Cue.ANCHOR_TYPE_START:
return SubripDecoder.START_FRACTION;
case Cue.ANCHOR_TYPE_MIDDLE:
return SubripDecoder.MID_FRACTION;
case Cue.ANCHOR_TYPE_END:
default:
return SubripDecoder.END_FRACTION;
}
}
}
......@@ -19,6 +19,7 @@ import android.graphics.Typeface;
import android.support.annotation.IntDef;
import android.text.Layout;
import com.google.android.exoplayer2.util.Assertions;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
......@@ -29,25 +30,32 @@ import java.lang.annotation.RetentionPolicy;
public static final int UNSPECIFIED = -1;
@Documented
@Retention(RetentionPolicy.SOURCE)
@IntDef(flag = true, value = {UNSPECIFIED, STYLE_NORMAL, STYLE_BOLD, STYLE_ITALIC,
STYLE_BOLD_ITALIC})
@IntDef(
flag = true,
value = {UNSPECIFIED, STYLE_NORMAL, STYLE_BOLD, STYLE_ITALIC, STYLE_BOLD_ITALIC})
public @interface StyleFlags {}
public static final int STYLE_NORMAL = Typeface.NORMAL;
public static final int STYLE_BOLD = Typeface.BOLD;
public static final int STYLE_ITALIC = Typeface.ITALIC;
public static final int STYLE_BOLD_ITALIC = Typeface.BOLD_ITALIC;
@Documented
@Retention(RetentionPolicy.SOURCE)
@IntDef({UNSPECIFIED, FONT_SIZE_UNIT_PIXEL, FONT_SIZE_UNIT_EM, FONT_SIZE_UNIT_PERCENT})
public @interface FontSizeUnit {}
public static final int FONT_SIZE_UNIT_PIXEL = 1;
public static final int FONT_SIZE_UNIT_EM = 2;
public static final int FONT_SIZE_UNIT_PERCENT = 3;
@Documented
@Retention(RetentionPolicy.SOURCE)
@IntDef({UNSPECIFIED, OFF, ON})
private @interface OptionalBoolean {}
private static final int OFF = 0;
private static final int ON = 1;
......
......@@ -19,6 +19,7 @@ import android.graphics.Typeface;
import android.support.annotation.IntDef;
import android.text.Layout;
import com.google.android.exoplayer2.util.Util;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Arrays;
......@@ -39,6 +40,7 @@ public final class WebvttCssStyle {
* Style flag enum. Possible flag values are {@link #UNSPECIFIED}, {@link #STYLE_NORMAL}, {@link
* #STYLE_BOLD}, {@link #STYLE_ITALIC} and {@link #STYLE_BOLD_ITALIC}.
*/
@Documented
@Retention(RetentionPolicy.SOURCE)
@IntDef(
flag = true,
......@@ -54,6 +56,7 @@ public final class WebvttCssStyle {
* Font size unit enum. One of {@link #UNSPECIFIED}, {@link #FONT_SIZE_UNIT_PIXEL}, {@link
* #FONT_SIZE_UNIT_EM} or {@link #FONT_SIZE_UNIT_PERCENT}.
*/
@Documented
@Retention(RetentionPolicy.SOURCE)
@IntDef({UNSPECIFIED, FONT_SIZE_UNIT_PIXEL, FONT_SIZE_UNIT_EM, FONT_SIZE_UNIT_PERCENT})
public @interface FontSizeUnit {}
......@@ -62,9 +65,11 @@ public final class WebvttCssStyle {
public static final int FONT_SIZE_UNIT_EM = 2;
public static final int FONT_SIZE_UNIT_PERCENT = 3;
@Documented
@Retention(RetentionPolicy.SOURCE)
@IntDef({UNSPECIFIED, OFF, ON})
private @interface OptionalBoolean {}
private static final int OFF = 0;
private static final int ON = 1;
......
......@@ -2141,7 +2141,7 @@ public class DefaultTrackSelector extends MappingTrackSelector {
}
/** Represents how well an audio track matches the selection {@link Parameters}. */
private static final class AudioTrackScore implements Comparable<AudioTrackScore> {
protected static final class AudioTrackScore implements Comparable<AudioTrackScore> {
private final Parameters parameters;
private final int withinRendererCapabilitiesScore;
......
......@@ -26,6 +26,7 @@ import com.google.android.exoplayer2.RendererConfiguration;
import com.google.android.exoplayer2.source.TrackGroup;
import com.google.android.exoplayer2.source.TrackGroupArray;
import com.google.android.exoplayer2.util.Util;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Arrays;
......@@ -48,6 +49,7 @@ public abstract class MappingTrackSelector extends TrackSelector {
* {@link #RENDERER_SUPPORT_NO_TRACKS}, {@link #RENDERER_SUPPORT_UNSUPPORTED_TRACKS}, {@link
* #RENDERER_SUPPORT_EXCEEDS_CAPABILITIES_TRACKS} or {@link #RENDERER_SUPPORT_PLAYABLE_TRACKS}.
*/
@Documented
@Retention(RetentionPolicy.SOURCE)
@IntDef({
RENDERER_SUPPORT_NO_TRACKS,
......
......@@ -20,6 +20,7 @@ import android.support.annotation.IntDef;
import android.support.annotation.Nullable;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.util.Assertions;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Arrays;
......@@ -33,6 +34,7 @@ public final class DataSpec {
* The flags that apply to any request for data. Possible flag values are {@link #FLAG_ALLOW_GZIP}
* and {@link #FLAG_ALLOW_CACHING_UNKNOWN_LENGTH}.
*/
@Documented
@Retention(RetentionPolicy.SOURCE)
@IntDef(
flag = true,
......@@ -61,6 +63,7 @@ public final class DataSpec {
* The set of HTTP methods that are supported by ExoPlayer {@link HttpDataSource}s. One of {@link
* #HTTP_METHOD_GET}, {@link #HTTP_METHOD_POST} or {@link #HTTP_METHOD_HEAD}.
*/
@Documented
@Retention(RetentionPolicy.SOURCE)
@IntDef({HTTP_METHOD_GET, HTTP_METHOD_POST, HTTP_METHOD_HEAD})
public @interface HttpMethod {}
......
......@@ -117,19 +117,6 @@ public final class DefaultAllocator implements Allocator {
Math.max(availableAllocations.length * 2, availableCount + allocations.length));
}
for (Allocation allocation : allocations) {
// Weak sanity check that the allocation probably originated from this pool.
if (allocation.data != initialAllocationBlock
&& allocation.data.length != individualAllocationSize) {
throw new IllegalArgumentException(
"Unexpected allocation: "
+ System.identityHashCode(allocation.data)
+ ", "
+ System.identityHashCode(initialAllocationBlock)
+ ", "
+ allocation.data.length
+ ", "
+ individualAllocationSize);
}
availableAllocations[availableCount++] = allocation;
}
allocatedCount -= allocations.length;
......
......@@ -23,6 +23,7 @@ import com.google.android.exoplayer2.util.Log;
import com.google.android.exoplayer2.util.Util;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
......@@ -261,9 +262,7 @@ public final class DefaultDataSource implements DataSource {
@Override
public Map<String, List<String>> getResponseHeaders() {
return dataSource == null
? DataSource.super.getResponseHeaders()
: dataSource.getResponseHeaders();
return dataSource == null ? Collections.emptyMap() : dataSource.getResponseHeaders();
}
@Override
......
......@@ -283,8 +283,10 @@ public class DefaultHttpDataSource extends BaseDataSource implements HttpDataSou
}
int responseCode;
String responseMessage;
try {
responseCode = connection.getResponseCode();
responseMessage = connection.getResponseMessage();
} catch (IOException e) {
closeConnectionQuietly();
throw new HttpDataSourceException("Unable to connect to " + dataSpec.uri.toString(), e,
......@@ -296,7 +298,7 @@ public class DefaultHttpDataSource extends BaseDataSource implements HttpDataSou
Map<String, List<String>> headers = connection.getHeaderFields();
closeConnectionQuietly();
InvalidResponseCodeException exception =
new InvalidResponseCodeException(responseCode, headers, dataSpec);
new InvalidResponseCodeException(responseCode, responseMessage, headers, dataSpec);
if (responseCode == 416) {
exception.initCause(new DataSourceException(DataSourceException.POSITION_OUT_OF_RANGE));
}
......
......@@ -16,10 +16,12 @@
package com.google.android.exoplayer2.upstream;
import android.support.annotation.IntDef;
import android.support.annotation.Nullable;
import android.text.TextUtils;
import com.google.android.exoplayer2.util.Predicate;
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.Collections;
......@@ -226,9 +228,11 @@ public interface HttpDataSource extends DataSource {
*/
class HttpDataSourceException extends IOException {
@Documented
@Retention(RetentionPolicy.SOURCE)
@IntDef({TYPE_OPEN, TYPE_READ, TYPE_CLOSE})
public @interface Type {}
public static final int TYPE_OPEN = 1;
public static final int TYPE_READ = 2;
public static final int TYPE_CLOSE = 3;
......@@ -291,15 +295,29 @@ public interface HttpDataSource extends DataSource {
*/
public final int responseCode;
/** The http status message. */
@Nullable public final String responseMessage;
/**
* An unmodifiable map of the response header fields and values.
*/
public final Map<String, List<String>> headerFields;
public InvalidResponseCodeException(int responseCode, Map<String, List<String>> headerFields,
/** @deprecated Use {@link #InvalidResponseCodeException(int, String, Map, DataSpec)}. */
@Deprecated
public InvalidResponseCodeException(
int responseCode, Map<String, List<String>> headerFields, DataSpec dataSpec) {
this(responseCode, /* responseMessage= */ null, headerFields, dataSpec);
}
public InvalidResponseCodeException(
int responseCode,
@Nullable String responseMessage,
Map<String, List<String>> headerFields,
DataSpec dataSpec) {
super("Response code: " + responseCode, dataSpec, TYPE_OPEN);
this.responseCode = responseCode;
this.responseMessage = responseMessage;
this.headerFields = headerFields;
}
......
......@@ -28,6 +28,7 @@ import com.google.android.exoplayer2.util.Log;
import com.google.android.exoplayer2.util.TraceUtil;
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.concurrent.ExecutorService;
......@@ -136,6 +137,7 @@ public final class Loader implements LoaderErrorThrower {
}
/** Types of action that can be taken in response to a load error. */
@Documented
@Retention(RetentionPolicy.SOURCE)
@IntDef({
ACTION_TYPE_RETRY,
......
......@@ -31,8 +31,10 @@ import com.google.android.exoplayer2.upstream.cache.Cache.CacheException;
import com.google.android.exoplayer2.util.Assertions;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Collections;
import java.util.List;
import java.util.Map;
......@@ -59,6 +61,7 @@ public final class CacheDataSource implements DataSource {
* Flags controlling the cache's behavior. Possible flag values are {@link #FLAG_BLOCK_ON_CACHE},
* {@link #FLAG_IGNORE_CACHE_ON_ERROR} and {@link #FLAG_IGNORE_CACHE_FOR_UNSET_LENGTH_REQUESTS}.
*/
@Documented
@Retention(RetentionPolicy.SOURCE)
@IntDef(
flag = true,
......@@ -91,6 +94,7 @@ public final class CacheDataSource implements DataSource {
* Reasons the cache may be ignored. One of {@link #CACHE_IGNORED_REASON_ERROR} or {@link
* #CACHE_IGNORED_REASON_UNSET_LENGTH}.
*/
@Documented
@Retention(RetentionPolicy.SOURCE)
@IntDef({CACHE_IGNORED_REASON_ERROR, CACHE_IGNORED_REASON_UNSET_LENGTH})
public @interface CacheIgnoredReason {}
......@@ -358,7 +362,7 @@ public final class CacheDataSource implements DataSource {
// TODO: Implement.
return isReadingFromUpstream()
? upstreamDataSource.getResponseHeaders()
: DataSource.super.getResponseHeaders();
: Collections.emptyMap();
}
@Override
......
......@@ -26,6 +26,7 @@ import android.opengl.GLES20;
import android.os.Handler;
import android.support.annotation.IntDef;
import android.support.annotation.Nullable;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
......@@ -43,6 +44,7 @@ public final class EGLSurfaceTexture implements SurfaceTexture.OnFrameAvailableL
* Secure mode to be used by the EGL surface and context. One of {@link #SECURE_MODE_NONE}, {@link
* #SECURE_MODE_SURFACELESS_CONTEXT} or {@link #SECURE_MODE_PROTECTED_PBUFFER}.
*/
@Documented
@Retention(RetentionPolicy.SOURCE)
@IntDef({SECURE_MODE_NONE, SECURE_MODE_SURFACELESS_CONTEXT, SECURE_MODE_PROTECTED_PBUFFER})
public @interface SecureMode {}
......
......@@ -39,22 +39,23 @@ public final class EventDispatcher<T> {
/** The list of listeners and handlers. */
private final CopyOnWriteArrayList<HandlerAndListener<T>> listeners;
/** Creates event dispatcher. */
/** Creates an event dispatcher. */
public EventDispatcher() {
listeners = new CopyOnWriteArrayList<>();
}
/** Adds listener to event dispatcher. */
/** Adds a listener to the event dispatcher. */
public void addListener(Handler handler, T eventListener) {
Assertions.checkArgument(handler != null && eventListener != null);
removeListener(eventListener);
listeners.add(new HandlerAndListener<>(handler, eventListener));
}
/** Removes listener from event dispatcher. */
/** Removes a listener from the event dispatcher. */
public void removeListener(T eventListener) {
for (HandlerAndListener<T> handlerAndListener : listeners) {
if (handlerAndListener.listener == eventListener) {
handlerAndListener.release();
listeners.remove(handlerAndListener);
}
}
......@@ -67,19 +68,33 @@ public final class EventDispatcher<T> {
*/
public void dispatch(Event<T> event) {
for (HandlerAndListener<T> handlerAndListener : listeners) {
T eventListener = handlerAndListener.listener;
handlerAndListener.handler.post(() -> event.sendTo(eventListener));
handlerAndListener.dispatch(event);
}
}
private static final class HandlerAndListener<T> {
public final Handler handler;
public final T listener;
private final Handler handler;
private final T listener;
private boolean released;
public HandlerAndListener(Handler handler, T eventListener) {
this.handler = handler;
this.listener = eventListener;
}
public void release() {
released = true;
}
public void dispatch(Event<T> event) {
handler.post(
() -> {
if (!released) {
event.sendTo(listener);
}
});
}
}
}
......@@ -18,6 +18,7 @@ package com.google.android.exoplayer2.util;
import android.support.annotation.IntDef;
import android.support.annotation.Nullable;
import android.text.TextUtils;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
......@@ -28,6 +29,7 @@ public final class Log {
* Log level for ExoPlayer logcat logging. One of {@link #LOG_LEVEL_ALL}, {@link #LOG_LEVEL_INFO},
* {@link #LOG_LEVEL_WARNING}, {@link #LOG_LEVEL_ERROR} or {@link #LOG_LEVEL_OFF}.
*/
@Documented
@Retention(RetentionPolicy.SOURCE)
@IntDef({LOG_LEVEL_ALL, LOG_LEVEL_INFO, LOG_LEVEL_WARNING, LOG_LEVEL_ERROR, LOG_LEVEL_OFF})
@interface LogLevel {}
......
......@@ -24,6 +24,7 @@ import android.content.Intent;
import android.support.annotation.IntDef;
import android.support.annotation.Nullable;
import android.support.annotation.StringRes;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
......@@ -36,6 +37,7 @@ public final class NotificationUtil {
* #IMPORTANCE_NONE}, {@link #IMPORTANCE_MIN}, {@link #IMPORTANCE_LOW}, {@link
* #IMPORTANCE_DEFAULT} or {@link #IMPORTANCE_HIGH}.
*/
@Documented
@Retention(RetentionPolicy.SOURCE)
@IntDef({
IMPORTANCE_UNSPECIFIED,
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment