mirror of
https://github.com/samsonjs/media.git
synced 2026-04-20 13:45:47 +00:00
commit
b5beb32618
134 changed files with 3316 additions and 645 deletions
22
README.md
22
README.md
|
|
@ -28,13 +28,13 @@ repository and depend on the modules locally.
|
|||
### From JCenter ###
|
||||
|
||||
The easiest way to get started using ExoPlayer is to add it as a gradle
|
||||
dependency. You need to make sure you have the JCenter and Google repositories
|
||||
dependency. You need to make sure you have the Google and JCenter repositories
|
||||
included in the `build.gradle` file in the root of your project:
|
||||
|
||||
```gradle
|
||||
repositories {
|
||||
jcenter()
|
||||
google()
|
||||
jcenter()
|
||||
}
|
||||
```
|
||||
|
||||
|
|
@ -45,10 +45,20 @@ following will add a dependency to the full library:
|
|||
implementation 'com.google.android.exoplayer:exoplayer:2.X.X'
|
||||
```
|
||||
|
||||
where `2.X.X` is your preferred version. Alternatively, you can depend on only
|
||||
the library modules that you actually need. For example the following will add
|
||||
dependencies on the Core, DASH and UI library modules, as might be required for
|
||||
an app that plays DASH content:
|
||||
where `2.X.X` is your preferred version. If not enabled already, you also need
|
||||
to turn on Java 8 support in all `build.gradle` files depending on ExoPlayer, by
|
||||
adding the following to the `android` section:
|
||||
|
||||
```gradle
|
||||
compileOptions {
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
```
|
||||
|
||||
As an alternative to the full library, you can depend on only the library
|
||||
modules that you actually need. For example the following will add dependencies
|
||||
on the Core, DASH and UI library modules, as might be required for an app that
|
||||
plays DASH content:
|
||||
|
||||
```gradle
|
||||
implementation 'com.google.android.exoplayer:exoplayer-core:2.X.X'
|
||||
|
|
|
|||
|
|
@ -1,5 +1,57 @@
|
|||
# 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
|
||||
|
|
|
|||
Binary file not shown.
|
Before Width: | Height: | Size: 303 B After Width: | Height: | Size: 261 B |
Binary file not shown.
|
Before Width: | Height: | Size: 304 B After Width: | Height: | Size: 263 B |
|
|
@ -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();
|
||||
|
|
@ -341,21 +339,6 @@ public final class CastPlayer implements Player {
|
|||
return playWhenReady;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void seekToDefaultPosition() {
|
||||
seekTo(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void seekToDefaultPosition(int windowIndex) {
|
||||
seekTo(windowIndex, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void seekTo(long positionMs) {
|
||||
seekTo(getCurrentWindowIndex(), positionMs);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void seekTo(int windowIndex, long positionMs) {
|
||||
MediaStatus mediaStatus = getMediaStatus();
|
||||
|
|
@ -392,11 +375,6 @@ public final class CastPlayer implements Player {
|
|||
return PlaybackParameters.DEFAULT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
stop(/* reset= */ false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop(boolean reset) {
|
||||
playbackState = STATE_IDLE;
|
||||
|
|
@ -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
|
||||
|
|
@ -528,15 +485,6 @@ public final class CastPlayer implements Player {
|
|||
return getCurrentPosition();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBufferedPercentage() {
|
||||
long position = getBufferedPosition();
|
||||
long duration = getDuration();
|
||||
return position == C.TIME_UNSET || duration == C.TIME_UNSET
|
||||
? 0
|
||||
: duration == 0 ? 100 : Util.constrainValue((int) ((position * 100) / duration), 0, 100);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getTotalBufferedDuration() {
|
||||
long bufferedPosition = getBufferedPosition();
|
||||
|
|
@ -546,18 +494,6 @@ public final class CastPlayer implements Player {
|
|||
: bufferedPosition - currentPosition;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCurrentWindowDynamic() {
|
||||
return !currentTimeline.isEmpty()
|
||||
&& currentTimeline.getWindow(getCurrentWindowIndex(), window).isDynamic;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCurrentWindowSeekable() {
|
||||
return !currentTimeline.isEmpty()
|
||||
&& currentTimeline.getWindow(getCurrentWindowIndex(), window).isSeekable;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isPlayingAd() {
|
||||
return false;
|
||||
|
|
@ -573,11 +509,6 @@ public final class CastPlayer implements Player {
|
|||
return C.INDEX_UNSET;
|
||||
}
|
||||
|
||||
@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();
|
||||
}
|
||||
|
||||
|
|
@ -132,6 +141,18 @@ public final class ImaAdsLoader
|
|||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the ad UI elements to be rendered by the IMA SDK.
|
||||
*
|
||||
* @param adUiElements The ad UI elements to be rendered by the IMA SDK.
|
||||
* @return This builder, for convenience.
|
||||
* @see AdsRenderingSettings#setUiElements(Set)
|
||||
*/
|
||||
public Builder setAdUiElements(Set<UiElement> adUiElements) {
|
||||
this.adUiElements = new HashSet<>(Assertions.checkNotNull(adUiElements));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets 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;
|
||||
|
|
@ -151,16 +149,6 @@ import java.util.ArrayList;
|
|||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getNextWindowIndex() {
|
||||
return C.INDEX_UNSET;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPreviousWindowIndex() {
|
||||
return C.INDEX_UNSET;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getDuration() {
|
||||
if (timeline.isEmpty()) {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
return actions | PlaybackStateCompat.ACTION_SKIP_TO_QUEUE_ITEM;
|
||||
if (window.isSeekable || !window.isDynamic || player.hasPrevious()) {
|
||||
actions |= PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS;
|
||||
}
|
||||
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.
|
||||
|
|
|
|||
|
|
@ -0,0 +1,138 @@
|
|||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.google.android.exoplayer2;
|
||||
|
||||
import android.support.annotation.Nullable;
|
||||
import com.google.android.exoplayer2.util.Util;
|
||||
|
||||
/** Abstract base {@link Player} which implements common implementation independent methods. */
|
||||
public abstract class BasePlayer implements Player {
|
||||
|
||||
protected final Timeline.Window window;
|
||||
|
||||
public BasePlayer() {
|
||||
window = new Timeline.Window();
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void seekToDefaultPosition() {
|
||||
seekToDefaultPosition(getCurrentWindowIndex());
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void seekToDefaultPosition(int windowIndex) {
|
||||
seekTo(windowIndex, /* positionMs= */ C.TIME_UNSET);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void seekTo(long positionMs) {
|
||||
seekTo(getCurrentWindowIndex(), positionMs);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final boolean hasPrevious() {
|
||||
return getPreviousWindowIndex() != C.INDEX_UNSET;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void previous() {
|
||||
int previousWindowIndex = getPreviousWindowIndex();
|
||||
if (previousWindowIndex != C.INDEX_UNSET) {
|
||||
seekToDefaultPosition(previousWindowIndex);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public final boolean hasNext() {
|
||||
return getNextWindowIndex() != C.INDEX_UNSET;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void next() {
|
||||
int nextWindowIndex = getNextWindowIndex();
|
||||
if (nextWindowIndex != C.INDEX_UNSET) {
|
||||
seekToDefaultPosition(nextWindowIndex);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void stop() {
|
||||
stop(/* reset= */ false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final int getNextWindowIndex() {
|
||||
Timeline timeline = getCurrentTimeline();
|
||||
return timeline.isEmpty()
|
||||
? C.INDEX_UNSET
|
||||
: timeline.getNextWindowIndex(
|
||||
getCurrentWindowIndex(), getRepeatModeForNavigation(), getShuffleModeEnabled());
|
||||
}
|
||||
|
||||
@Override
|
||||
public final int getPreviousWindowIndex() {
|
||||
Timeline timeline = getCurrentTimeline();
|
||||
return timeline.isEmpty()
|
||||
? C.INDEX_UNSET
|
||||
: timeline.getPreviousWindowIndex(
|
||||
getCurrentWindowIndex(), getRepeatModeForNavigation(), getShuffleModeEnabled());
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public final Object getCurrentTag() {
|
||||
int windowIndex = getCurrentWindowIndex();
|
||||
Timeline timeline = getCurrentTimeline();
|
||||
return windowIndex >= timeline.getWindowCount()
|
||||
? null
|
||||
: timeline.getWindow(windowIndex, window, /* setTag= */ true).tag;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final int getBufferedPercentage() {
|
||||
long position = getBufferedPosition();
|
||||
long duration = getDuration();
|
||||
return position == C.TIME_UNSET || duration == C.TIME_UNSET
|
||||
? 0
|
||||
: duration == 0 ? 100 : Util.constrainValue((int) ((position * 100) / duration), 0, 100);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final boolean isCurrentWindowDynamic() {
|
||||
Timeline timeline = getCurrentTimeline();
|
||||
return !timeline.isEmpty() && timeline.getWindow(getCurrentWindowIndex(), window).isDynamic;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final boolean isCurrentWindowSeekable() {
|
||||
Timeline timeline = getCurrentTimeline();
|
||||
return !timeline.isEmpty() && timeline.getWindow(getCurrentWindowIndex(), window).isSeekable;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final long getContentDuration() {
|
||||
Timeline timeline = getCurrentTimeline();
|
||||
return timeline.isEmpty()
|
||||
? C.TIME_UNSET
|
||||
: timeline.getWindow(getCurrentWindowIndex(), window).getDurationMs();
|
||||
}
|
||||
|
||||
@RepeatMode
|
||||
private int getRepeatModeForNavigation() {
|
||||
@RepeatMode int repeatMode = getRepeatMode();
|
||||
return repeatMode == REPEAT_MODE_ONE ? REPEAT_MODE_OFF : repeatMode;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
@ -293,21 +289,6 @@ import java.util.concurrent.CopyOnWriteArraySet;
|
|||
return playbackInfo.isLoading;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void seekToDefaultPosition() {
|
||||
seekToDefaultPosition(getCurrentWindowIndex());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void seekToDefaultPosition(int windowIndex) {
|
||||
seekTo(windowIndex, C.TIME_UNSET);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void seekTo(long positionMs) {
|
||||
seekTo(getCurrentWindowIndex(), positionMs);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void seekTo(int windowIndex, long positionMs) {
|
||||
Timeline timeline = playbackInfo.timeline;
|
||||
|
|
@ -377,19 +358,6 @@ import java.util.concurrent.CopyOnWriteArraySet;
|
|||
return seekParameters;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable Object getCurrentTag() {
|
||||
int windowIndex = getCurrentWindowIndex();
|
||||
return windowIndex >= playbackInfo.timeline.getWindowCount()
|
||||
? null
|
||||
: playbackInfo.timeline.getWindow(windowIndex, window, /* setTag= */ true).tag;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
stop(/* reset= */ false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop(boolean reset) {
|
||||
if (reset) {
|
||||
|
|
@ -494,20 +462,6 @@ 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()) {
|
||||
|
|
@ -540,32 +494,11 @@ import java.util.concurrent.CopyOnWriteArraySet;
|
|||
return getContentBufferedPosition();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBufferedPercentage() {
|
||||
long position = getBufferedPosition();
|
||||
long duration = getDuration();
|
||||
return position == C.TIME_UNSET || duration == C.TIME_UNSET
|
||||
? 0
|
||||
: (duration == 0 ? 100 : Util.constrainValue((int) ((position * 100) / duration), 0, 100));
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getTotalBufferedDuration() {
|
||||
return Math.max(0, C.usToMs(playbackInfo.totalBufferedDurationUs));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCurrentWindowDynamic() {
|
||||
Timeline timeline = playbackInfo.timeline;
|
||||
return !timeline.isEmpty() && timeline.getWindow(getCurrentWindowIndex(), window).isDynamic;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCurrentWindowSeekable() {
|
||||
Timeline timeline = playbackInfo.timeline;
|
||||
return !timeline.isEmpty() && timeline.getWindow(getCurrentWindowIndex(), window).isSeekable;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isPlayingAd() {
|
||||
return !shouldMaskPosition() && playbackInfo.periodId.isAd();
|
||||
|
|
@ -581,13 +514,6 @@ import java.util.concurrent.CopyOnWriteArraySet;
|
|||
return isPlayingAd() ? playbackInfo.periodId.adIndexInAdGroup : C.INDEX_UNSET;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getContentDuration() {
|
||||
return playbackInfo.timeline.isEmpty()
|
||||
? C.TIME_UNSET
|
||||
: playbackInfo.timeline.getWindow(getCurrentWindowIndex(), window).getDurationMs();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getContentPosition() {
|
||||
if (isPlayingAd()) {
|
||||
|
|
@ -692,7 +618,7 @@ 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(
|
||||
|
|
|
|||
|
|
@ -448,7 +448,11 @@ import java.util.Collections;
|
|||
seekToPeriodPosition(periodId, playbackInfo.positionUs, /* forceDisableRenderers= */ true);
|
||||
if (newPositionUs != playbackInfo.positionUs) {
|
||||
playbackInfo =
|
||||
playbackInfo.fromNewPosition(periodId, newPositionUs, playbackInfo.contentPositionUs);
|
||||
playbackInfo.copyWithNewPosition(
|
||||
periodId,
|
||||
newPositionUs,
|
||||
playbackInfo.contentPositionUs,
|
||||
getTotalBufferedDurationUs());
|
||||
if (sendDiscontinuity) {
|
||||
playbackInfoUpdate.setPositionDiscontinuity(Player.DISCONTINUITY_REASON_INTERNAL);
|
||||
}
|
||||
|
|
@ -483,8 +487,12 @@ import java.util.Collections;
|
|||
// A MediaPeriod may report a discontinuity at the current playback position to ensure the
|
||||
// renderers are flushed. Only report the discontinuity externally if the position changed.
|
||||
if (periodPositionUs != playbackInfo.positionUs) {
|
||||
playbackInfo = playbackInfo.fromNewPosition(playbackInfo.periodId, periodPositionUs,
|
||||
playbackInfo.contentPositionUs);
|
||||
playbackInfo =
|
||||
playbackInfo.copyWithNewPosition(
|
||||
playbackInfo.periodId,
|
||||
periodPositionUs,
|
||||
playbackInfo.contentPositionUs,
|
||||
getTotalBufferedDurationUs());
|
||||
playbackInfoUpdate.setPositionDiscontinuity(Player.DISCONTINUITY_REASON_INTERNAL);
|
||||
}
|
||||
} else {
|
||||
|
|
@ -496,10 +504,8 @@ import java.util.Collections;
|
|||
|
||||
// Update the buffered position and total buffered duration.
|
||||
MediaPeriodHolder loadingPeriod = queue.getLoadingPeriod();
|
||||
playbackInfo.bufferedPositionUs =
|
||||
loadingPeriod.getBufferedPositionUs(/* convertEosToDuration= */ true);
|
||||
playbackInfo.totalBufferedDurationUs =
|
||||
playbackInfo.bufferedPositionUs - loadingPeriod.toPeriodTime(rendererPositionUs);
|
||||
playbackInfo.bufferedPositionUs = loadingPeriod.getBufferedPositionUs();
|
||||
playbackInfo.totalBufferedDurationUs = getTotalBufferedDurationUs();
|
||||
}
|
||||
|
||||
private void doSomeWork() throws ExoPlaybackException, IOException {
|
||||
|
|
@ -599,7 +605,7 @@ import java.util.Collections;
|
|||
if (resolvedSeekPosition == null) {
|
||||
// The seek position was valid for the timeline that it was performed into, but the
|
||||
// timeline has changed or is not ready and a suitable seek position could not be resolved.
|
||||
periodId = getFirstMediaPeriodId();
|
||||
periodId = playbackInfo.getDummyFirstMediaPeriodId(shuffleModeEnabled, window);
|
||||
periodPositionUs = C.TIME_UNSET;
|
||||
contentPositionUs = C.TIME_UNSET;
|
||||
seekPositionAdjusted = true;
|
||||
|
|
@ -647,7 +653,9 @@ import java.util.Collections;
|
|||
periodPositionUs = newPeriodPositionUs;
|
||||
}
|
||||
} finally {
|
||||
playbackInfo = playbackInfo.fromNewPosition(periodId, periodPositionUs, contentPositionUs);
|
||||
playbackInfo =
|
||||
playbackInfo.copyWithNewPosition(
|
||||
periodId, periodPositionUs, contentPositionUs, getTotalBufferedDurationUs());
|
||||
if (seekPositionAdjusted) {
|
||||
playbackInfoUpdate.setPositionDiscontinuity(Player.DISCONTINUITY_REASON_SEEK_ADJUSTMENT);
|
||||
}
|
||||
|
|
@ -752,17 +760,6 @@ import java.util.Collections;
|
|||
}
|
||||
}
|
||||
|
||||
private MediaPeriodId getFirstMediaPeriodId() {
|
||||
Timeline timeline = playbackInfo.timeline;
|
||||
if (timeline.isEmpty()) {
|
||||
return PlaybackInfo.DUMMY_MEDIA_PERIOD_ID;
|
||||
}
|
||||
int firstPeriodIndex =
|
||||
timeline.getWindow(timeline.getFirstWindowIndex(shuffleModeEnabled), window)
|
||||
.firstPeriodIndex;
|
||||
return new MediaPeriodId(timeline.getUidOfPeriod(firstPeriodIndex));
|
||||
}
|
||||
|
||||
private void resetInternal(
|
||||
boolean releaseMediaSource, boolean resetPosition, boolean resetState) {
|
||||
handler.removeMessages(MSG_DO_SOME_WORK);
|
||||
|
|
@ -791,8 +788,11 @@ import java.util.Collections;
|
|||
pendingMessages.clear();
|
||||
nextPendingMessageIndex = 0;
|
||||
}
|
||||
MediaPeriodId mediaPeriodId =
|
||||
resetPosition
|
||||
? playbackInfo.getDummyFirstMediaPeriodId(shuffleModeEnabled, window)
|
||||
: playbackInfo.periodId;
|
||||
// Set the start position to TIME_UNSET so that a subsequent seek to 0 isn't ignored.
|
||||
MediaPeriodId mediaPeriodId = resetPosition ? getFirstMediaPeriodId() : playbackInfo.periodId;
|
||||
long startPositionUs = resetPosition ? C.TIME_UNSET : playbackInfo.positionUs;
|
||||
long contentPositionUs = resetPosition ? C.TIME_UNSET : playbackInfo.contentPositionUs;
|
||||
playbackInfo =
|
||||
|
|
@ -1020,8 +1020,12 @@ import java.util.Collections;
|
|||
playbackInfo.positionUs, recreateStreams, streamResetFlags);
|
||||
if (playbackInfo.playbackState != Player.STATE_ENDED
|
||||
&& periodPositionUs != playbackInfo.positionUs) {
|
||||
playbackInfo = playbackInfo.fromNewPosition(playbackInfo.periodId, periodPositionUs,
|
||||
playbackInfo.contentPositionUs);
|
||||
playbackInfo =
|
||||
playbackInfo.copyWithNewPosition(
|
||||
playbackInfo.periodId,
|
||||
periodPositionUs,
|
||||
playbackInfo.contentPositionUs,
|
||||
getTotalBufferedDurationUs());
|
||||
playbackInfoUpdate.setPositionDiscontinuity(Player.DISCONTINUITY_REASON_INTERNAL);
|
||||
resetRendererPosition(periodPositionUs);
|
||||
}
|
||||
|
|
@ -1097,12 +1101,10 @@ import java.util.Collections;
|
|||
}
|
||||
// Renderers are ready and we're loading. Ask the LoadControl whether to transition.
|
||||
MediaPeriodHolder loadingHolder = queue.getLoadingPeriod();
|
||||
long bufferedPositionUs = loadingHolder.getBufferedPositionUs(!loadingHolder.info.isFinal);
|
||||
return bufferedPositionUs == C.TIME_END_OF_SOURCE
|
||||
boolean bufferedToEnd = loadingHolder.isFullyBuffered() && loadingHolder.info.isFinal;
|
||||
return bufferedToEnd
|
||||
|| loadControl.shouldStartPlayback(
|
||||
bufferedPositionUs - loadingHolder.toPeriodTime(rendererPositionUs),
|
||||
mediaClock.getPlaybackParameters().speed,
|
||||
rebuffering);
|
||||
getTotalBufferedDurationUs(), mediaClock.getPlaybackParameters().speed, rebuffering);
|
||||
}
|
||||
|
||||
private boolean isTimelineReady() {
|
||||
|
|
@ -1164,8 +1166,13 @@ import java.util.Collections;
|
|||
periodPosition =
|
||||
resolveSeekPosition(pendingInitialSeekPosition, /* trySubsequentPeriods= */ true);
|
||||
} catch (IllegalSeekPositionException e) {
|
||||
MediaPeriodId firstMediaPeriodId =
|
||||
playbackInfo.getDummyFirstMediaPeriodId(shuffleModeEnabled, window);
|
||||
playbackInfo =
|
||||
playbackInfo.fromNewPosition(getFirstMediaPeriodId(), C.TIME_UNSET, C.TIME_UNSET);
|
||||
playbackInfo.resetToNewPosition(
|
||||
firstMediaPeriodId,
|
||||
/* startPositionUs= */ C.TIME_UNSET,
|
||||
/* contentPositionUs= */ C.TIME_UNSET);
|
||||
throw e;
|
||||
}
|
||||
pendingInitialSeekPosition = null;
|
||||
|
|
@ -1178,7 +1185,7 @@ import java.util.Collections;
|
|||
long positionUs = periodPosition.second;
|
||||
MediaPeriodId periodId = queue.resolveMediaPeriodIdForAds(periodUid, positionUs);
|
||||
playbackInfo =
|
||||
playbackInfo.fromNewPosition(
|
||||
playbackInfo.resetToNewPosition(
|
||||
periodId, periodId.isAd() ? 0 : positionUs, /* contentPositionUs= */ positionUs);
|
||||
}
|
||||
} else if (playbackInfo.startPositionUs == C.TIME_UNSET) {
|
||||
|
|
@ -1192,7 +1199,7 @@ import java.util.Collections;
|
|||
long startPositionUs = defaultPosition.second;
|
||||
MediaPeriodId periodId = queue.resolveMediaPeriodIdForAds(periodUid, startPositionUs);
|
||||
playbackInfo =
|
||||
playbackInfo.fromNewPosition(
|
||||
playbackInfo.resetToNewPosition(
|
||||
periodId,
|
||||
periodId.isAd() ? 0 : startPositionUs,
|
||||
/* contentPositionUs= */ startPositionUs);
|
||||
|
|
@ -1211,7 +1218,7 @@ import java.util.Collections;
|
|||
long startPositionUs = defaultPosition.second;
|
||||
MediaPeriodId periodId = queue.resolveMediaPeriodIdForAds(periodUid, startPositionUs);
|
||||
playbackInfo =
|
||||
playbackInfo.fromNewPosition(
|
||||
playbackInfo.resetToNewPosition(
|
||||
periodId,
|
||||
/* startPositionUs= */ periodId.isAd() ? 0 : startPositionUs,
|
||||
/* contentPositionUs= */ startPositionUs);
|
||||
|
|
@ -1250,7 +1257,9 @@ import java.util.Collections;
|
|||
}
|
||||
// Actually do the seek.
|
||||
long seekPositionUs = seekToPeriodPosition(periodId, periodId.isAd() ? 0 : contentPositionUs);
|
||||
playbackInfo = playbackInfo.fromNewPosition(periodId, seekPositionUs, contentPositionUs);
|
||||
playbackInfo =
|
||||
playbackInfo.copyWithNewPosition(
|
||||
periodId, seekPositionUs, contentPositionUs, getTotalBufferedDurationUs());
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -1262,7 +1271,9 @@ import java.util.Collections;
|
|||
// The previously playing ad should no longer be played, so skip it.
|
||||
long seekPositionUs =
|
||||
seekToPeriodPosition(periodId, periodId.isAd() ? 0 : contentPositionUs);
|
||||
playbackInfo = playbackInfo.fromNewPosition(periodId, seekPositionUs, contentPositionUs);
|
||||
playbackInfo =
|
||||
playbackInfo.copyWithNewPosition(
|
||||
periodId, seekPositionUs, contentPositionUs, getTotalBufferedDurationUs());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
|
@ -1418,8 +1429,12 @@ import java.util.Collections;
|
|||
MediaPeriodHolder oldPlayingPeriodHolder = playingPeriodHolder;
|
||||
playingPeriodHolder = queue.advancePlayingPeriod();
|
||||
updatePlayingPeriodRenderers(oldPlayingPeriodHolder);
|
||||
playbackInfo = playbackInfo.fromNewPosition(playingPeriodHolder.info.id,
|
||||
playingPeriodHolder.info.startPositionUs, playingPeriodHolder.info.contentPositionUs);
|
||||
playbackInfo =
|
||||
playbackInfo.copyWithNewPosition(
|
||||
playingPeriodHolder.info.id,
|
||||
playingPeriodHolder.info.startPositionUs,
|
||||
playingPeriodHolder.info.contentPositionUs,
|
||||
getTotalBufferedDurationUs());
|
||||
playbackInfoUpdate.setPositionDiscontinuity(discontinuityReason);
|
||||
updatePlaybackPositions();
|
||||
advancedPlayingPeriod = true;
|
||||
|
|
@ -1571,7 +1586,7 @@ import java.util.Collections;
|
|||
return;
|
||||
}
|
||||
long bufferedDurationUs =
|
||||
nextLoadPositionUs - loadingPeriodHolder.toPeriodTime(rendererPositionUs);
|
||||
getTotalBufferedDurationUs(/* bufferedPositionInLoadingPeriodUs= */ nextLoadPositionUs);
|
||||
boolean continueLoading =
|
||||
loadControl.shouldContinueLoading(
|
||||
bufferedDurationUs, mediaClock.getPlaybackParameters().speed);
|
||||
|
|
@ -1667,6 +1682,11 @@ import java.util.Collections;
|
|||
if (loadingMediaPeriodChanged) {
|
||||
playbackInfo = playbackInfo.copyWithLoadingMediaPeriodId(loadingMediaPeriodId);
|
||||
}
|
||||
playbackInfo.bufferedPositionUs =
|
||||
loadingMediaPeriodHolder == null
|
||||
? playbackInfo.positionUs
|
||||
: loadingMediaPeriodHolder.getBufferedPositionUs();
|
||||
playbackInfo.totalBufferedDurationUs = getTotalBufferedDurationUs();
|
||||
if ((loadingMediaPeriodChanged || loadingTrackSelectionChanged)
|
||||
&& loadingMediaPeriodHolder != null
|
||||
&& loadingMediaPeriodHolder.prepared) {
|
||||
|
|
@ -1675,6 +1695,17 @@ import java.util.Collections;
|
|||
}
|
||||
}
|
||||
|
||||
private long getTotalBufferedDurationUs() {
|
||||
return getTotalBufferedDurationUs(playbackInfo.bufferedPositionUs);
|
||||
}
|
||||
|
||||
private long getTotalBufferedDurationUs(long bufferedPositionInLoadingPeriodUs) {
|
||||
MediaPeriodHolder loadingPeriodHolder = queue.getLoadingPeriod();
|
||||
return loadingPeriodHolder == null
|
||||
? 0
|
||||
: bufferedPositionInLoadingPeriodUs - loadingPeriodHolder.toPeriodTime(rendererPositionUs);
|
||||
}
|
||||
|
||||
private void updateLoadControlTrackSelection(
|
||||
TrackGroupArray trackGroups, TrackSelectorResult trackSelectorResult) {
|
||||
loadControl.onTracksSelected(renderers, trackGroups, trackSelectorResult.selections);
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
@ -655,6 +659,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.
|
||||
|
|
|
|||
|
|
@ -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}. */
|
||||
|
|
@ -927,27 +927,6 @@ public class SimpleExoPlayer
|
|||
return player.isLoading();
|
||||
}
|
||||
|
||||
@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();
|
||||
|
|
@ -979,17 +958,6 @@ public class SimpleExoPlayer
|
|||
return player.getSeekParameters();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable Object getCurrentTag() {
|
||||
verifyApplicationThread();
|
||||
return player.getCurrentTag();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
stop(/* reset= */ false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop(boolean reset) {
|
||||
verifyApplicationThread();
|
||||
|
|
@ -1092,18 +1060,6 @@ public class SimpleExoPlayer
|
|||
return player.getCurrentWindowIndex();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getNextWindowIndex() {
|
||||
verifyApplicationThread();
|
||||
return player.getNextWindowIndex();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPreviousWindowIndex() {
|
||||
verifyApplicationThread();
|
||||
return player.getPreviousWindowIndex();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getDuration() {
|
||||
verifyApplicationThread();
|
||||
|
|
@ -1122,30 +1078,12 @@ public class SimpleExoPlayer
|
|||
return player.getBufferedPosition();
|
||||
}
|
||||
|
||||
@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();
|
||||
|
|
@ -1164,12 +1102,6 @@ public class SimpleExoPlayer
|
|||
return player.getCurrentAdIndexInAdGroup();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getContentDuration() {
|
||||
verifyApplicationThread();
|
||||
return player.getContentDuration();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getContentPosition() {
|
||||
verifyApplicationThread();
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,125 @@
|
|||
/*
|
||||
* 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();
|
||||
|
|
@ -313,6 +335,17 @@ public final class Mp3Extractor implements Extractor {
|
|||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
|
|
@ -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];
|
||||
|
|
|
|||
|
|
@ -0,0 +1,112 @@
|
|||
/*
|
||||
* 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 {
|
|||
* -> 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;
|
||||
|
|
|
|||
|
|
@ -15,20 +15,57 @@
|
|||
*/
|
||||
package com.google.android.exoplayer2.upstream;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Handler;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.util.SparseArray;
|
||||
import com.google.android.exoplayer2.C;
|
||||
import com.google.android.exoplayer2.util.Assertions;
|
||||
import com.google.android.exoplayer2.util.Clock;
|
||||
import com.google.android.exoplayer2.util.EventDispatcher;
|
||||
import com.google.android.exoplayer2.util.SlidingPercentile;
|
||||
import com.google.android.exoplayer2.util.Util;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Estimates bandwidth by listening to data transfers. The bandwidth estimate is calculated using a
|
||||
* {@link SlidingPercentile} and is updated each time a transfer ends.
|
||||
* Estimates bandwidth by listening to data transfers.
|
||||
*
|
||||
* <p>The bandwidth estimate is calculated using a {@link SlidingPercentile} and is updated each
|
||||
* time a transfer ends. The initial estimate is based on the current operator's network country
|
||||
* code or the locale of the user, as well as the network connection type. This can be configured in
|
||||
* the {@link Builder}.
|
||||
*/
|
||||
public final class DefaultBandwidthMeter implements BandwidthMeter, TransferListener {
|
||||
|
||||
/** Default initial bitrate estimate in bits per second. */
|
||||
/**
|
||||
* Country groups used to determine the default initial bitrate estimate. The group assignment for
|
||||
* each country is an array of group indices for [Wifi, 2G, 3G, 4G].
|
||||
*/
|
||||
public static final Map<String, int[]> DEFAULT_INITIAL_BITRATE_COUNTRY_GROUPS =
|
||||
createInitialBitrateCountryGroupAssignment();
|
||||
|
||||
/** Default initial Wifi bitrate estimate in bits per second. */
|
||||
public static final long[] DEFAULT_INITIAL_BITRATE_ESTIMATES_WIFI =
|
||||
new long[] {5_700_000, 3_400_000, 1_900_000, 1_000_000, 400_000};
|
||||
|
||||
/** Default initial 2G bitrate estimates in bits per second. */
|
||||
public static final long[] DEFAULT_INITIAL_BITRATE_ESTIMATES_2G =
|
||||
new long[] {169_000, 129_000, 114_000, 102_000, 87_000};
|
||||
|
||||
/** Default initial 3G bitrate estimates in bits per second. */
|
||||
public static final long[] DEFAULT_INITIAL_BITRATE_ESTIMATES_3G =
|
||||
new long[] {2_100_000, 1_300_000, 950_000, 700_000, 400_000};
|
||||
|
||||
/** Default initial 4G bitrate estimates in bits per second. */
|
||||
public static final long[] DEFAULT_INITIAL_BITRATE_ESTIMATES_4G =
|
||||
new long[] {6_900_000, 4_300_000, 2_700_000, 1_600_000, 450_000};
|
||||
|
||||
/**
|
||||
* Default initial bitrate estimate used when the device is offline or the network type cannot be
|
||||
* determined, in bits per second.
|
||||
*/
|
||||
public static final long DEFAULT_INITIAL_BITRATE_ESTIMATE = 1_000_000;
|
||||
|
||||
/** Default maximum weight for the sliding window. */
|
||||
|
|
@ -37,15 +74,28 @@ public final class DefaultBandwidthMeter implements BandwidthMeter, TransferList
|
|||
/** Builder for a bandwidth meter. */
|
||||
public static final class Builder {
|
||||
|
||||
private @Nullable Handler eventHandler;
|
||||
private @Nullable EventListener eventListener;
|
||||
private long initialBitrateEstimate;
|
||||
@Nullable private final Context context;
|
||||
|
||||
@Nullable private Handler eventHandler;
|
||||
@Nullable private EventListener eventListener;
|
||||
private SparseArray<Long> initialBitrateEstimates;
|
||||
private int slidingWindowMaxWeight;
|
||||
private Clock clock;
|
||||
|
||||
/** Creates a builder with default parameters and without listener. */
|
||||
/** @deprecated Use {@link #Builder(Context)} instead. */
|
||||
@Deprecated
|
||||
public Builder() {
|
||||
initialBitrateEstimate = DEFAULT_INITIAL_BITRATE_ESTIMATE;
|
||||
this(/* context= */ null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a builder with default parameters and without listener.
|
||||
*
|
||||
* @param context A context.
|
||||
*/
|
||||
public Builder(@Nullable Context context) {
|
||||
this.context = context == null ? null : context.getApplicationContext();
|
||||
initialBitrateEstimates = getInitialBitrateEstimatesForCountry(Util.getCountryCode(context));
|
||||
slidingWindowMaxWeight = DEFAULT_SLIDING_WINDOW_MAX_WEIGHT;
|
||||
clock = Clock.DEFAULT;
|
||||
}
|
||||
|
|
@ -84,7 +134,38 @@ public final class DefaultBandwidthMeter implements BandwidthMeter, TransferList
|
|||
* @return This builder.
|
||||
*/
|
||||
public Builder setInitialBitrateEstimate(long initialBitrateEstimate) {
|
||||
this.initialBitrateEstimate = initialBitrateEstimate;
|
||||
for (int i = 0; i < initialBitrateEstimates.size(); i++) {
|
||||
initialBitrateEstimates.setValueAt(i, initialBitrateEstimate);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the initial bitrate estimate in bits per second for a network type that should be
|
||||
* assumed when a bandwidth estimate is unavailable and the current network connection is of the
|
||||
* specified type.
|
||||
*
|
||||
* @param networkType The {@link C.NetworkType} this initial estimate is for.
|
||||
* @param initialBitrateEstimate The initial bitrate estimate in bits per second.
|
||||
* @return This builder.
|
||||
*/
|
||||
public Builder setInitialBitrateEstimate(
|
||||
@C.NetworkType int networkType, long initialBitrateEstimate) {
|
||||
initialBitrateEstimates.put(networkType, initialBitrateEstimate);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the initial bitrate estimates to the default values of the specified country. The
|
||||
* initial estimates are used when a bandwidth estimate is unavailable.
|
||||
*
|
||||
* @param countryCode The ISO 3166-1 alpha-2 country code of the country whose default bitrate
|
||||
* estimates should be used.
|
||||
* @return This builder.
|
||||
*/
|
||||
public Builder setInitialBitrateEstimate(String countryCode) {
|
||||
initialBitrateEstimates =
|
||||
getInitialBitrateEstimatesForCountry(Util.toUpperInvariant(countryCode));
|
||||
return this;
|
||||
}
|
||||
|
||||
|
|
@ -106,6 +187,10 @@ public final class DefaultBandwidthMeter implements BandwidthMeter, TransferList
|
|||
* @return A bandwidth meter with the configured properties.
|
||||
*/
|
||||
public DefaultBandwidthMeter build() {
|
||||
Long initialBitrateEstimate = initialBitrateEstimates.get(Util.getNetworkType(context));
|
||||
if (initialBitrateEstimate == null) {
|
||||
initialBitrateEstimate = initialBitrateEstimates.get(C.NETWORK_TYPE_UNKNOWN);
|
||||
}
|
||||
DefaultBandwidthMeter bandwidthMeter =
|
||||
new DefaultBandwidthMeter(initialBitrateEstimate, slidingWindowMaxWeight, clock);
|
||||
if (eventHandler != null && eventListener != null) {
|
||||
|
|
@ -113,6 +198,26 @@ public final class DefaultBandwidthMeter implements BandwidthMeter, TransferList
|
|||
}
|
||||
return bandwidthMeter;
|
||||
}
|
||||
|
||||
private static SparseArray<Long> getInitialBitrateEstimatesForCountry(String countryCode) {
|
||||
int[] groupIndices = getCountryGroupIndices(countryCode);
|
||||
SparseArray<Long> result = new SparseArray<>(/* initialCapacity= */ 6);
|
||||
result.append(C.NETWORK_TYPE_UNKNOWN, DEFAULT_INITIAL_BITRATE_ESTIMATE);
|
||||
result.append(C.NETWORK_TYPE_WIFI, DEFAULT_INITIAL_BITRATE_ESTIMATES_WIFI[groupIndices[0]]);
|
||||
result.append(C.NETWORK_TYPE_2G, DEFAULT_INITIAL_BITRATE_ESTIMATES_2G[groupIndices[1]]);
|
||||
result.append(C.NETWORK_TYPE_3G, DEFAULT_INITIAL_BITRATE_ESTIMATES_3G[groupIndices[2]]);
|
||||
result.append(C.NETWORK_TYPE_4G, DEFAULT_INITIAL_BITRATE_ESTIMATES_4G[groupIndices[3]]);
|
||||
// Assume default Wifi bitrate for Ethernet to prevent using the slower fallback bitrate.
|
||||
result.append(
|
||||
C.NETWORK_TYPE_ETHERNET, DEFAULT_INITIAL_BITRATE_ESTIMATES_WIFI[groupIndices[0]]);
|
||||
return result;
|
||||
}
|
||||
|
||||
private static int[] getCountryGroupIndices(String countryCode) {
|
||||
int[] groupIndices = DEFAULT_INITIAL_BITRATE_COUNTRY_GROUPS.get(countryCode);
|
||||
// Assume median group if not found.
|
||||
return groupIndices == null ? new int[] {2, 2, 2, 2} : groupIndices;
|
||||
}
|
||||
}
|
||||
|
||||
private static final int ELAPSED_MILLIS_FOR_ESTIMATE = 2000;
|
||||
|
|
@ -153,10 +258,7 @@ public final class DefaultBandwidthMeter implements BandwidthMeter, TransferList
|
|||
}
|
||||
}
|
||||
|
||||
private DefaultBandwidthMeter(
|
||||
long initialBitrateEstimate,
|
||||
int maxWeight,
|
||||
Clock clock) {
|
||||
private DefaultBandwidthMeter(long initialBitrateEstimate, int maxWeight, Clock clock) {
|
||||
this.eventDispatcher = new EventDispatcher<>();
|
||||
this.slidingPercentile = new SlidingPercentile(maxWeight);
|
||||
this.clock = clock;
|
||||
|
|
@ -169,7 +271,8 @@ public final class DefaultBandwidthMeter implements BandwidthMeter, TransferList
|
|||
}
|
||||
|
||||
@Override
|
||||
public @Nullable TransferListener getTransferListener() {
|
||||
@Nullable
|
||||
public TransferListener getTransferListener() {
|
||||
return this;
|
||||
}
|
||||
|
||||
|
|
@ -237,4 +340,249 @@ public final class DefaultBandwidthMeter implements BandwidthMeter, TransferList
|
|||
private void notifyBandwidthSample(int elapsedMs, long bytes, long bitrate) {
|
||||
eventDispatcher.dispatch(listener -> listener.onBandwidthSample(elapsedMs, bytes, bitrate));
|
||||
}
|
||||
|
||||
private static Map<String, int[]> createInitialBitrateCountryGroupAssignment() {
|
||||
HashMap<String, int[]> countryGroupAssignment = new HashMap<>();
|
||||
countryGroupAssignment.put("AD", new int[] {1, 0, 0, 0});
|
||||
countryGroupAssignment.put("AE", new int[] {1, 3, 4, 4});
|
||||
countryGroupAssignment.put("AF", new int[] {4, 4, 3, 2});
|
||||
countryGroupAssignment.put("AG", new int[] {3, 2, 1, 2});
|
||||
countryGroupAssignment.put("AI", new int[] {1, 0, 0, 2});
|
||||
countryGroupAssignment.put("AL", new int[] {1, 1, 1, 1});
|
||||
countryGroupAssignment.put("AM", new int[] {2, 2, 4, 3});
|
||||
countryGroupAssignment.put("AO", new int[] {2, 4, 2, 0});
|
||||
countryGroupAssignment.put("AR", new int[] {2, 3, 2, 3});
|
||||
countryGroupAssignment.put("AS", new int[] {3, 4, 4, 1});
|
||||
countryGroupAssignment.put("AT", new int[] {0, 1, 0, 0});
|
||||
countryGroupAssignment.put("AU", new int[] {0, 3, 0, 0});
|
||||
countryGroupAssignment.put("AW", new int[] {1, 1, 0, 4});
|
||||
countryGroupAssignment.put("AX", new int[] {0, 1, 0, 0});
|
||||
countryGroupAssignment.put("AZ", new int[] {3, 3, 2, 2});
|
||||
countryGroupAssignment.put("BA", new int[] {1, 1, 1, 2});
|
||||
countryGroupAssignment.put("BB", new int[] {0, 1, 0, 0});
|
||||
countryGroupAssignment.put("BD", new int[] {2, 1, 3, 2});
|
||||
countryGroupAssignment.put("BE", new int[] {0, 0, 0, 0});
|
||||
countryGroupAssignment.put("BF", new int[] {4, 4, 4, 1});
|
||||
countryGroupAssignment.put("BG", new int[] {0, 0, 0, 1});
|
||||
countryGroupAssignment.put("BH", new int[] {2, 1, 3, 4});
|
||||
countryGroupAssignment.put("BI", new int[] {4, 3, 4, 4});
|
||||
countryGroupAssignment.put("BJ", new int[] {4, 3, 4, 3});
|
||||
countryGroupAssignment.put("BL", new int[] {1, 0, 1, 2});
|
||||
countryGroupAssignment.put("BM", new int[] {1, 0, 0, 0});
|
||||
countryGroupAssignment.put("BN", new int[] {4, 3, 3, 3});
|
||||
countryGroupAssignment.put("BO", new int[] {2, 2, 1, 2});
|
||||
countryGroupAssignment.put("BQ", new int[] {1, 1, 2, 4});
|
||||
countryGroupAssignment.put("BR", new int[] {2, 3, 2, 2});
|
||||
countryGroupAssignment.put("BS", new int[] {1, 1, 0, 2});
|
||||
countryGroupAssignment.put("BT", new int[] {3, 0, 2, 1});
|
||||
countryGroupAssignment.put("BW", new int[] {4, 4, 2, 3});
|
||||
countryGroupAssignment.put("BY", new int[] {1, 1, 1, 1});
|
||||
countryGroupAssignment.put("BZ", new int[] {2, 3, 3, 1});
|
||||
countryGroupAssignment.put("CA", new int[] {0, 2, 2, 3});
|
||||
countryGroupAssignment.put("CD", new int[] {4, 4, 2, 1});
|
||||
countryGroupAssignment.put("CF", new int[] {4, 4, 3, 3});
|
||||
countryGroupAssignment.put("CG", new int[] {4, 4, 4, 4});
|
||||
countryGroupAssignment.put("CH", new int[] {0, 0, 0, 0});
|
||||
countryGroupAssignment.put("CI", new int[] {4, 4, 4, 4});
|
||||
countryGroupAssignment.put("CK", new int[] {2, 4, 2, 0});
|
||||
countryGroupAssignment.put("CL", new int[] {2, 2, 2, 3});
|
||||
countryGroupAssignment.put("CM", new int[] {3, 4, 3, 1});
|
||||
countryGroupAssignment.put("CN", new int[] {2, 0, 1, 2});
|
||||
countryGroupAssignment.put("CO", new int[] {2, 3, 2, 1});
|
||||
countryGroupAssignment.put("CR", new int[] {2, 2, 4, 4});
|
||||
countryGroupAssignment.put("CU", new int[] {4, 4, 4, 1});
|
||||
countryGroupAssignment.put("CV", new int[] {2, 2, 2, 4});
|
||||
countryGroupAssignment.put("CW", new int[] {1, 1, 0, 0});
|
||||
countryGroupAssignment.put("CX", new int[] {1, 2, 2, 2});
|
||||
countryGroupAssignment.put("CY", new int[] {1, 1, 0, 0});
|
||||
countryGroupAssignment.put("CZ", new int[] {0, 1, 0, 0});
|
||||
countryGroupAssignment.put("DE", new int[] {0, 2, 2, 2});
|
||||
countryGroupAssignment.put("DJ", new int[] {3, 4, 4, 0});
|
||||
countryGroupAssignment.put("DK", new int[] {0, 0, 0, 0});
|
||||
countryGroupAssignment.put("DM", new int[] {2, 0, 3, 4});
|
||||
countryGroupAssignment.put("DO", new int[] {3, 3, 4, 4});
|
||||
countryGroupAssignment.put("DZ", new int[] {3, 3, 4, 4});
|
||||
countryGroupAssignment.put("EC", new int[] {2, 3, 3, 1});
|
||||
countryGroupAssignment.put("EE", new int[] {0, 0, 0, 0});
|
||||
countryGroupAssignment.put("EG", new int[] {3, 3, 1, 1});
|
||||
countryGroupAssignment.put("EH", new int[] {2, 0, 2, 3});
|
||||
countryGroupAssignment.put("ER", new int[] {4, 2, 2, 2});
|
||||
countryGroupAssignment.put("ES", new int[] {0, 0, 1, 1});
|
||||
countryGroupAssignment.put("ET", new int[] {4, 4, 4, 0});
|
||||
countryGroupAssignment.put("FI", new int[] {0, 0, 1, 0});
|
||||
countryGroupAssignment.put("FJ", new int[] {3, 2, 3, 3});
|
||||
countryGroupAssignment.put("FK", new int[] {3, 4, 2, 1});
|
||||
countryGroupAssignment.put("FM", new int[] {4, 2, 4, 0});
|
||||
countryGroupAssignment.put("FO", new int[] {0, 0, 0, 1});
|
||||
countryGroupAssignment.put("FR", new int[] {1, 0, 2, 1});
|
||||
countryGroupAssignment.put("GA", new int[] {3, 3, 2, 1});
|
||||
countryGroupAssignment.put("GB", new int[] {0, 1, 3, 2});
|
||||
countryGroupAssignment.put("GD", new int[] {2, 0, 3, 0});
|
||||
countryGroupAssignment.put("GE", new int[] {1, 1, 0, 3});
|
||||
countryGroupAssignment.put("GF", new int[] {1, 2, 4, 4});
|
||||
countryGroupAssignment.put("GG", new int[] {0, 1, 0, 0});
|
||||
countryGroupAssignment.put("GH", new int[] {3, 2, 2, 2});
|
||||
countryGroupAssignment.put("GI", new int[] {0, 0, 0, 1});
|
||||
countryGroupAssignment.put("GL", new int[] {2, 4, 1, 4});
|
||||
countryGroupAssignment.put("GM", new int[] {4, 3, 3, 0});
|
||||
countryGroupAssignment.put("GN", new int[] {4, 4, 3, 4});
|
||||
countryGroupAssignment.put("GP", new int[] {2, 2, 1, 3});
|
||||
countryGroupAssignment.put("GQ", new int[] {4, 4, 3, 1});
|
||||
countryGroupAssignment.put("GR", new int[] {1, 1, 0, 1});
|
||||
countryGroupAssignment.put("GT", new int[] {3, 2, 3, 4});
|
||||
countryGroupAssignment.put("GU", new int[] {1, 0, 4, 4});
|
||||
countryGroupAssignment.put("GW", new int[] {4, 4, 4, 0});
|
||||
countryGroupAssignment.put("GY", new int[] {3, 4, 1, 0});
|
||||
countryGroupAssignment.put("HK", new int[] {0, 2, 3, 4});
|
||||
countryGroupAssignment.put("HN", new int[] {3, 3, 2, 2});
|
||||
countryGroupAssignment.put("HR", new int[] {1, 0, 0, 2});
|
||||
countryGroupAssignment.put("HT", new int[] {3, 3, 3, 3});
|
||||
countryGroupAssignment.put("HU", new int[] {0, 0, 1, 0});
|
||||
countryGroupAssignment.put("ID", new int[] {2, 3, 3, 4});
|
||||
countryGroupAssignment.put("IE", new int[] {0, 0, 1, 1});
|
||||
countryGroupAssignment.put("IL", new int[] {0, 1, 1, 3});
|
||||
countryGroupAssignment.put("IM", new int[] {0, 1, 0, 1});
|
||||
countryGroupAssignment.put("IN", new int[] {2, 3, 3, 4});
|
||||
countryGroupAssignment.put("IO", new int[] {4, 2, 2, 2});
|
||||
countryGroupAssignment.put("IQ", new int[] {3, 3, 4, 3});
|
||||
countryGroupAssignment.put("IR", new int[] {3, 2, 4, 4});
|
||||
countryGroupAssignment.put("IS", new int[] {0, 0, 0, 0});
|
||||
countryGroupAssignment.put("IT", new int[] {1, 0, 1, 3});
|
||||
countryGroupAssignment.put("JE", new int[] {0, 0, 0, 1});
|
||||
countryGroupAssignment.put("JM", new int[] {3, 3, 3, 2});
|
||||
countryGroupAssignment.put("JO", new int[] {1, 1, 1, 2});
|
||||
countryGroupAssignment.put("JP", new int[] {0, 1, 1, 2});
|
||||
countryGroupAssignment.put("KE", new int[] {3, 3, 3, 3});
|
||||
countryGroupAssignment.put("KG", new int[] {2, 2, 3, 3});
|
||||
countryGroupAssignment.put("KH", new int[] {1, 0, 4, 4});
|
||||
countryGroupAssignment.put("KI", new int[] {4, 4, 4, 4});
|
||||
countryGroupAssignment.put("KM", new int[] {4, 4, 2, 2});
|
||||
countryGroupAssignment.put("KN", new int[] {1, 0, 1, 3});
|
||||
countryGroupAssignment.put("KP", new int[] {1, 2, 2, 2});
|
||||
countryGroupAssignment.put("KR", new int[] {0, 4, 0, 2});
|
||||
countryGroupAssignment.put("KW", new int[] {1, 2, 1, 2});
|
||||
countryGroupAssignment.put("KY", new int[] {1, 1, 0, 2});
|
||||
countryGroupAssignment.put("KZ", new int[] {1, 2, 2, 3});
|
||||
countryGroupAssignment.put("LA", new int[] {3, 2, 2, 2});
|
||||
countryGroupAssignment.put("LB", new int[] {3, 2, 0, 0});
|
||||
countryGroupAssignment.put("LC", new int[] {2, 2, 1, 0});
|
||||
countryGroupAssignment.put("LI", new int[] {0, 0, 1, 2});
|
||||
countryGroupAssignment.put("LK", new int[] {1, 1, 2, 2});
|
||||
countryGroupAssignment.put("LR", new int[] {3, 4, 3, 1});
|
||||
countryGroupAssignment.put("LS", new int[] {3, 3, 2, 0});
|
||||
countryGroupAssignment.put("LT", new int[] {0, 0, 0, 1});
|
||||
countryGroupAssignment.put("LU", new int[] {0, 0, 1, 0});
|
||||
countryGroupAssignment.put("LV", new int[] {0, 0, 0, 0});
|
||||
countryGroupAssignment.put("LY", new int[] {4, 4, 4, 4});
|
||||
countryGroupAssignment.put("MA", new int[] {2, 1, 2, 2});
|
||||
countryGroupAssignment.put("MC", new int[] {1, 0, 1, 0});
|
||||
countryGroupAssignment.put("MD", new int[] {1, 1, 0, 0});
|
||||
countryGroupAssignment.put("ME", new int[] {1, 2, 2, 3});
|
||||
countryGroupAssignment.put("MF", new int[] {1, 4, 3, 3});
|
||||
countryGroupAssignment.put("MG", new int[] {3, 4, 1, 2});
|
||||
countryGroupAssignment.put("MH", new int[] {4, 0, 2, 3});
|
||||
countryGroupAssignment.put("MK", new int[] {1, 0, 0, 1});
|
||||
countryGroupAssignment.put("ML", new int[] {4, 4, 4, 4});
|
||||
countryGroupAssignment.put("MM", new int[] {2, 3, 1, 2});
|
||||
countryGroupAssignment.put("MN", new int[] {2, 2, 2, 4});
|
||||
countryGroupAssignment.put("MO", new int[] {0, 1, 4, 4});
|
||||
countryGroupAssignment.put("MP", new int[] {0, 0, 4, 4});
|
||||
countryGroupAssignment.put("MQ", new int[] {1, 1, 1, 3});
|
||||
countryGroupAssignment.put("MR", new int[] {4, 2, 4, 2});
|
||||
countryGroupAssignment.put("MS", new int[] {1, 2, 1, 2});
|
||||
countryGroupAssignment.put("MT", new int[] {0, 0, 0, 0});
|
||||
countryGroupAssignment.put("MU", new int[] {2, 2, 4, 4});
|
||||
countryGroupAssignment.put("MV", new int[] {4, 2, 0, 1});
|
||||
countryGroupAssignment.put("MW", new int[] {3, 2, 1, 1});
|
||||
countryGroupAssignment.put("MX", new int[] {2, 4, 3, 1});
|
||||
countryGroupAssignment.put("MY", new int[] {2, 3, 3, 3});
|
||||
countryGroupAssignment.put("MZ", new int[] {3, 3, 2, 4});
|
||||
countryGroupAssignment.put("NA", new int[] {4, 2, 1, 1});
|
||||
countryGroupAssignment.put("NC", new int[] {2, 1, 3, 3});
|
||||
countryGroupAssignment.put("NE", new int[] {4, 4, 4, 4});
|
||||
countryGroupAssignment.put("NF", new int[] {0, 2, 2, 2});
|
||||
countryGroupAssignment.put("NG", new int[] {3, 4, 2, 2});
|
||||
countryGroupAssignment.put("NI", new int[] {3, 4, 3, 3});
|
||||
countryGroupAssignment.put("NL", new int[] {0, 1, 3, 2});
|
||||
countryGroupAssignment.put("NO", new int[] {0, 0, 1, 0});
|
||||
countryGroupAssignment.put("NP", new int[] {2, 3, 2, 2});
|
||||
countryGroupAssignment.put("NR", new int[] {4, 3, 4, 1});
|
||||
countryGroupAssignment.put("NU", new int[] {4, 2, 2, 2});
|
||||
countryGroupAssignment.put("NZ", new int[] {0, 0, 0, 1});
|
||||
countryGroupAssignment.put("OM", new int[] {2, 2, 1, 3});
|
||||
countryGroupAssignment.put("PA", new int[] {1, 3, 2, 3});
|
||||
countryGroupAssignment.put("PE", new int[] {2, 2, 4, 4});
|
||||
countryGroupAssignment.put("PF", new int[] {2, 2, 0, 1});
|
||||
countryGroupAssignment.put("PG", new int[] {4, 4, 4, 4});
|
||||
countryGroupAssignment.put("PH", new int[] {3, 0, 4, 4});
|
||||
countryGroupAssignment.put("PK", new int[] {3, 3, 3, 3});
|
||||
countryGroupAssignment.put("PL", new int[] {1, 0, 1, 3});
|
||||
countryGroupAssignment.put("PM", new int[] {0, 2, 2, 3});
|
||||
countryGroupAssignment.put("PR", new int[] {2, 3, 4, 3});
|
||||
countryGroupAssignment.put("PS", new int[] {2, 3, 0, 4});
|
||||
countryGroupAssignment.put("PT", new int[] {1, 1, 1, 1});
|
||||
countryGroupAssignment.put("PW", new int[] {3, 2, 3, 0});
|
||||
countryGroupAssignment.put("PY", new int[] {2, 1, 3, 3});
|
||||
countryGroupAssignment.put("QA", new int[] {2, 3, 1, 2});
|
||||
countryGroupAssignment.put("RE", new int[] {1, 1, 2, 2});
|
||||
countryGroupAssignment.put("RO", new int[] {0, 1, 1, 3});
|
||||
countryGroupAssignment.put("RS", new int[] {1, 1, 0, 0});
|
||||
countryGroupAssignment.put("RU", new int[] {0, 1, 1, 1});
|
||||
countryGroupAssignment.put("RW", new int[] {3, 4, 3, 1});
|
||||
countryGroupAssignment.put("SA", new int[] {3, 2, 2, 3});
|
||||
countryGroupAssignment.put("SB", new int[] {4, 4, 3, 0});
|
||||
countryGroupAssignment.put("SC", new int[] {4, 2, 0, 1});
|
||||
countryGroupAssignment.put("SD", new int[] {3, 4, 4, 4});
|
||||
countryGroupAssignment.put("SE", new int[] {0, 0, 0, 0});
|
||||
countryGroupAssignment.put("SG", new int[] {1, 2, 3, 3});
|
||||
countryGroupAssignment.put("SH", new int[] {4, 2, 2, 2});
|
||||
countryGroupAssignment.put("SI", new int[] {0, 1, 0, 0});
|
||||
countryGroupAssignment.put("SJ", new int[] {3, 2, 0, 2});
|
||||
countryGroupAssignment.put("SK", new int[] {0, 1, 0, 1});
|
||||
countryGroupAssignment.put("SL", new int[] {4, 3, 2, 4});
|
||||
countryGroupAssignment.put("SM", new int[] {1, 0, 1, 1});
|
||||
countryGroupAssignment.put("SN", new int[] {4, 4, 4, 2});
|
||||
countryGroupAssignment.put("SO", new int[] {4, 4, 4, 3});
|
||||
countryGroupAssignment.put("SR", new int[] {3, 2, 2, 3});
|
||||
countryGroupAssignment.put("SS", new int[] {4, 3, 4, 2});
|
||||
countryGroupAssignment.put("ST", new int[] {3, 2, 2, 2});
|
||||
countryGroupAssignment.put("SV", new int[] {2, 3, 2, 3});
|
||||
countryGroupAssignment.put("SX", new int[] {2, 4, 2, 0});
|
||||
countryGroupAssignment.put("SY", new int[] {4, 4, 2, 0});
|
||||
countryGroupAssignment.put("SZ", new int[] {3, 4, 1, 1});
|
||||
countryGroupAssignment.put("TC", new int[] {2, 1, 2, 1});
|
||||
countryGroupAssignment.put("TD", new int[] {4, 4, 4, 3});
|
||||
countryGroupAssignment.put("TG", new int[] {3, 2, 2, 0});
|
||||
countryGroupAssignment.put("TH", new int[] {1, 3, 4, 4});
|
||||
countryGroupAssignment.put("TJ", new int[] {4, 4, 4, 4});
|
||||
countryGroupAssignment.put("TL", new int[] {4, 2, 4, 4});
|
||||
countryGroupAssignment.put("TM", new int[] {4, 1, 3, 3});
|
||||
countryGroupAssignment.put("TN", new int[] {2, 2, 1, 2});
|
||||
countryGroupAssignment.put("TO", new int[] {2, 3, 3, 1});
|
||||
countryGroupAssignment.put("TR", new int[] {1, 2, 0, 2});
|
||||
countryGroupAssignment.put("TT", new int[] {2, 1, 1, 0});
|
||||
countryGroupAssignment.put("TV", new int[] {4, 2, 2, 4});
|
||||
countryGroupAssignment.put("TW", new int[] {0, 0, 0, 1});
|
||||
countryGroupAssignment.put("TZ", new int[] {3, 3, 3, 2});
|
||||
countryGroupAssignment.put("UA", new int[] {0, 2, 1, 3});
|
||||
countryGroupAssignment.put("UG", new int[] {4, 3, 2, 2});
|
||||
countryGroupAssignment.put("US", new int[] {0, 1, 3, 3});
|
||||
countryGroupAssignment.put("UY", new int[] {2, 1, 2, 2});
|
||||
countryGroupAssignment.put("UZ", new int[] {4, 3, 2, 4});
|
||||
countryGroupAssignment.put("VA", new int[] {1, 2, 2, 2});
|
||||
countryGroupAssignment.put("VC", new int[] {2, 0, 3, 2});
|
||||
countryGroupAssignment.put("VE", new int[] {3, 4, 4, 3});
|
||||
countryGroupAssignment.put("VG", new int[] {3, 1, 3, 4});
|
||||
countryGroupAssignment.put("VI", new int[] {1, 0, 2, 4});
|
||||
countryGroupAssignment.put("VN", new int[] {0, 2, 4, 4});
|
||||
countryGroupAssignment.put("VU", new int[] {4, 1, 3, 2});
|
||||
countryGroupAssignment.put("WS", new int[] {3, 2, 3, 0});
|
||||
countryGroupAssignment.put("XK", new int[] {1, 2, 1, 0});
|
||||
countryGroupAssignment.put("YE", new int[] {4, 4, 4, 2});
|
||||
countryGroupAssignment.put("YT", new int[] {3, 1, 1, 2});
|
||||
countryGroupAssignment.put("ZA", new int[] {2, 3, 1, 2});
|
||||
countryGroupAssignment.put("ZM", new int[] {3, 3, 3, 1});
|
||||
countryGroupAssignment.put("ZW", new int[] {3, 3, 2, 1});
|
||||
return Collections.unmodifiableMap(countryGroupAssignment);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue