Commit fd4998bc by zsmatyas Committed by GitHub

Merge branch 'dev-v2' into dev-v2

parents 2621d962 a6c1dbe1
Showing with 1682 additions and 892 deletions
...@@ -37,6 +37,12 @@ local.properties ...@@ -37,6 +37,12 @@ local.properties
proguard.cfg proguard.cfg
proguard-project.txt proguard-project.txt
# Bazel
bazel-bin
bazel-genfiles
bazel-out
bazel-testlogs
# Other # Other
.DS_Store .DS_Store
cmake-build-debug cmake-build-debug
...@@ -66,3 +72,6 @@ extensions/cronet/jniLibs/* ...@@ -66,3 +72,6 @@ extensions/cronet/jniLibs/*
extensions/cronet/libs/* extensions/cronet/libs/*
!extensions/cronet/libs/README.md !extensions/cronet/libs/README.md
# Cast receiver
cast_receiver_app/external-js
cast_receiver_app/bazel-cast_receiver_app
...@@ -44,6 +44,12 @@ local.properties ...@@ -44,6 +44,12 @@ local.properties
proguard.cfg proguard.cfg
proguard-project.txt proguard-project.txt
# Bazel
bazel-bin
bazel-genfiles
bazel-out
bazel-testlogs
# Other # Other
.DS_Store .DS_Store
cmake-build-debug cmake-build-debug
...@@ -69,3 +75,7 @@ extensions/cronet/jniLibs/* ...@@ -69,3 +75,7 @@ extensions/cronet/jniLibs/*
!extensions/cronet/jniLibs/README.md !extensions/cronet/jniLibs/README.md
extensions/cronet/libs/* extensions/cronet/libs/*
!extensions/cronet/libs/README.md !extensions/cronet/libs/README.md
# Cast receiver
cast_receiver_app/external-js
cast_receiver_app/bazel-cast_receiver_app
...@@ -27,6 +27,8 @@ repository and depend on the modules locally. ...@@ -27,6 +27,8 @@ repository and depend on the modules locally.
### From JCenter ### ### From JCenter ###
#### 1. Add repositories ####
The easiest way to get started using ExoPlayer is to add it as a gradle The easiest way to get started using ExoPlayer is to add it as a gradle
dependency. You need to make sure you have the Google and JCenter 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: included in the `build.gradle` file in the root of your project:
...@@ -38,6 +40,8 @@ repositories { ...@@ -38,6 +40,8 @@ repositories {
} }
``` ```
#### 2. Add ExoPlayer module dependencies ####
Next add a dependency in the `build.gradle` file of your app module. The Next add a dependency in the `build.gradle` file of your app module. The
following will add a dependency to the full library: following will add a dependency to the full library:
...@@ -45,15 +49,7 @@ following will add a dependency to the full library: ...@@ -45,15 +49,7 @@ following will add a dependency to the full library:
implementation 'com.google.android.exoplayer:exoplayer:2.X.X' implementation 'com.google.android.exoplayer:exoplayer:2.X.X'
``` ```
where `2.X.X` is your preferred version. If not enabled already, you also need where `2.X.X` is your preferred version.
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 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 modules that you actually need. For example the following will add dependencies
...@@ -87,6 +83,32 @@ JCenter can be found on [Bintray][]. ...@@ -87,6 +83,32 @@ JCenter can be found on [Bintray][].
[extensions directory]: https://github.com/google/ExoPlayer/tree/release-v2/extensions/ [extensions directory]: https://github.com/google/ExoPlayer/tree/release-v2/extensions/
[Bintray]: https://bintray.com/google/exoplayer [Bintray]: https://bintray.com/google/exoplayer
#### 3. Turn on Java 8 support ####
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
}
```
Note that if you want to use Java 8 features in your own code, the following
additional options need to be set:
```gradle
// For Java compilers:
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
}
// For Kotlin compilers:
kotlinOptions {
jvmTarget = JavaVersion.VERSION_1_8
}
```
### Locally ### ### Locally ###
Cloning the repository and depending on the modules locally is required when Cloning the repository and depending on the modules locally is required when
......
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
### dev-v2 (not yet released) ### ### dev-v2 (not yet released) ###
* `ExtractorMediaSource` renamed to `ProgressiveMediaSource`.
* Support for playing spherical videos on Daydream. * Support for playing spherical videos on Daydream.
* Improve decoder re-use between playbacks. TODO: Write and link a blog post * Improve decoder re-use between playbacks. TODO: Write and link a blog post
here ([#2826](https://github.com/google/ExoPlayer/issues/2826)). here ([#2826](https://github.com/google/ExoPlayer/issues/2826)).
...@@ -17,7 +18,8 @@ ...@@ -17,7 +18,8 @@
* Add `setStreamKeys` method to factories of DASH, SmoothStreaming and HLS * Add `setStreamKeys` method to factories of DASH, SmoothStreaming and HLS
media sources to simplify filtering by downloaded streams. media sources to simplify filtering by downloaded streams.
* Caching: * Caching:
* Improve performance of `SimpleCache`. * Improve performance of `SimpleCache`
([#4253](https://github.com/google/ExoPlayer/issues/4253)).
* Cache data with unknown length by default. The previous flag to opt in to * Cache data with unknown length by default. The previous flag to opt in to
this behavior (`DataSpec.FLAG_ALLOW_CACHING_UNKNOWN_LENGTH`) has been this behavior (`DataSpec.FLAG_ALLOW_CACHING_UNKNOWN_LENGTH`) has been
replaced with an opt out flag replaced with an opt out flag
...@@ -27,20 +29,70 @@ ...@@ -27,20 +29,70 @@
* Rename TaskState to DownloadState. * Rename TaskState to DownloadState.
* Add new states to DownloadState. * Add new states to DownloadState.
* Replace DownloadState.action with DownloadAction fields. * Replace DownloadState.action with DownloadAction fields.
* DRM: Fix black flicker when keys rotate in DRM protected content
([#3561](https://github.com/google/ExoPlayer/issues/3561)).
* Add support for SHOUTcast ICY metadata * Add support for SHOUTcast ICY metadata
([#3735](https://github.com/google/ExoPlayer/issues/3735)). ([#3735](https://github.com/google/ExoPlayer/issues/3735)).
* IMA extension: * CEA-608: Improved conformance to the specification
* Clear ads loader listeners on release ([#3860](https://github.com/google/ExoPlayer/issues/3860)).
([#4114](https://github.com/google/ExoPlayer/issues/4114)). * IMA extension: Require setting the `Player` on `AdsLoader` instances before
* Require setting the `Player` on `AdsLoader` instances before playback. playback.
* Remove `player` and `isTopLevelSource` parameters from `MediaSource.prepare`.
* VP9 extension: Remove RGB output mode and libyuv dependency, and switch to
surface YUV output as the default. Remove constructor parameters `scaleToFit`
and `useSurfaceYuvOutput`.
* Change signature of `PlayerNotificationManager.NotificationListener` to better
fit service requirements. Remove ability to set a custom stop action.
* Fix issues with flickering notifications on KitKat.
`PlayerNotificationManager` has been fixed. Apps using
`DownloadNotificationUtil` should switch to using
`DownloadNotificationHelper`.
### 2.9.5 ###
* HLS: Parse `CHANNELS` attribute from `EXT-X-MEDIA` tag.
* ConcatenatingMediaSource:
* Add `Handler` parameter to methods that take a callback `Runnable`.
* Fix issue with dropped messages when releasing the source
([#5464](https://github.com/google/ExoPlayer/issues/5464)).
* ExtractorMediaSource: Fix issue that could cause the player to get stuck
buffering at the end of the media.
* PlayerView: Fix issue preventing `OnClickListener` from receiving events
([#5433](https://github.com/google/ExoPlayer/issues/5433)).
* IMA extension: Upgrade IMA dependency to 3.10.6.
* Cronet extension: Upgrade Cronet dependency to 71.3578.98.
* OkHttp extension: Upgrade OkHttp dependency to 3.12.1.
* MP3: Wider fix for issue where streams would play twice on some Samsung
devices ([#4519](https://github.com/google/ExoPlayer/issues/4519)).
### 2.9.4 ###
* IMA extension: Clear ads loader listeners on release
([#4114](https://github.com/google/ExoPlayer/issues/4114)).
* SmoothStreaming: Fix support for subtitles in DRM protected streams
([#5378](https://github.com/google/ExoPlayer/issues/5378)).
* FFmpeg extension: Treat invalid data errors as non-fatal to match the behavior * FFmpeg extension: Treat invalid data errors as non-fatal to match the behavior
of MediaCodec ([#5293](https://github.com/google/ExoPlayer/issues/5293)). of MediaCodec ([#5293](https://github.com/google/ExoPlayer/issues/5293)).
* GVR extension: upgrade GVR SDK dependency to 1.190.0.
* Associate fatal player errors of type SOURCE with the loading source in
`AnalyticsListener.EventTime`
([#5407](https://github.com/google/ExoPlayer/issues/5407)).
* Add `startPositionUs` to `MediaSource.createPeriod`. This fixes an issue where
using lazy preparation in `ConcatenatingMediaSource` with an
`ExtractorMediaSource` overrides initial seek positions
([#5350](https://github.com/google/ExoPlayer/issues/5350)).
* Add subtext to the `MediaDescriptionAdapter` of the
`PlayerNotificationManager`.
* Add workaround for video quality problems with Amlogic decoders
([#5003](https://github.com/google/ExoPlayer/issues/5003)).
* Fix issue where sending callbacks for playlist changes may cause problems * Fix issue where sending callbacks for playlist changes may cause problems
because of parallel player access because of parallel player access
([#5240](https://github.com/google/ExoPlayer/issues/5240)). ([#5240](https://github.com/google/ExoPlayer/issues/5240)).
* Add `Handler` parameter to `ConcatenatingMediaSource` methods which take a * Fix issue with reusing a `ClippingMediaSource` with an inner
callback `Runnable`. `ExtractorMediaSource` and a non-zero start position
* Remove `player` and `isTopLevelSource` parameters from `MediaSource.prepare`. ([#5351](https://github.com/google/ExoPlayer/issues/5351)).
* Fix issue where uneven track durations in MP4 streams can cause OOM problems
([#3670](https://github.com/google/ExoPlayer/issues/3670)).
### 2.9.3 ### ### 2.9.3 ###
...@@ -1173,7 +1225,7 @@ ...@@ -1173,7 +1225,7 @@
[here](https://medium.com/google-exoplayer/customizing-exoplayers-ui-components-728cf55ee07a#.9ewjg7avi). [here](https://medium.com/google-exoplayer/customizing-exoplayers-ui-components-728cf55ee07a#.9ewjg7avi).
* Robustness improvements when handling MediaSource timeline changes and * Robustness improvements when handling MediaSource timeline changes and
MediaPeriod transitions. MediaPeriod transitions.
* EIA608: Support for caption styling and positioning. * CEA-608: Support for caption styling and positioning.
* MPEG-TS: Improved support: * MPEG-TS: Improved support:
* Support injection of custom TS payload readers. * Support injection of custom TS payload readers.
* Support injection of custom section payload readers. * Support injection of custom section payload readers.
...@@ -1417,8 +1469,8 @@ V2 release. ...@@ -1417,8 +1469,8 @@ V2 release.
(#801). (#801).
* MP3: Fix playback of some streams when stream length is unknown. * MP3: Fix playback of some streams when stream length is unknown.
* ID3: Support multiple frames of the same type in a single tag. * ID3: Support multiple frames of the same type in a single tag.
* EIA608: Correctly handle repeated control characters, fixing an issue in which * CEA-608: Correctly handle repeated control characters, fixing an issue in
captions would immediately disappear. which captions would immediately disappear.
* AVC3: Fix decoder failures on some MediaTek devices in the case where the * AVC3: Fix decoder failures on some MediaTek devices in the case where the
first buffer fed to the decoder does not start with SPS/PPS NAL units. first buffer fed to the decoder does not start with SPS/PPS NAL units.
* Misc bug fixes. * Misc bug fixes.
......
...@@ -13,13 +13,9 @@ ...@@ -13,13 +13,9 @@
// limitations under the License. // limitations under the License.
project.ext { project.ext {
// ExoPlayer version and version code. // ExoPlayer version and version code.
releaseVersion = '2.9.3' releaseVersion = '2.9.5'
releaseVersionCode = 2009003 releaseVersionCode = 2009005
// Important: ExoPlayer specifies a minSdkVersion of 14 because various minSdkVersion = 16
// components provided by the library may be of use on older devices.
// However, please note that the core media playback functionality provided
// by the library requires API level 16 or greater.
minSdkVersion = 14
targetSdkVersion = 28 targetSdkVersion = 28
compileSdkVersion = 28 compileSdkVersion = 28
buildToolsVersion = '28.0.2' buildToolsVersion = '28.0.2'
......
...@@ -26,7 +26,7 @@ android { ...@@ -26,7 +26,7 @@ android {
defaultConfig { defaultConfig {
versionName project.ext.releaseVersion versionName project.ext.releaseVersion
versionCode project.ext.releaseVersionCode versionCode project.ext.releaseVersionCode
minSdkVersion 16 minSdkVersion project.ext.minSdkVersion
targetSdkVersion project.ext.targetSdkVersion targetSdkVersion project.ext.targetSdkVersion
} }
......
...@@ -35,8 +35,8 @@ import com.google.android.exoplayer2.ext.cast.CastPlayer; ...@@ -35,8 +35,8 @@ import com.google.android.exoplayer2.ext.cast.CastPlayer;
import com.google.android.exoplayer2.ext.cast.MediaItem; import com.google.android.exoplayer2.ext.cast.MediaItem;
import com.google.android.exoplayer2.ext.cast.SessionAvailabilityListener; import com.google.android.exoplayer2.ext.cast.SessionAvailabilityListener;
import com.google.android.exoplayer2.source.ConcatenatingMediaSource; import com.google.android.exoplayer2.source.ConcatenatingMediaSource;
import com.google.android.exoplayer2.source.ExtractorMediaSource;
import com.google.android.exoplayer2.source.MediaSource; import com.google.android.exoplayer2.source.MediaSource;
import com.google.android.exoplayer2.source.ProgressiveMediaSource;
import com.google.android.exoplayer2.source.dash.DashMediaSource; import com.google.android.exoplayer2.source.dash.DashMediaSource;
import com.google.android.exoplayer2.source.hls.HlsMediaSource; import com.google.android.exoplayer2.source.hls.HlsMediaSource;
import com.google.android.exoplayer2.source.smoothstreaming.SsMediaSource; import com.google.android.exoplayer2.source.smoothstreaming.SsMediaSource;
...@@ -63,7 +63,7 @@ import java.util.ArrayList; ...@@ -63,7 +63,7 @@ import java.util.ArrayList;
private final SimpleExoPlayer exoPlayer; private final SimpleExoPlayer exoPlayer;
private final CastPlayer castPlayer; private final CastPlayer castPlayer;
private final ArrayList<MediaItem> mediaQueue; private final ArrayList<MediaItem> mediaQueue;
private final QueuePositionListener queuePositionListener; private final QueueChangesListener queueChangesListener;
private final ConcatenatingMediaSource concatenatingMediaSource; private final ConcatenatingMediaSource concatenatingMediaSource;
private boolean castMediaQueueCreationPending; private boolean castMediaQueueCreationPending;
...@@ -71,32 +71,21 @@ import java.util.ArrayList; ...@@ -71,32 +71,21 @@ import java.util.ArrayList;
private Player currentPlayer; private Player currentPlayer;
/** /**
* @param queuePositionListener A {@link QueuePositionListener} for queue position changes. * Creates a new manager for {@link SimpleExoPlayer} and {@link CastPlayer}.
*
* @param queueChangesListener A {@link QueueChangesListener} for queue position changes.
* @param localPlayerView The {@link PlayerView} for local playback. * @param localPlayerView The {@link PlayerView} for local playback.
* @param castControlView The {@link PlayerControlView} to control remote playback. * @param castControlView The {@link PlayerControlView} to control remote playback.
* @param context A {@link Context}. * @param context A {@link Context}.
* @param castContext The {@link CastContext}. * @param castContext The {@link CastContext}.
*/ */
public static DefaultReceiverPlayerManager createPlayerManager( public DefaultReceiverPlayerManager(
QueuePositionListener queuePositionListener, QueueChangesListener queueChangesListener,
PlayerView localPlayerView,
PlayerControlView castControlView,
Context context,
CastContext castContext) {
DefaultReceiverPlayerManager defaultReceiverPlayerManager =
new DefaultReceiverPlayerManager(
queuePositionListener, localPlayerView, castControlView, context, castContext);
defaultReceiverPlayerManager.init();
return defaultReceiverPlayerManager;
}
private DefaultReceiverPlayerManager(
QueuePositionListener queuePositionListener,
PlayerView localPlayerView, PlayerView localPlayerView,
PlayerControlView castControlView, PlayerControlView castControlView,
Context context, Context context,
CastContext castContext) { CastContext castContext) {
this.queuePositionListener = queuePositionListener; this.queueChangesListener = queueChangesListener;
this.localPlayerView = localPlayerView; this.localPlayerView = localPlayerView;
this.castControlView = castControlView; this.castControlView = castControlView;
mediaQueue = new ArrayList<>(); mediaQueue = new ArrayList<>();
...@@ -113,6 +102,8 @@ import java.util.ArrayList; ...@@ -113,6 +102,8 @@ import java.util.ArrayList;
castPlayer.addListener(this); castPlayer.addListener(this);
castPlayer.setSessionAvailabilityListener(this); castPlayer.setSessionAvailabilityListener(this);
castControlView.setPlayer(castPlayer); castControlView.setPlayer(castPlayer);
setCurrentPlayer(castPlayer.isCastSessionAvailable() ? castPlayer : exoPlayer);
} }
// Queue manipulation methods. // Queue manipulation methods.
...@@ -287,10 +278,6 @@ import java.util.ArrayList; ...@@ -287,10 +278,6 @@ import java.util.ArrayList;
// Internal methods. // Internal methods.
private void init() {
setCurrentPlayer(castPlayer.isCastSessionAvailable() ? castPlayer : exoPlayer);
}
private void updateCurrentItemIndex() { private void updateCurrentItemIndex() {
int playbackState = currentPlayer.getPlaybackState(); int playbackState = currentPlayer.getPlaybackState();
maybeSetCurrentItemAndNotify( maybeSetCurrentItemAndNotify(
...@@ -372,7 +359,7 @@ import java.util.ArrayList; ...@@ -372,7 +359,7 @@ import java.util.ArrayList;
if (this.currentItemIndex != currentItemIndex) { if (this.currentItemIndex != currentItemIndex) {
int oldIndex = this.currentItemIndex; int oldIndex = this.currentItemIndex;
this.currentItemIndex = currentItemIndex; this.currentItemIndex = currentItemIndex;
queuePositionListener.onQueuePositionChanged(oldIndex, currentItemIndex); queueChangesListener.onQueuePositionChanged(oldIndex, currentItemIndex);
} }
} }
...@@ -386,7 +373,7 @@ import java.util.ArrayList; ...@@ -386,7 +373,7 @@ import java.util.ArrayList;
case DemoUtil.MIME_TYPE_HLS: case DemoUtil.MIME_TYPE_HLS:
return new HlsMediaSource.Factory(DATA_SOURCE_FACTORY).createMediaSource(uri); return new HlsMediaSource.Factory(DATA_SOURCE_FACTORY).createMediaSource(uri);
case DemoUtil.MIME_TYPE_VIDEO_MP4: case DemoUtil.MIME_TYPE_VIDEO_MP4:
return new ExtractorMediaSource.Factory(DATA_SOURCE_FACTORY).createMediaSource(uri); return new ProgressiveMediaSource.Factory(DATA_SOURCE_FACTORY).createMediaSource(uri);
default: { default: {
throw new IllegalStateException("Unsupported type: " + item.mimeType); throw new IllegalStateException("Unsupported type: " + item.mimeType);
} }
......
...@@ -15,10 +15,14 @@ ...@@ -15,10 +15,14 @@
*/ */
package com.google.android.exoplayer2.castdemo; package com.google.android.exoplayer2.castdemo;
import android.net.Uri;
import android.support.annotation.Nullable;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.MimeTypes;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.UUID;
/** Utility methods and constants for the Cast demo application. */ /** Utility methods and constants for the Cast demo application. */
/* package */ final class DemoUtil { /* package */ final class DemoUtil {
...@@ -32,6 +36,16 @@ import java.util.List; ...@@ -32,6 +36,16 @@ import java.util.List;
public final String name; public final String name;
/** The mime type of the sample media content. */ /** The mime type of the sample media content. */
public final String mimeType; public final String mimeType;
/**
* The {@link UUID} of the DRM scheme that protects the content, or null if the content is not
* DRM-protected.
*/
@Nullable public final UUID drmSchemeUuid;
/**
* The url from which players should obtain DRM licenses, or null if the content is not
* DRM-protected.
*/
@Nullable public final Uri licenseServerUri;
/** /**
* @param uri See {@link #uri}. * @param uri See {@link #uri}.
...@@ -39,9 +53,21 @@ import java.util.List; ...@@ -39,9 +53,21 @@ import java.util.List;
* @param mimeType See {@link #mimeType}. * @param mimeType See {@link #mimeType}.
*/ */
public Sample(String uri, String name, String mimeType) { public Sample(String uri, String name, String mimeType) {
this(uri, name, mimeType, /* drmSchemeUuid= */ null, /* licenseServerUriString= */ null);
}
public Sample(
String uri,
String name,
String mimeType,
@Nullable UUID drmSchemeUuid,
@Nullable String licenseServerUriString) {
this.uri = uri; this.uri = uri;
this.name = name; this.name = name;
this.mimeType = mimeType; this.mimeType = mimeType;
this.drmSchemeUuid = drmSchemeUuid;
this.licenseServerUri =
licenseServerUriString != null ? Uri.parse(licenseServerUriString) : null;
} }
@Override @Override
...@@ -62,25 +88,15 @@ import java.util.List; ...@@ -62,25 +88,15 @@ import java.util.List;
// App samples. // App samples.
ArrayList<Sample> samples = new ArrayList<>(); ArrayList<Sample> samples = new ArrayList<>();
// Clear content.
samples.add( samples.add(
new Sample( new Sample(
"https://storage.googleapis.com/wvmedia/clear/h264/tears/tears.mpd", "https://storage.googleapis.com/wvmedia/clear/h264/tears/tears.mpd",
"DASH (clear,MP4,H264)", "Clear DASH: Tears",
MIME_TYPE_DASH)); MIME_TYPE_DASH));
samples.add( samples.add(
new Sample( new Sample(
"https://commondatastorage.googleapis.com/gtv-videos-bucket/CastVideos/" "https://html5demos.com/assets/dizzy.mp4", "Clear MP4: Dizzy", MIME_TYPE_VIDEO_MP4));
+ "hls/TearsOfSteel.m3u8",
"Tears of Steel (HLS)",
MIME_TYPE_HLS));
samples.add(
new Sample(
"https://devstreaming-cdn.apple.com/videos/streaming/examples/bipbop_4x3"
+ "/bipbop_4x3_variant.m3u8",
"HLS Basic (TS)",
MIME_TYPE_HLS));
samples.add(
new Sample("https://html5demos.com/assets/dizzy.mp4", "Dizzy (MP4)", MIME_TYPE_VIDEO_MP4));
SAMPLES = Collections.unmodifiableList(samples); SAMPLES = Collections.unmodifiableList(samples);
} }
......
...@@ -42,13 +42,14 @@ import com.google.android.gms.cast.CastMediaControlIntent; ...@@ -42,13 +42,14 @@ import com.google.android.gms.cast.CastMediaControlIntent;
import com.google.android.gms.cast.framework.CastButtonFactory; import com.google.android.gms.cast.framework.CastButtonFactory;
import com.google.android.gms.cast.framework.CastContext; import com.google.android.gms.cast.framework.CastContext;
import com.google.android.gms.dynamite.DynamiteModule; import com.google.android.gms.dynamite.DynamiteModule;
import java.util.Collections;
/** /**
* An activity that plays video using {@link SimpleExoPlayer} and supports casting using ExoPlayer's * An activity that plays video using {@link SimpleExoPlayer} and supports casting using ExoPlayer's
* Cast extension. * Cast extension.
*/ */
public class MainActivity extends AppCompatActivity public class MainActivity extends AppCompatActivity
implements OnClickListener, PlayerManager.QueuePositionListener { implements OnClickListener, PlayerManager.QueueChangesListener {
private final MediaItem.Builder mediaItemBuilder; private final MediaItem.Builder mediaItemBuilder;
...@@ -120,8 +121,8 @@ public class MainActivity extends AppCompatActivity ...@@ -120,8 +121,8 @@ public class MainActivity extends AppCompatActivity
switch (applicationId) { switch (applicationId) {
case CastMediaControlIntent.DEFAULT_MEDIA_RECEIVER_APPLICATION_ID: case CastMediaControlIntent.DEFAULT_MEDIA_RECEIVER_APPLICATION_ID:
playerManager = playerManager =
DefaultReceiverPlayerManager.createPlayerManager( new DefaultReceiverPlayerManager(
/* queuePositionListener= */ this, /* queueChangesListener= */ this,
localPlayerView, localPlayerView,
castControlView, castControlView,
/* context= */ this, /* context= */ this,
...@@ -161,7 +162,7 @@ public class MainActivity extends AppCompatActivity ...@@ -161,7 +162,7 @@ public class MainActivity extends AppCompatActivity
.show(); .show();
} }
// PlayerManager.QueuePositionListener implementation. // PlayerManager.QueueChangesListener implementation.
@Override @Override
public void onQueuePositionChanged(int previousIndex, int newIndex) { public void onQueuePositionChanged(int previousIndex, int newIndex) {
...@@ -173,6 +174,11 @@ public class MainActivity extends AppCompatActivity ...@@ -173,6 +174,11 @@ public class MainActivity extends AppCompatActivity
} }
} }
@Override
public void onQueueContentsExternallyChanged() {
mediaQueueListAdapter.notifyDataSetChanged();
}
// Internal methods. // Internal methods.
private View buildSampleListView() { private View buildSampleListView() {
...@@ -182,13 +188,18 @@ public class MainActivity extends AppCompatActivity ...@@ -182,13 +188,18 @@ public class MainActivity extends AppCompatActivity
sampleList.setOnItemClickListener( sampleList.setOnItemClickListener(
(parent, view, position, id) -> { (parent, view, position, id) -> {
DemoUtil.Sample sample = DemoUtil.SAMPLES.get(position); DemoUtil.Sample sample = DemoUtil.SAMPLES.get(position);
playerManager.addItem( mediaItemBuilder
mediaItemBuilder .clear()
.clear() .setMedia(sample.uri)
.setMedia(sample.uri) .setTitle(sample.name)
.setTitle(sample.name) .setMimeType(sample.mimeType);
.setMimeType(sample.mimeType) if (sample.drmSchemeUuid != null) {
.build()); mediaItemBuilder.setDrmSchemes(
Collections.singletonList(
new MediaItem.DrmScheme(
sample.drmSchemeUuid, new MediaItem.UriBundle(sample.licenseServerUri))));
}
playerManager.addItem(mediaItemBuilder.build());
mediaQueueListAdapter.notifyItemInserted(playerManager.getMediaQueueSize() - 1); mediaQueueListAdapter.notifyItemInserted(playerManager.getMediaQueueSize() - 1);
}); });
return dialogList; return dialogList;
...@@ -268,6 +279,8 @@ public class MainActivity extends AppCompatActivity ...@@ -268,6 +279,8 @@ public class MainActivity extends AppCompatActivity
int position = viewHolder.getAdapterPosition(); int position = viewHolder.getAdapterPosition();
if (playerManager.removeItem(position)) { if (playerManager.removeItem(position)) {
mediaQueueListAdapter.notifyItemRemoved(position); mediaQueueListAdapter.notifyItemRemoved(position);
// Update whichever item took its place, in case it became the new selected item.
mediaQueueListAdapter.notifyItemChanged(position);
} }
} }
......
...@@ -22,14 +22,14 @@ import com.google.android.exoplayer2.ext.cast.MediaItem; ...@@ -22,14 +22,14 @@ import com.google.android.exoplayer2.ext.cast.MediaItem;
/** Manages the players in the Cast demo app. */ /** Manages the players in the Cast demo app. */
interface PlayerManager { interface PlayerManager {
/** Listener for changes in the media queue playback position. */ /** Listener for changes in the media queue. */
interface QueuePositionListener { interface QueueChangesListener {
/** /** Called when the currently played item of the media queue changes. */
* Called when the currently played item of the media queue changes.
*/
void onQueuePositionChanged(int previousIndex, int newIndex); void onQueuePositionChanged(int previousIndex, int newIndex);
/** Called when the media queue changes due to modifications not caused by this manager. */
void onQueueContentsExternallyChanged();
} }
/** Redirects the given {@code keyEvent} to the active player. */ /** Redirects the given {@code keyEvent} to the active player. */
......
...@@ -26,7 +26,7 @@ android { ...@@ -26,7 +26,7 @@ android {
defaultConfig { defaultConfig {
versionName project.ext.releaseVersion versionName project.ext.releaseVersion
versionCode project.ext.releaseVersionCode versionCode project.ext.releaseVersionCode
minSdkVersion 16 minSdkVersion project.ext.minSdkVersion
targetSdkVersion project.ext.targetSdkVersion targetSdkVersion project.ext.targetSdkVersion
} }
......
...@@ -23,16 +23,12 @@ import com.google.android.exoplayer2.ExoPlayer; ...@@ -23,16 +23,12 @@ import com.google.android.exoplayer2.ExoPlayer;
import com.google.android.exoplayer2.ExoPlayerFactory; import com.google.android.exoplayer2.ExoPlayerFactory;
import com.google.android.exoplayer2.SimpleExoPlayer; import com.google.android.exoplayer2.SimpleExoPlayer;
import com.google.android.exoplayer2.ext.ima.ImaAdsLoader; import com.google.android.exoplayer2.ext.ima.ImaAdsLoader;
import com.google.android.exoplayer2.source.ExtractorMediaSource;
import com.google.android.exoplayer2.source.MediaSource; import com.google.android.exoplayer2.source.MediaSource;
import com.google.android.exoplayer2.source.ProgressiveMediaSource;
import com.google.android.exoplayer2.source.ads.AdsMediaSource; import com.google.android.exoplayer2.source.ads.AdsMediaSource;
import com.google.android.exoplayer2.source.dash.DashMediaSource; import com.google.android.exoplayer2.source.dash.DashMediaSource;
import com.google.android.exoplayer2.source.hls.HlsMediaSource; import com.google.android.exoplayer2.source.hls.HlsMediaSource;
import com.google.android.exoplayer2.source.smoothstreaming.SsMediaSource; import com.google.android.exoplayer2.source.smoothstreaming.SsMediaSource;
import com.google.android.exoplayer2.trackselection.AdaptiveTrackSelection;
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
import com.google.android.exoplayer2.trackselection.TrackSelection;
import com.google.android.exoplayer2.trackselection.TrackSelector;
import com.google.android.exoplayer2.ui.PlayerView; import com.google.android.exoplayer2.ui.PlayerView;
import com.google.android.exoplayer2.upstream.DataSource; import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory; import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory;
...@@ -56,14 +52,9 @@ import com.google.android.exoplayer2.util.Util; ...@@ -56,14 +52,9 @@ import com.google.android.exoplayer2.util.Util;
} }
public void init(Context context, PlayerView playerView) { public void init(Context context, PlayerView playerView) {
// Create a default track selector.
TrackSelection.Factory videoTrackSelectionFactory = new AdaptiveTrackSelection.Factory();
TrackSelector trackSelector = new DefaultTrackSelector(videoTrackSelectionFactory);
// Create a player instance. // Create a player instance.
player = ExoPlayerFactory.newSimpleInstance(context, trackSelector); player = ExoPlayerFactory.newSimpleInstance(context);
adsLoader.setPlayer(player);
// Bind the player to the view.
playerView.setPlayer(player); playerView.setPlayer(player);
// This is the MediaSource representing the content media (i.e. not the ad). // This is the MediaSource representing the content media (i.e. not the ad).
...@@ -89,6 +80,7 @@ import com.google.android.exoplayer2.util.Util; ...@@ -89,6 +80,7 @@ import com.google.android.exoplayer2.util.Util;
contentPosition = player.getContentPosition(); contentPosition = player.getContentPosition();
player.release(); player.release();
player = null; player = null;
adsLoader.setPlayer(null);
} }
} }
...@@ -125,7 +117,7 @@ import com.google.android.exoplayer2.util.Util; ...@@ -125,7 +117,7 @@ import com.google.android.exoplayer2.util.Util;
case C.TYPE_HLS: case C.TYPE_HLS:
return new HlsMediaSource.Factory(dataSourceFactory).createMediaSource(uri); return new HlsMediaSource.Factory(dataSourceFactory).createMediaSource(uri);
case C.TYPE_OTHER: case C.TYPE_OTHER:
return new ExtractorMediaSource.Factory(dataSourceFactory).createMediaSource(uri); return new ProgressiveMediaSource.Factory(dataSourceFactory).createMediaSource(uri);
default: default:
throw new IllegalStateException("Unsupported type: " + type); throw new IllegalStateException("Unsupported type: " + type);
} }
......
...@@ -26,7 +26,7 @@ android { ...@@ -26,7 +26,7 @@ android {
defaultConfig { defaultConfig {
versionName project.ext.releaseVersion versionName project.ext.releaseVersion
versionCode project.ext.releaseVersionCode versionCode project.ext.releaseVersionCode
minSdkVersion 16 minSdkVersion project.ext.minSdkVersion
targetSdkVersion project.ext.targetSdkVersion targetSdkVersion project.ext.targetSdkVersion
} }
......
...@@ -18,7 +18,11 @@ package com.google.android.exoplayer2.demo; ...@@ -18,7 +18,11 @@ package com.google.android.exoplayer2.demo;
import android.app.Application; import android.app.Application;
import com.google.android.exoplayer2.DefaultRenderersFactory; import com.google.android.exoplayer2.DefaultRenderersFactory;
import com.google.android.exoplayer2.RenderersFactory; import com.google.android.exoplayer2.RenderersFactory;
import com.google.android.exoplayer2.database.ExoDatabaseProvider;
import com.google.android.exoplayer2.offline.ActionFile;
import com.google.android.exoplayer2.offline.DefaultDownloadIndex;
import com.google.android.exoplayer2.offline.DefaultDownloaderFactory; import com.google.android.exoplayer2.offline.DefaultDownloaderFactory;
import com.google.android.exoplayer2.offline.DownloadIndexUtil;
import com.google.android.exoplayer2.offline.DownloadManager; import com.google.android.exoplayer2.offline.DownloadManager;
import com.google.android.exoplayer2.offline.DownloaderConstructorHelper; import com.google.android.exoplayer2.offline.DownloaderConstructorHelper;
import com.google.android.exoplayer2.upstream.DataSource; import com.google.android.exoplayer2.upstream.DataSource;
...@@ -31,14 +35,17 @@ import com.google.android.exoplayer2.upstream.cache.CacheDataSource; ...@@ -31,14 +35,17 @@ import com.google.android.exoplayer2.upstream.cache.CacheDataSource;
import com.google.android.exoplayer2.upstream.cache.CacheDataSourceFactory; import com.google.android.exoplayer2.upstream.cache.CacheDataSourceFactory;
import com.google.android.exoplayer2.upstream.cache.NoOpCacheEvictor; import com.google.android.exoplayer2.upstream.cache.NoOpCacheEvictor;
import com.google.android.exoplayer2.upstream.cache.SimpleCache; import com.google.android.exoplayer2.upstream.cache.SimpleCache;
import com.google.android.exoplayer2.util.Log;
import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.util.Util;
import java.io.File; import java.io.File;
import java.io.IOException;
/** /**
* Placeholder application to facilitate overriding Application methods for debugging and testing. * Placeholder application to facilitate overriding Application methods for debugging and testing.
*/ */
public class DemoApplication extends Application { public class DemoApplication extends Application {
private static final String TAG = "DemoApplication";
private static final String DOWNLOAD_ACTION_FILE = "actions"; private static final String DOWNLOAD_ACTION_FILE = "actions";
private static final String DOWNLOAD_TRACKER_ACTION_FILE = "tracked_actions"; private static final String DOWNLOAD_TRACKER_ACTION_FILE = "tracked_actions";
private static final String DOWNLOAD_CONTENT_DIRECTORY = "downloads"; private static final String DOWNLOAD_CONTENT_DIRECTORY = "downloads";
...@@ -97,19 +104,28 @@ public class DemoApplication extends Application { ...@@ -97,19 +104,28 @@ public class DemoApplication extends Application {
private synchronized void initDownloadManager() { private synchronized void initDownloadManager() {
if (downloadManager == null) { if (downloadManager == null) {
DefaultDownloadIndex downloadIndex = new DefaultDownloadIndex(new ExoDatabaseProvider(this));
File actionFile = new File(getDownloadDirectory(), DOWNLOAD_TRACKER_ACTION_FILE);
if (actionFile.exists()) {
try {
DownloadIndexUtil.upgradeActionFile(new ActionFile(actionFile), downloadIndex, null);
} catch (IOException e) {
Log.e(TAG, "Upgrading action file failed", e);
}
actionFile.delete();
}
DownloaderConstructorHelper downloaderConstructorHelper = DownloaderConstructorHelper downloaderConstructorHelper =
new DownloaderConstructorHelper(getDownloadCache(), buildHttpDataSourceFactory()); new DownloaderConstructorHelper(getDownloadCache(), buildHttpDataSourceFactory());
downloadManager = downloadManager =
new DownloadManager( new DownloadManager(
this,
new File(getDownloadDirectory(), DOWNLOAD_ACTION_FILE), new File(getDownloadDirectory(), DOWNLOAD_ACTION_FILE),
new DefaultDownloaderFactory(downloaderConstructorHelper), new DefaultDownloaderFactory(downloaderConstructorHelper),
MAX_SIMULTANEOUS_DOWNLOADS, MAX_SIMULTANEOUS_DOWNLOADS,
DownloadManager.DEFAULT_MIN_RETRY_COUNT); DownloadManager.DEFAULT_MIN_RETRY_COUNT,
DownloadManager.DEFAULT_REQUIREMENTS);
downloadTracker = downloadTracker =
new DownloadTracker( new DownloadTracker(/* context= */ this, buildDataSourceFactory(), downloadIndex);
/* context= */ this,
buildDataSourceFactory(),
new File(getDownloadDirectory(), DOWNLOAD_TRACKER_ACTION_FILE));
downloadManager.addListener(downloadTracker); downloadManager.addListener(downloadTracker);
} }
} }
......
...@@ -20,7 +20,7 @@ import com.google.android.exoplayer2.offline.DownloadManager; ...@@ -20,7 +20,7 @@ import com.google.android.exoplayer2.offline.DownloadManager;
import com.google.android.exoplayer2.offline.DownloadService; import com.google.android.exoplayer2.offline.DownloadService;
import com.google.android.exoplayer2.offline.DownloadState; import com.google.android.exoplayer2.offline.DownloadState;
import com.google.android.exoplayer2.scheduler.PlatformScheduler; import com.google.android.exoplayer2.scheduler.PlatformScheduler;
import com.google.android.exoplayer2.ui.DownloadNotificationUtil; import com.google.android.exoplayer2.ui.DownloadNotificationHelper;
import com.google.android.exoplayer2.util.NotificationUtil; import com.google.android.exoplayer2.util.NotificationUtil;
import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.util.Util;
...@@ -33,6 +33,8 @@ public class DemoDownloadService extends DownloadService { ...@@ -33,6 +33,8 @@ public class DemoDownloadService extends DownloadService {
private static int nextNotificationId = FOREGROUND_NOTIFICATION_ID + 1; private static int nextNotificationId = FOREGROUND_NOTIFICATION_ID + 1;
private DownloadNotificationHelper notificationHelper;
public DemoDownloadService() { public DemoDownloadService() {
super( super(
FOREGROUND_NOTIFICATION_ID, FOREGROUND_NOTIFICATION_ID,
...@@ -43,6 +45,12 @@ public class DemoDownloadService extends DownloadService { ...@@ -43,6 +45,12 @@ public class DemoDownloadService extends DownloadService {
} }
@Override @Override
public void onCreate() {
super.onCreate();
notificationHelper = new DownloadNotificationHelper(this, CHANNEL_ID);
}
@Override
protected DownloadManager getDownloadManager() { protected DownloadManager getDownloadManager() {
return ((DemoApplication) getApplication()).getDownloadManager(); return ((DemoApplication) getApplication()).getDownloadManager();
} }
...@@ -54,32 +62,23 @@ public class DemoDownloadService extends DownloadService { ...@@ -54,32 +62,23 @@ public class DemoDownloadService extends DownloadService {
@Override @Override
protected Notification getForegroundNotification(DownloadState[] downloadStates) { protected Notification getForegroundNotification(DownloadState[] downloadStates) {
return DownloadNotificationUtil.buildProgressNotification( return notificationHelper.buildProgressNotification(
/* context= */ this, R.drawable.ic_download, /* contentIntent= */ null, /* message= */ null, downloadStates);
R.drawable.ic_download,
CHANNEL_ID,
/* contentIntent= */ null,
/* message= */ null,
downloadStates);
} }
@Override @Override
protected void onDownloadStateChanged(DownloadState downloadState) { protected void onDownloadStateChanged(DownloadState downloadState) {
Notification notification = null; Notification notification;
if (downloadState.state == DownloadState.STATE_COMPLETED) { if (downloadState.state == DownloadState.STATE_COMPLETED) {
notification = notification =
DownloadNotificationUtil.buildDownloadCompletedNotification( notificationHelper.buildDownloadCompletedNotification(
/* context= */ this,
R.drawable.ic_download_done, R.drawable.ic_download_done,
CHANNEL_ID,
/* contentIntent= */ null, /* contentIntent= */ null,
Util.fromUtf8Bytes(downloadState.customMetadata)); Util.fromUtf8Bytes(downloadState.customMetadata));
} else if (downloadState.state == DownloadState.STATE_FAILED) { } else if (downloadState.state == DownloadState.STATE_FAILED) {
notification = notification =
DownloadNotificationUtil.buildDownloadFailedNotification( notificationHelper.buildDownloadFailedNotification(
/* context= */ this,
R.drawable.ic_download_done, R.drawable.ic_download_done,
CHANNEL_ID,
/* contentIntent= */ null, /* contentIntent= */ null,
Util.fromUtf8Bytes(downloadState.customMetadata)); Util.fromUtf8Bytes(downloadState.customMetadata));
} else { } else {
......
...@@ -51,8 +51,8 @@ import com.google.android.exoplayer2.mediacodec.MediaCodecUtil.DecoderQueryExcep ...@@ -51,8 +51,8 @@ import com.google.android.exoplayer2.mediacodec.MediaCodecUtil.DecoderQueryExcep
import com.google.android.exoplayer2.offline.StreamKey; import com.google.android.exoplayer2.offline.StreamKey;
import com.google.android.exoplayer2.source.BehindLiveWindowException; import com.google.android.exoplayer2.source.BehindLiveWindowException;
import com.google.android.exoplayer2.source.ConcatenatingMediaSource; import com.google.android.exoplayer2.source.ConcatenatingMediaSource;
import com.google.android.exoplayer2.source.ExtractorMediaSource;
import com.google.android.exoplayer2.source.MediaSource; import com.google.android.exoplayer2.source.MediaSource;
import com.google.android.exoplayer2.source.ProgressiveMediaSource;
import com.google.android.exoplayer2.source.TrackGroupArray; import com.google.android.exoplayer2.source.TrackGroupArray;
import com.google.android.exoplayer2.source.ads.AdsLoader; import com.google.android.exoplayer2.source.ads.AdsLoader;
import com.google.android.exoplayer2.source.ads.AdsMediaSource; import com.google.android.exoplayer2.source.ads.AdsMediaSource;
...@@ -483,7 +483,7 @@ public class PlayerActivity extends Activity ...@@ -483,7 +483,7 @@ public class PlayerActivity extends Activity
.setStreamKeys(offlineStreamKeys) .setStreamKeys(offlineStreamKeys)
.createMediaSource(uri); .createMediaSource(uri);
case C.TYPE_OTHER: case C.TYPE_OTHER:
return new ExtractorMediaSource.Factory(dataSourceFactory).createMediaSource(uri); return new ProgressiveMediaSource.Factory(dataSourceFactory).createMediaSource(uri);
default: { default: {
throw new IllegalStateException("Unsupported type: " + type); throw new IllegalStateException("Unsupported type: " + type);
} }
......
...@@ -24,7 +24,7 @@ android { ...@@ -24,7 +24,7 @@ android {
} }
defaultConfig { defaultConfig {
minSdkVersion 14 minSdkVersion project.ext.minSdkVersion
targetSdkVersion project.ext.targetSdkVersion targetSdkVersion project.ext.targetSdkVersion
consumerProguardFiles 'proguard-rules.txt' consumerProguardFiles 'proguard-rules.txt'
} }
......
...@@ -19,7 +19,7 @@ android { ...@@ -19,7 +19,7 @@ android {
buildToolsVersion project.ext.buildToolsVersion buildToolsVersion project.ext.buildToolsVersion
defaultConfig { defaultConfig {
minSdkVersion 16 minSdkVersion project.ext.minSdkVersion
targetSdkVersion project.ext.targetSdkVersion targetSdkVersion project.ext.targetSdkVersion
} }
...@@ -30,7 +30,7 @@ android { ...@@ -30,7 +30,7 @@ android {
} }
dependencies { dependencies {
api 'org.chromium.net:cronet-embedded:66.3359.158' api 'org.chromium.net:cronet-embedded:71.3578.98'
implementation project(modulePrefix + 'library-core') implementation project(modulePrefix + 'library-core')
implementation 'com.android.support:support-annotations:' + supportLibraryVersion implementation 'com.android.support:support-annotations:' + supportLibraryVersion
testImplementation project(modulePrefix + 'library') testImplementation project(modulePrefix + 'library')
......
...@@ -28,8 +28,8 @@ import com.google.android.exoplayer2.ExoPlayerFactory; ...@@ -28,8 +28,8 @@ import com.google.android.exoplayer2.ExoPlayerFactory;
import com.google.android.exoplayer2.Player; import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.Renderer; import com.google.android.exoplayer2.Renderer;
import com.google.android.exoplayer2.extractor.mkv.MatroskaExtractor; import com.google.android.exoplayer2.extractor.mkv.MatroskaExtractor;
import com.google.android.exoplayer2.source.ExtractorMediaSource;
import com.google.android.exoplayer2.source.MediaSource; import com.google.android.exoplayer2.source.MediaSource;
import com.google.android.exoplayer2.source.ProgressiveMediaSource;
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector; import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory; import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory;
import org.junit.Before; import org.junit.Before;
...@@ -86,7 +86,7 @@ public class FlacPlaybackTest { ...@@ -86,7 +86,7 @@ public class FlacPlaybackTest {
player = ExoPlayerFactory.newInstance(context, new Renderer[] {audioRenderer}, trackSelector); player = ExoPlayerFactory.newInstance(context, new Renderer[] {audioRenderer}, trackSelector);
player.addListener(this); player.addListener(this);
MediaSource mediaSource = MediaSource mediaSource =
new ExtractorMediaSource.Factory( new ProgressiveMediaSource.Factory(
new DefaultDataSourceFactory(context, "ExoPlayerExtFlacTest")) new DefaultDataSourceFactory(context, "ExoPlayerExtFlacTest"))
.setExtractorsFactory(MatroskaExtractor.FACTORY) .setExtractorsFactory(MatroskaExtractor.FACTORY)
.createMediaSource(uri); .createMediaSource(uri);
......
...@@ -33,9 +33,7 @@ dependencies { ...@@ -33,9 +33,7 @@ dependencies {
implementation project(modulePrefix + 'library-core') implementation project(modulePrefix + 'library-core')
implementation project(modulePrefix + 'library-ui') implementation project(modulePrefix + 'library-ui')
implementation 'com.android.support:support-annotations:' + supportLibraryVersion implementation 'com.android.support:support-annotations:' + supportLibraryVersion
implementation 'com.google.vr:sdk-audio:1.80.0' api 'com.google.vr:sdk-base:1.190.0'
implementation 'com.google.vr:sdk-controller:1.80.0'
api 'com.google.vr:sdk-base:1.80.0'
compileOnly 'org.checkerframework:checker-qual:' + checkerframeworkVersion compileOnly 'org.checkerframework:checker-qual:' + checkerframeworkVersion
} }
......
...@@ -31,13 +31,13 @@ android { ...@@ -31,13 +31,13 @@ android {
} }
dependencies { dependencies {
api 'com.google.ads.interactivemedia.v3:interactivemedia:3.10.2' api 'com.google.ads.interactivemedia.v3:interactivemedia:3.10.6'
implementation project(modulePrefix + 'library-core') implementation project(modulePrefix + 'library-core')
implementation 'com.google.android.gms:play-services-ads:17.1.1' implementation 'com.google.android.gms:play-services-ads:17.1.2'
// These dependencies are necessary to force the supportLibraryVersion of // These dependencies are necessary to force the supportLibraryVersion of
// com.android.support:support-v4 and com.android.support:customtabs to be // com.android.support:support-v4 and com.android.support:customtabs to be
// used. Else older versions are used, for example via: // used. Else older versions are used, for example via:
// com.google.android.gms:play-services-ads:17.1.1 // com.google.android.gms:play-services-ads:17.1.2
// |-- com.android.support:customtabs:26.1.0 // |-- com.android.support:customtabs:26.1.0
implementation 'com.android.support:support-v4:' + supportLibraryVersion implementation 'com.android.support:support-v4:' + supportLibraryVersion
implementation 'com.android.support:customtabs:' + supportLibraryVersion implementation 'com.android.support:customtabs:' + supportLibraryVersion
......
...@@ -466,11 +466,11 @@ public final class ImaAdsLoader ...@@ -466,11 +466,11 @@ public final class ImaAdsLoader
} }
imaSdkSettings.setPlayerType(IMA_SDK_SETTINGS_PLAYER_TYPE); imaSdkSettings.setPlayerType(IMA_SDK_SETTINGS_PLAYER_TYPE);
imaSdkSettings.setPlayerVersion(IMA_SDK_SETTINGS_PLAYER_VERSION); imaSdkSettings.setPlayerVersion(IMA_SDK_SETTINGS_PLAYER_VERSION);
adsLoader = imaFactory.createAdsLoader(context, imaSdkSettings);
period = new Timeline.Period(); period = new Timeline.Period();
adCallbacks = new ArrayList<>(/* initialCapacity= */ 1); adCallbacks = new ArrayList<>(/* initialCapacity= */ 1);
adDisplayContainer = imaFactory.createAdDisplayContainer(); adDisplayContainer = imaFactory.createAdDisplayContainer();
adDisplayContainer.setPlayer(/* videoAdPlayer= */ this); adDisplayContainer.setPlayer(/* videoAdPlayer= */ this);
adsLoader = imaFactory.createAdsLoader(context, imaSdkSettings, adDisplayContainer);
adsLoader.addAdErrorListener(/* adErrorListener= */ this); adsLoader.addAdErrorListener(/* adErrorListener= */ this);
adsLoader.addAdsLoadedListener(/* adsLoadedListener= */ this); adsLoader.addAdsLoadedListener(/* adsLoadedListener= */ this);
fakeContentProgressElapsedRealtimeMs = C.TIME_UNSET; fakeContentProgressElapsedRealtimeMs = C.TIME_UNSET;
...@@ -524,7 +524,6 @@ public final class ImaAdsLoader ...@@ -524,7 +524,6 @@ public final class ImaAdsLoader
if (vastLoadTimeoutMs != TIMEOUT_UNSET) { if (vastLoadTimeoutMs != TIMEOUT_UNSET) {
request.setVastLoadTimeout(vastLoadTimeoutMs); request.setVastLoadTimeout(vastLoadTimeoutMs);
} }
request.setAdDisplayContainer(adDisplayContainer);
request.setContentProgressProvider(this); request.setContentProgressProvider(this);
request.setUserRequestContext(pendingAdRequestContext); request.setUserRequestContext(pendingAdRequestContext);
adsLoader.requestAds(request); adsLoader.requestAds(request);
...@@ -1374,9 +1373,9 @@ public final class ImaAdsLoader ...@@ -1374,9 +1373,9 @@ public final class ImaAdsLoader
AdDisplayContainer createAdDisplayContainer(); AdDisplayContainer createAdDisplayContainer();
/** @see com.google.ads.interactivemedia.v3.api.ImaSdkFactory#createAdsRequest() */ /** @see com.google.ads.interactivemedia.v3.api.ImaSdkFactory#createAdsRequest() */
AdsRequest createAdsRequest(); AdsRequest createAdsRequest();
/** @see ImaSdkFactory#createAdsLoader(Context, ImaSdkSettings) */ /** @see ImaSdkFactory#createAdsLoader(Context, ImaSdkSettings, AdDisplayContainer) */
com.google.ads.interactivemedia.v3.api.AdsLoader createAdsLoader( com.google.ads.interactivemedia.v3.api.AdsLoader createAdsLoader(
Context context, ImaSdkSettings imaSdkSettings); Context context, ImaSdkSettings imaSdkSettings, AdDisplayContainer adDisplayContainer);
} }
/** Default {@link ImaFactory} for non-test usage, which delegates to {@link ImaSdkFactory}. */ /** Default {@link ImaFactory} for non-test usage, which delegates to {@link ImaSdkFactory}. */
...@@ -1403,8 +1402,9 @@ public final class ImaAdsLoader ...@@ -1403,8 +1402,9 @@ public final class ImaAdsLoader
@Override @Override
public com.google.ads.interactivemedia.v3.api.AdsLoader createAdsLoader( public com.google.ads.interactivemedia.v3.api.AdsLoader createAdsLoader(
Context context, ImaSdkSettings imaSdkSettings) { Context context, ImaSdkSettings imaSdkSettings, AdDisplayContainer adDisplayContainer) {
return ImaSdkFactory.getInstance().createAdsLoader(context, imaSdkSettings); return ImaSdkFactory.getInstance()
.createAdsLoader(context, imaSdkSettings, adDisplayContainer);
} }
} }
} }
...@@ -93,8 +93,8 @@ public final class ImaAdsMediaSource extends BaseMediaSource implements SourceIn ...@@ -93,8 +93,8 @@ public final class ImaAdsMediaSource extends BaseMediaSource implements SourceIn
} }
@Override @Override
public MediaPeriod createPeriod(MediaPeriodId id, Allocator allocator) { public MediaPeriod createPeriod(MediaPeriodId id, Allocator allocator, long startPositionUs) {
return adsMediaSource.createPeriod(id, allocator); return adsMediaSource.createPeriod(id, allocator, startPositionUs);
} }
@Override @Override
......
...@@ -17,6 +17,7 @@ package com.google.android.exoplayer2.ext.ima; ...@@ -17,6 +17,7 @@ package com.google.android.exoplayer2.ext.ima;
import android.content.Context; import android.content.Context;
import com.google.ads.interactivemedia.v3.api.AdDisplayContainer; import com.google.ads.interactivemedia.v3.api.AdDisplayContainer;
import com.google.ads.interactivemedia.v3.api.AdsLoader;
import com.google.ads.interactivemedia.v3.api.AdsRenderingSettings; import com.google.ads.interactivemedia.v3.api.AdsRenderingSettings;
import com.google.ads.interactivemedia.v3.api.AdsRequest; import com.google.ads.interactivemedia.v3.api.AdsRequest;
import com.google.ads.interactivemedia.v3.api.ImaSdkSettings; import com.google.ads.interactivemedia.v3.api.ImaSdkSettings;
...@@ -64,8 +65,8 @@ final class SingletonImaFactory implements ImaAdsLoader.ImaFactory { ...@@ -64,8 +65,8 @@ final class SingletonImaFactory implements ImaAdsLoader.ImaFactory {
} }
@Override @Override
public com.google.ads.interactivemedia.v3.api.AdsLoader createAdsLoader( public AdsLoader createAdsLoader(
Context context, ImaSdkSettings imaSdkSettings) { Context context, ImaSdkSettings imaSdkSettings, AdDisplayContainer adDisplayContainer) {
return adsLoader; return adsLoader;
} }
} }
...@@ -34,7 +34,7 @@ dependencies { ...@@ -34,7 +34,7 @@ dependencies {
implementation project(modulePrefix + 'library-core') implementation project(modulePrefix + 'library-core')
implementation 'com.android.support:support-annotations:' + supportLibraryVersion implementation 'com.android.support:support-annotations:' + supportLibraryVersion
compileOnly 'org.checkerframework:checker-qual:' + checkerframeworkVersion compileOnly 'org.checkerframework:checker-qual:' + checkerframeworkVersion
api 'com.squareup.okhttp3:okhttp:3.11.0' api 'com.squareup.okhttp3:okhttp:3.12.1'
} }
ext { ext {
......
...@@ -28,8 +28,8 @@ import com.google.android.exoplayer2.ExoPlayerFactory; ...@@ -28,8 +28,8 @@ import com.google.android.exoplayer2.ExoPlayerFactory;
import com.google.android.exoplayer2.Player; import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.Renderer; import com.google.android.exoplayer2.Renderer;
import com.google.android.exoplayer2.extractor.mkv.MatroskaExtractor; import com.google.android.exoplayer2.extractor.mkv.MatroskaExtractor;
import com.google.android.exoplayer2.source.ExtractorMediaSource;
import com.google.android.exoplayer2.source.MediaSource; import com.google.android.exoplayer2.source.MediaSource;
import com.google.android.exoplayer2.source.ProgressiveMediaSource;
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector; import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory; import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory;
import org.junit.Before; import org.junit.Before;
...@@ -86,7 +86,7 @@ public class OpusPlaybackTest { ...@@ -86,7 +86,7 @@ public class OpusPlaybackTest {
player = ExoPlayerFactory.newInstance(context, new Renderer[] {audioRenderer}, trackSelector); player = ExoPlayerFactory.newInstance(context, new Renderer[] {audioRenderer}, trackSelector);
player.addListener(this); player.addListener(this);
MediaSource mediaSource = MediaSource mediaSource =
new ExtractorMediaSource.Factory( new ProgressiveMediaSource.Factory(
new DefaultDataSourceFactory(context, "ExoPlayerExtOpusTest")) new DefaultDataSourceFactory(context, "ExoPlayerExtOpusTest"))
.setExtractorsFactory(MatroskaExtractor.FACTORY) .setExtractorsFactory(MatroskaExtractor.FACTORY)
.createMediaSource(uri); .createMediaSource(uri);
......
...@@ -24,7 +24,7 @@ android { ...@@ -24,7 +24,7 @@ android {
} }
defaultConfig { defaultConfig {
minSdkVersion 15 minSdkVersion project.ext.minSdkVersion
targetSdkVersion project.ext.targetSdkVersion targetSdkVersion project.ext.targetSdkVersion
} }
} }
......
...@@ -34,26 +34,6 @@ VP9_EXT_PATH="${EXOPLAYER_ROOT}/extensions/vp9/src/main" ...@@ -34,26 +34,6 @@ VP9_EXT_PATH="${EXOPLAYER_ROOT}/extensions/vp9/src/main"
NDK_PATH="<path to Android NDK>" NDK_PATH="<path to Android NDK>"
``` ```
* Fetch libvpx and libyuv:
```
cd "${VP9_EXT_PATH}/jni" && \
git clone https://chromium.googlesource.com/webm/libvpx libvpx && \
git clone https://chromium.googlesource.com/libyuv/libyuv libyuv
```
* Checkout the appropriate branches of libvpx and libyuv (the scripts and
makefiles bundled in this repo are known to work only at these versions of the
libraries - we will update this periodically as newer versions of
libvpx/libyuv are released):
```
cd "${VP9_EXT_PATH}/jni/libvpx" && \
git checkout tags/v1.7.0 -b v1.7.0 && \
cd "${VP9_EXT_PATH}/jni/libyuv" && \
git checkout 996a2bbd
```
* Run a script that generates necessary configuration files for libvpx: * Run a script that generates necessary configuration files for libvpx:
``` ```
...@@ -78,10 +58,6 @@ ${NDK_PATH}/ndk-build APP_ABI=all -j4 ...@@ -78,10 +58,6 @@ ${NDK_PATH}/ndk-build APP_ABI=all -j4
* Android config scripts should be re-generated by running * Android config scripts should be re-generated by running
`generate_libvpx_android_configs.sh` `generate_libvpx_android_configs.sh`
* Clean and re-build the project. * Clean and re-build the project.
* If you want to use your own version of libvpx or libyuv, place it in
`${VP9_EXT_PATH}/jni/libvpx` or `${VP9_EXT_PATH}/jni/libyuv` respectively. But
please note that `generate_libvpx_android_configs.sh` and the makefiles need
to be modified to work with arbitrary versions of libvpx and libyuv.
## Using the extension ## ## Using the extension ##
......
...@@ -29,8 +29,8 @@ import com.google.android.exoplayer2.ExoPlayerFactory; ...@@ -29,8 +29,8 @@ import com.google.android.exoplayer2.ExoPlayerFactory;
import com.google.android.exoplayer2.Player; import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.Renderer; import com.google.android.exoplayer2.Renderer;
import com.google.android.exoplayer2.extractor.mkv.MatroskaExtractor; import com.google.android.exoplayer2.extractor.mkv.MatroskaExtractor;
import com.google.android.exoplayer2.source.ExtractorMediaSource;
import com.google.android.exoplayer2.source.MediaSource; import com.google.android.exoplayer2.source.MediaSource;
import com.google.android.exoplayer2.source.ProgressiveMediaSource;
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector; import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory; import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory;
import com.google.android.exoplayer2.util.Log; import com.google.android.exoplayer2.util.Log;
...@@ -114,12 +114,12 @@ public class VpxPlaybackTest { ...@@ -114,12 +114,12 @@ public class VpxPlaybackTest {
@Override @Override
public void run() { public void run() {
Looper.prepare(); Looper.prepare();
LibvpxVideoRenderer videoRenderer = new LibvpxVideoRenderer(true, 0); LibvpxVideoRenderer videoRenderer = new LibvpxVideoRenderer(0);
DefaultTrackSelector trackSelector = new DefaultTrackSelector(); DefaultTrackSelector trackSelector = new DefaultTrackSelector();
player = ExoPlayerFactory.newInstance(context, new Renderer[] {videoRenderer}, trackSelector); player = ExoPlayerFactory.newInstance(context, new Renderer[] {videoRenderer}, trackSelector);
player.addListener(this); player.addListener(this);
MediaSource mediaSource = MediaSource mediaSource =
new ExtractorMediaSource.Factory( new ProgressiveMediaSource.Factory(
new DefaultDataSourceFactory(context, "ExoPlayerExtVp9Test")) new DefaultDataSourceFactory(context, "ExoPlayerExtVp9Test"))
.setExtractorsFactory(MatroskaExtractor.FACTORY) .setExtractorsFactory(MatroskaExtractor.FACTORY)
.createMediaSource(uri); .createMediaSource(uri);
......
...@@ -31,8 +31,7 @@ import java.nio.ByteBuffer; ...@@ -31,8 +31,7 @@ import java.nio.ByteBuffer;
public static final int OUTPUT_MODE_NONE = -1; public static final int OUTPUT_MODE_NONE = -1;
public static final int OUTPUT_MODE_YUV = 0; public static final int OUTPUT_MODE_YUV = 0;
public static final int OUTPUT_MODE_RGB = 1; public static final int OUTPUT_MODE_SURFACE_YUV = 1;
public static final int OUTPUT_MODE_SURFACE_YUV = 2;
private static final int NO_ERROR = 0; private static final int NO_ERROR = 0;
private static final int DECODE_ERROR = 1; private static final int DECODE_ERROR = 1;
...@@ -52,7 +51,6 @@ import java.nio.ByteBuffer; ...@@ -52,7 +51,6 @@ import java.nio.ByteBuffer;
* @param exoMediaCrypto The {@link ExoMediaCrypto} object required for decoding encrypted * @param exoMediaCrypto The {@link ExoMediaCrypto} object required for decoding encrypted
* content. Maybe null and can be ignored if decoder does not handle encrypted content. * content. Maybe null and can be ignored if decoder does not handle encrypted content.
* @param disableLoopFilter Disable the libvpx in-loop smoothing filter. * @param disableLoopFilter Disable the libvpx in-loop smoothing filter.
* @param enableSurfaceYuvOutputMode Whether OUTPUT_MODE_SURFACE_YUV is allowed.
* @throws VpxDecoderException Thrown if an exception occurs when initializing the decoder. * @throws VpxDecoderException Thrown if an exception occurs when initializing the decoder.
*/ */
public VpxDecoder( public VpxDecoder(
...@@ -60,8 +58,7 @@ import java.nio.ByteBuffer; ...@@ -60,8 +58,7 @@ import java.nio.ByteBuffer;
int numOutputBuffers, int numOutputBuffers,
int initialInputBufferSize, int initialInputBufferSize,
ExoMediaCrypto exoMediaCrypto, ExoMediaCrypto exoMediaCrypto,
boolean disableLoopFilter, boolean disableLoopFilter)
boolean enableSurfaceYuvOutputMode)
throws VpxDecoderException { throws VpxDecoderException {
super(new VpxInputBuffer[numInputBuffers], new VpxOutputBuffer[numOutputBuffers]); super(new VpxInputBuffer[numInputBuffers], new VpxOutputBuffer[numOutputBuffers]);
if (!VpxLibrary.isAvailable()) { if (!VpxLibrary.isAvailable()) {
...@@ -71,7 +68,7 @@ import java.nio.ByteBuffer; ...@@ -71,7 +68,7 @@ import java.nio.ByteBuffer;
if (exoMediaCrypto != null && !VpxLibrary.vpxIsSecureDecodeSupported()) { if (exoMediaCrypto != null && !VpxLibrary.vpxIsSecureDecodeSupported()) {
throw new VpxDecoderException("Vpx decoder does not support secure decode."); throw new VpxDecoderException("Vpx decoder does not support secure decode.");
} }
vpxDecContext = vpxInit(disableLoopFilter, enableSurfaceYuvOutputMode); vpxDecContext = vpxInit(disableLoopFilter);
if (vpxDecContext == 0) { if (vpxDecContext == 0) {
throw new VpxDecoderException("Failed to initialize decoder"); throw new VpxDecoderException("Failed to initialize decoder");
} }
...@@ -86,8 +83,8 @@ import java.nio.ByteBuffer; ...@@ -86,8 +83,8 @@ import java.nio.ByteBuffer;
/** /**
* Sets the output mode for frames rendered by the decoder. * Sets the output mode for frames rendered by the decoder.
* *
* @param outputMode The output mode. One of {@link #OUTPUT_MODE_NONE}, {@link #OUTPUT_MODE_RGB} * @param outputMode The output mode. One of {@link #OUTPUT_MODE_NONE} and {@link
* and {@link #OUTPUT_MODE_YUV}. * #OUTPUT_MODE_YUV}.
*/ */
public void setOutputMode(int outputMode) { public void setOutputMode(int outputMode) {
this.outputMode = outputMode; this.outputMode = outputMode;
...@@ -168,7 +165,7 @@ import java.nio.ByteBuffer; ...@@ -168,7 +165,7 @@ import java.nio.ByteBuffer;
} }
} }
private native long vpxInit(boolean disableLoopFilter, boolean enableSurfaceYuvOutputMode); private native long vpxInit(boolean disableLoopFilter);
private native long vpxClose(long context); private native long vpxClose(long context);
private native long vpxDecode(long context, ByteBuffer encoded, int length); private native long vpxDecode(long context, ByteBuffer encoded, int length);
......
...@@ -60,36 +60,19 @@ public final class VpxOutputBuffer extends OutputBuffer { ...@@ -60,36 +60,19 @@ public final class VpxOutputBuffer extends OutputBuffer {
* Initializes the buffer. * Initializes the buffer.
* *
* @param timeUs The presentation timestamp for the buffer, in microseconds. * @param timeUs The presentation timestamp for the buffer, in microseconds.
* @param mode The output mode. One of {@link VpxDecoder#OUTPUT_MODE_NONE}, * @param mode The output mode. One of {@link VpxDecoder#OUTPUT_MODE_NONE} and {@link
* {@link VpxDecoder#OUTPUT_MODE_RGB} and {@link VpxDecoder#OUTPUT_MODE_YUV}. * VpxDecoder#OUTPUT_MODE_YUV}.
*/ */
public void init(long timeUs, int mode) { public void init(long timeUs, int mode) {
this.timeUs = timeUs; this.timeUs = timeUs;
this.mode = mode; this.mode = mode;
} }
/**
* Resizes the buffer based on the given dimensions. Called via JNI after decoding completes.
* @return Whether the buffer was resized successfully.
*/
public boolean initForRgbFrame(int width, int height) {
this.width = width;
this.height = height;
this.yuvPlanes = null;
if (!isSafeToMultiply(width, height) || !isSafeToMultiply(width * height, 2)) {
return false;
}
int minimumRgbSize = width * height * 2;
initData(minimumRgbSize);
return true;
}
/** /**
* Resizes the buffer based on the given stride. Called via JNI after decoding completes. * Resizes the buffer based on the given stride. Called via JNI after decoding completes.
*
* @return Whether the buffer was resized successfully. * @return Whether the buffer was resized successfully.
*/ */
public boolean initForYuvFrame(int width, int height, int yStride, int uvStride, public boolean initForYuvFrame(int width, int height, int yStride, int uvStride, int colorspace) {
int colorspace) {
this.width = width; this.width = width;
this.height = height; this.height = height;
this.colorspace = colorspace; this.colorspace = colorspace;
......
...@@ -17,12 +17,6 @@ ...@@ -17,12 +17,6 @@
WORKING_DIR := $(call my-dir) WORKING_DIR := $(call my-dir)
include $(CLEAR_VARS) include $(CLEAR_VARS)
LIBVPX_ROOT := $(WORKING_DIR)/libvpx LIBVPX_ROOT := $(WORKING_DIR)/libvpx
LIBYUV_ROOT := $(WORKING_DIR)/libyuv
# build libyuv_static.a
LOCAL_PATH := $(WORKING_DIR)
LIBYUV_DISABLE_JPEG := "yes"
include $(LIBYUV_ROOT)/Android.mk
# build libvpx.so # build libvpx.so
LOCAL_PATH := $(WORKING_DIR) LOCAL_PATH := $(WORKING_DIR)
...@@ -37,7 +31,7 @@ LOCAL_CPP_EXTENSION := .cc ...@@ -37,7 +31,7 @@ LOCAL_CPP_EXTENSION := .cc
LOCAL_SRC_FILES := vpx_jni.cc LOCAL_SRC_FILES := vpx_jni.cc
LOCAL_LDLIBS := -llog -lz -lm -landroid LOCAL_LDLIBS := -llog -lz -lm -landroid
LOCAL_SHARED_LIBRARIES := libvpx LOCAL_SHARED_LIBRARIES := libvpx
LOCAL_STATIC_LIBRARIES := libyuv_static cpufeatures LOCAL_STATIC_LIBRARIES := cpufeatures
include $(BUILD_SHARED_LIBRARY) include $(BUILD_SHARED_LIBRARY)
$(call import-module,android/cpufeatures) $(call import-module,android/cpufeatures)
...@@ -30,8 +30,6 @@ ...@@ -30,8 +30,6 @@
#include <cstring> #include <cstring>
#include <new> #include <new>
#include "libyuv.h" // NOLINT
#define VPX_CODEC_DISABLE_COMPAT 1 #define VPX_CODEC_DISABLE_COMPAT 1
#include "vpx/vpx_decoder.h" #include "vpx/vpx_decoder.h"
#include "vpx/vp8dx.h" #include "vpx/vp8dx.h"
...@@ -61,7 +59,6 @@ ...@@ -61,7 +59,6 @@
(JNIEnv* env, jobject thiz, ##__VA_ARGS__)\ (JNIEnv* env, jobject thiz, ##__VA_ARGS__)\
// JNI references for VpxOutputBuffer class. // JNI references for VpxOutputBuffer class.
static jmethodID initForRgbFrame;
static jmethodID initForYuvFrame; static jmethodID initForYuvFrame;
static jfieldID dataField; static jfieldID dataField;
static jfieldID outputModeField; static jfieldID outputModeField;
...@@ -393,11 +390,7 @@ class JniBufferManager { ...@@ -393,11 +390,7 @@ class JniBufferManager {
}; };
struct JniCtx { struct JniCtx {
JniCtx(bool enableBufferManager) { JniCtx() { buffer_manager = new JniBufferManager(); }
if (enableBufferManager) {
buffer_manager = new JniBufferManager();
}
}
~JniCtx() { ~JniCtx() {
if (native_window) { if (native_window) {
...@@ -440,9 +433,8 @@ int vpx_release_frame_buffer(void* priv, vpx_codec_frame_buffer_t* fb) { ...@@ -440,9 +433,8 @@ int vpx_release_frame_buffer(void* priv, vpx_codec_frame_buffer_t* fb) {
return buffer_manager->release(*(int*)fb->priv); return buffer_manager->release(*(int*)fb->priv);
} }
DECODER_FUNC(jlong, vpxInit, jboolean disableLoopFilter, DECODER_FUNC(jlong, vpxInit, jboolean disableLoopFilter) {
jboolean enableBufferManager) { JniCtx* context = new JniCtx();
JniCtx* context = new JniCtx(enableBufferManager);
context->decoder = new vpx_codec_ctx_t(); context->decoder = new vpx_codec_ctx_t();
vpx_codec_dec_cfg_t cfg = {0, 0, 0}; vpx_codec_dec_cfg_t cfg = {0, 0, 0};
cfg.threads = android_getCpuCount(); cfg.threads = android_getCpuCount();
...@@ -469,14 +461,12 @@ DECODER_FUNC(jlong, vpxInit, jboolean disableLoopFilter, ...@@ -469,14 +461,12 @@ DECODER_FUNC(jlong, vpxInit, jboolean disableLoopFilter,
} }
#endif #endif
} }
if (enableBufferManager) { err = vpx_codec_set_frame_buffer_functions(
err = vpx_codec_set_frame_buffer_functions( context->decoder, vpx_get_frame_buffer, vpx_release_frame_buffer,
context->decoder, vpx_get_frame_buffer, vpx_release_frame_buffer, context->buffer_manager);
context->buffer_manager); if (err) {
if (err) { LOGE("ERROR: Failed to set libvpx frame buffer functions, error = %d.",
LOGE("ERROR: Failed to set libvpx frame buffer functions, error = %d.", err);
err);
}
} }
// Populate JNI References. // Populate JNI References.
...@@ -484,8 +474,6 @@ DECODER_FUNC(jlong, vpxInit, jboolean disableLoopFilter, ...@@ -484,8 +474,6 @@ DECODER_FUNC(jlong, vpxInit, jboolean disableLoopFilter,
"com/google/android/exoplayer2/ext/vp9/VpxOutputBuffer"); "com/google/android/exoplayer2/ext/vp9/VpxOutputBuffer");
initForYuvFrame = env->GetMethodID(outputBufferClass, "initForYuvFrame", initForYuvFrame = env->GetMethodID(outputBufferClass, "initForYuvFrame",
"(IIIII)Z"); "(IIIII)Z");
initForRgbFrame = env->GetMethodID(outputBufferClass, "initForRgbFrame",
"(II)Z");
dataField = env->GetFieldID(outputBufferClass, "data", dataField = env->GetFieldID(outputBufferClass, "data",
"Ljava/nio/ByteBuffer;"); "Ljava/nio/ByteBuffer;");
outputModeField = env->GetFieldID(outputBufferClass, "mode", "I"); outputModeField = env->GetFieldID(outputBufferClass, "mode", "I");
...@@ -537,28 +525,10 @@ DECODER_FUNC(jint, vpxGetFrame, jlong jContext, jobject jOutputBuffer) { ...@@ -537,28 +525,10 @@ DECODER_FUNC(jint, vpxGetFrame, jlong jContext, jobject jOutputBuffer) {
} }
const int kOutputModeYuv = 0; const int kOutputModeYuv = 0;
const int kOutputModeRgb = 1; const int kOutputModeSurfaceYuv = 1;
const int kOutputModeSurfaceYuv = 2;
int outputMode = env->GetIntField(jOutputBuffer, outputModeField); int outputMode = env->GetIntField(jOutputBuffer, outputModeField);
if (outputMode == kOutputModeRgb) { if (outputMode == kOutputModeYuv) {
// resize buffer if required.
jboolean initResult = env->CallBooleanMethod(jOutputBuffer, initForRgbFrame,
img->d_w, img->d_h);
if (env->ExceptionCheck() || !initResult) {
return -1;
}
// get pointer to the data buffer.
const jobject dataObject = env->GetObjectField(jOutputBuffer, dataField);
uint8_t* const dst =
reinterpret_cast<uint8_t*>(env->GetDirectBufferAddress(dataObject));
libyuv::I420ToRGB565(img->planes[VPX_PLANE_Y], img->stride[VPX_PLANE_Y],
img->planes[VPX_PLANE_U], img->stride[VPX_PLANE_U],
img->planes[VPX_PLANE_V], img->stride[VPX_PLANE_V],
dst, img->d_w * 2, img->d_w, img->d_h);
} else if (outputMode == kOutputModeYuv) {
const int kColorspaceUnknown = 0; const int kColorspaceUnknown = 0;
const int kColorspaceBT601 = 1; const int kColorspaceBT601 = 1;
const int kColorspaceBT709 = 2; const int kColorspaceBT709 = 2;
...@@ -616,9 +586,6 @@ DECODER_FUNC(jint, vpxGetFrame, jlong jContext, jobject jOutputBuffer) { ...@@ -616,9 +586,6 @@ DECODER_FUNC(jint, vpxGetFrame, jlong jContext, jobject jOutputBuffer) {
} }
} else if (outputMode == kOutputModeSurfaceYuv && } else if (outputMode == kOutputModeSurfaceYuv &&
img->fmt != VPX_IMG_FMT_I42016) { img->fmt != VPX_IMG_FMT_I42016) {
if (!context->buffer_manager) {
return -1; // enableBufferManager was not set in vpxInit.
}
int id = *(int*)img->fb_priv; int id = *(int*)img->fb_priv;
context->buffer_manager->add_ref(id); context->buffer_manager->add_ref(id);
JniFrameBuffer* jfb = context->buffer_manager->get_buffer(id); JniFrameBuffer* jfb = context->buffer_manager->get_buffer(id);
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
# Constructors accessed via reflection in DefaultRenderersFactory # Constructors accessed via reflection in DefaultRenderersFactory
-dontnote com.google.android.exoplayer2.ext.vp9.LibvpxVideoRenderer -dontnote com.google.android.exoplayer2.ext.vp9.LibvpxVideoRenderer
-keepclassmembers class com.google.android.exoplayer2.ext.vp9.LibvpxVideoRenderer { -keepclassmembers class com.google.android.exoplayer2.ext.vp9.LibvpxVideoRenderer {
<init>(boolean, long, android.os.Handler, com.google.android.exoplayer2.video.VideoRendererEventListener, int); <init>(long, android.os.Handler, com.google.android.exoplayer2.video.VideoRendererEventListener, int);
} }
-dontnote com.google.android.exoplayer2.ext.opus.LibopusAudioRenderer -dontnote com.google.android.exoplayer2.ext.opus.LibopusAudioRenderer
-keepclassmembers class com.google.android.exoplayer2.ext.opus.LibopusAudioRenderer { -keepclassmembers class com.google.android.exoplayer2.ext.opus.LibopusAudioRenderer {
...@@ -44,5 +44,22 @@ ...@@ -44,5 +44,22 @@
<init>(android.net.Uri, java.util.List, com.google.android.exoplayer2.offlineDownloaderConstructorHelper); <init>(android.net.Uri, java.util.List, com.google.android.exoplayer2.offlineDownloaderConstructorHelper);
} }
# Constructors accessed via reflection in DownloadHelper
-dontnote com.google.android.exoplayer2.source.dash.DashMediaSource$Factory
-keepclassmembers class com.google.android.exoplayer2.source.dash.DashMediaSource$Factory {
<init>(com.google.android.exoplayer2.upstream.DataSource$Factory);
DashMediaSource createMediaSource(android.net.Uri);
}
-dontnote com.google.android.exoplayer2.source.hls.HlsMediaSource$Factory
-keepclassmembers class com.google.android.exoplayer2.source.hls.HlsMediaSource$Factory {
<init>(com.google.android.exoplayer2.upstream.DataSource$Factory);
HlsMediaSource createMediaSource(android.net.Uri);
}
-dontnote com.google.android.exoplayer2.source.smoothstreaming.SsMediaSource$Factory
-keepclassmembers class com.google.android.exoplayer2.source.smoothstreaming.SsMediaSource$Factory {
<init>(com.google.android.exoplayer2.upstream.DataSource$Factory);
SsMediaSource createMediaSource(android.net.Uri);
}
# Don't warn about checkerframework # Don't warn about checkerframework
-dontwarn org.checkerframework.** -dontwarn org.checkerframework.**
...@@ -37,7 +37,7 @@ public abstract class BaseRenderer implements Renderer, RendererCapabilities { ...@@ -37,7 +37,7 @@ public abstract class BaseRenderer implements Renderer, RendererCapabilities {
private SampleStream stream; private SampleStream stream;
private Format[] streamFormats; private Format[] streamFormats;
private long streamOffsetUs; private long streamOffsetUs;
private boolean readEndOfStream; private long readingPositionUs;
private boolean streamIsFinal; private boolean streamIsFinal;
/** /**
...@@ -46,7 +46,7 @@ public abstract class BaseRenderer implements Renderer, RendererCapabilities { ...@@ -46,7 +46,7 @@ public abstract class BaseRenderer implements Renderer, RendererCapabilities {
*/ */
public BaseRenderer(int trackType) { public BaseRenderer(int trackType) {
this.trackType = trackType; this.trackType = trackType;
readEndOfStream = true; readingPositionUs = C.TIME_END_OF_SOURCE;
} }
@Override @Override
...@@ -98,7 +98,7 @@ public abstract class BaseRenderer implements Renderer, RendererCapabilities { ...@@ -98,7 +98,7 @@ public abstract class BaseRenderer implements Renderer, RendererCapabilities {
throws ExoPlaybackException { throws ExoPlaybackException {
Assertions.checkState(!streamIsFinal); Assertions.checkState(!streamIsFinal);
this.stream = stream; this.stream = stream;
readEndOfStream = false; readingPositionUs = offsetUs;
streamFormats = formats; streamFormats = formats;
streamOffsetUs = offsetUs; streamOffsetUs = offsetUs;
onStreamChanged(formats, offsetUs); onStreamChanged(formats, offsetUs);
...@@ -111,7 +111,12 @@ public abstract class BaseRenderer implements Renderer, RendererCapabilities { ...@@ -111,7 +111,12 @@ public abstract class BaseRenderer implements Renderer, RendererCapabilities {
@Override @Override
public final boolean hasReadStreamToEnd() { public final boolean hasReadStreamToEnd() {
return readEndOfStream; return readingPositionUs == C.TIME_END_OF_SOURCE;
}
@Override
public final long getReadingPositionUs() {
return readingPositionUs;
} }
@Override @Override
...@@ -132,7 +137,7 @@ public abstract class BaseRenderer implements Renderer, RendererCapabilities { ...@@ -132,7 +137,7 @@ public abstract class BaseRenderer implements Renderer, RendererCapabilities {
@Override @Override
public final void resetPosition(long positionUs) throws ExoPlaybackException { public final void resetPosition(long positionUs) throws ExoPlaybackException {
streamIsFinal = false; streamIsFinal = false;
readEndOfStream = false; readingPositionUs = positionUs;
onPositionReset(positionUs, false); onPositionReset(positionUs, false);
} }
...@@ -303,10 +308,11 @@ public abstract class BaseRenderer implements Renderer, RendererCapabilities { ...@@ -303,10 +308,11 @@ public abstract class BaseRenderer implements Renderer, RendererCapabilities {
int result = stream.readData(formatHolder, buffer, formatRequired); int result = stream.readData(formatHolder, buffer, formatRequired);
if (result == C.RESULT_BUFFER_READ) { if (result == C.RESULT_BUFFER_READ) {
if (buffer.isEndOfStream()) { if (buffer.isEndOfStream()) {
readEndOfStream = true; readingPositionUs = C.TIME_END_OF_SOURCE;
return streamIsFinal ? C.RESULT_BUFFER_READ : C.RESULT_NOTHING_READ; return streamIsFinal ? C.RESULT_BUFFER_READ : C.RESULT_NOTHING_READ;
} }
buffer.timeUs += streamOffsetUs; buffer.timeUs += streamOffsetUs;
readingPositionUs = Math.max(readingPositionUs, buffer.timeUs);
} else if (result == C.RESULT_FORMAT_READ) { } else if (result == C.RESULT_FORMAT_READ) {
Format format = formatHolder.format; Format format = formatHolder.format;
if (format.subsampleOffsetUs != Format.OFFSET_SAMPLE_RELATIVE) { if (format.subsampleOffsetUs != Format.OFFSET_SAMPLE_RELATIVE) {
...@@ -332,7 +338,7 @@ public abstract class BaseRenderer implements Renderer, RendererCapabilities { ...@@ -332,7 +338,7 @@ public abstract class BaseRenderer implements Renderer, RendererCapabilities {
* Returns whether the upstream source is ready. * Returns whether the upstream source is ready.
*/ */
protected final boolean isSourceReady() { protected final boolean isSourceReady() {
return readEndOfStream ? streamIsFinal : stream.isReady(); return hasReadStreamToEnd() ? streamIsFinal : stream.isReady();
} }
/** /**
......
...@@ -460,8 +460,8 @@ public final class C { ...@@ -460,8 +460,8 @@ public final class C {
/** /**
* Flags which can apply to a buffer containing a media sample. Possible flag values are {@link * Flags which can apply to a buffer containing a media sample. Possible flag values are {@link
* #BUFFER_FLAG_KEY_FRAME}, {@link #BUFFER_FLAG_END_OF_STREAM}, {@link #BUFFER_FLAG_ENCRYPTED} and * #BUFFER_FLAG_KEY_FRAME}, {@link #BUFFER_FLAG_END_OF_STREAM}, {@link #BUFFER_FLAG_LAST_SAMPLE},
* {@link #BUFFER_FLAG_DECODE_ONLY}. * {@link #BUFFER_FLAG_ENCRYPTED} and {@link #BUFFER_FLAG_DECODE_ONLY}.
*/ */
@Documented @Documented
@Retention(RetentionPolicy.SOURCE) @Retention(RetentionPolicy.SOURCE)
...@@ -470,6 +470,7 @@ public final class C { ...@@ -470,6 +470,7 @@ public final class C {
value = { value = {
BUFFER_FLAG_KEY_FRAME, BUFFER_FLAG_KEY_FRAME,
BUFFER_FLAG_END_OF_STREAM, BUFFER_FLAG_END_OF_STREAM,
BUFFER_FLAG_LAST_SAMPLE,
BUFFER_FLAG_ENCRYPTED, BUFFER_FLAG_ENCRYPTED,
BUFFER_FLAG_DECODE_ONLY BUFFER_FLAG_DECODE_ONLY
}) })
...@@ -482,6 +483,8 @@ public final class C { ...@@ -482,6 +483,8 @@ public final class C {
* Flag for empty buffers that signal that the end of the stream was reached. * Flag for empty buffers that signal that the end of the stream was reached.
*/ */
public static final int BUFFER_FLAG_END_OF_STREAM = MediaCodec.BUFFER_FLAG_END_OF_STREAM; public static final int BUFFER_FLAG_END_OF_STREAM = MediaCodec.BUFFER_FLAG_END_OF_STREAM;
/** Indicates that a buffer is known to contain the last media sample of the stream. */
public static final int BUFFER_FLAG_LAST_SAMPLE = 1 << 29; // 0x20000000
/** Indicates that a buffer is (at least partially) encrypted. */ /** Indicates that a buffer is (at least partially) encrypted. */
public static final int BUFFER_FLAG_ENCRYPTED = 1 << 30; // 0x40000000 public static final int BUFFER_FLAG_ENCRYPTED = 1 << 30; // 0x40000000
/** Indicates that a buffer should be decoded but not rendered. */ /** Indicates that a buffer should be decoded but not rendered. */
...@@ -533,9 +536,7 @@ public final class C { ...@@ -533,9 +536,7 @@ public final class C {
*/ */
public static final int SELECTION_FLAG_AUTOSELECT = 1 << 2; // 4 public static final int SELECTION_FLAG_AUTOSELECT = 1 << 2; // 4
/** /** Represents an undetermined language as an ISO 639-2 language code. */
* Represents an undetermined language as an ISO 639 alpha-3 language code.
*/
public static final String LANGUAGE_UNDETERMINED = "und"; public static final String LANGUAGE_UNDETERMINED = "und";
/** /**
......
...@@ -16,6 +16,7 @@ ...@@ -16,6 +16,7 @@
package com.google.android.exoplayer2; package com.google.android.exoplayer2;
import android.support.annotation.IntDef; import android.support.annotation.IntDef;
import android.support.annotation.Nullable;
import com.google.android.exoplayer2.source.MediaSource; import com.google.android.exoplayer2.source.MediaSource;
import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Assertions;
import java.io.IOException; import java.io.IOException;
...@@ -34,7 +35,7 @@ public final class ExoPlaybackException extends Exception { ...@@ -34,7 +35,7 @@ public final class ExoPlaybackException extends Exception {
*/ */
@Documented @Documented
@Retention(RetentionPolicy.SOURCE) @Retention(RetentionPolicy.SOURCE)
@IntDef({TYPE_SOURCE, TYPE_RENDERER, TYPE_UNEXPECTED}) @IntDef({TYPE_SOURCE, TYPE_RENDERER, TYPE_UNEXPECTED, TYPE_REMOTE})
public @interface Type {} public @interface Type {}
/** /**
* The error occurred loading data from a {@link MediaSource}. * The error occurred loading data from a {@link MediaSource}.
...@@ -54,6 +55,12 @@ public final class ExoPlaybackException extends Exception { ...@@ -54,6 +55,12 @@ public final class ExoPlaybackException extends Exception {
* Call {@link #getUnexpectedException()} to retrieve the underlying cause. * Call {@link #getUnexpectedException()} to retrieve the underlying cause.
*/ */
public static final int TYPE_UNEXPECTED = 2; public static final int TYPE_UNEXPECTED = 2;
/**
* The error occurred in a remote component.
*
* <p>Call {@link #getMessage()} to retrieve the message associated with the error.
*/
public static final int TYPE_REMOTE = 3;
/** /**
* The type of the playback failure. One of {@link #TYPE_SOURCE}, {@link #TYPE_RENDERER} and * The type of the playback failure. One of {@link #TYPE_SOURCE}, {@link #TYPE_RENDERER} and
...@@ -66,7 +73,7 @@ public final class ExoPlaybackException extends Exception { ...@@ -66,7 +73,7 @@ public final class ExoPlaybackException extends Exception {
*/ */
public final int rendererIndex; public final int rendererIndex;
private final Throwable cause; @Nullable private final Throwable cause;
/** /**
* Creates an instance of type {@link #TYPE_SOURCE}. * Creates an instance of type {@link #TYPE_SOURCE}.
...@@ -99,6 +106,16 @@ public final class ExoPlaybackException extends Exception { ...@@ -99,6 +106,16 @@ public final class ExoPlaybackException extends Exception {
return new ExoPlaybackException(TYPE_UNEXPECTED, cause, C.INDEX_UNSET); return new ExoPlaybackException(TYPE_UNEXPECTED, cause, C.INDEX_UNSET);
} }
/**
* Creates an instance of type {@link #TYPE_REMOTE}.
*
* @param message The message associated with the error.
* @return The created instance.
*/
public static ExoPlaybackException createForRemote(String message) {
return new ExoPlaybackException(TYPE_REMOTE, message);
}
private ExoPlaybackException(@Type int type, Throwable cause, int rendererIndex) { private ExoPlaybackException(@Type int type, Throwable cause, int rendererIndex) {
super(cause); super(cause);
this.type = type; this.type = type;
...@@ -106,6 +123,13 @@ public final class ExoPlaybackException extends Exception { ...@@ -106,6 +123,13 @@ public final class ExoPlaybackException extends Exception {
this.rendererIndex = rendererIndex; this.rendererIndex = rendererIndex;
} }
private ExoPlaybackException(@Type int type, String message) {
super(message);
this.type = type;
rendererIndex = C.INDEX_UNSET;
cause = null;
}
/** /**
* Retrieves the underlying error when {@link #type} is {@link #TYPE_SOURCE}. * Retrieves the underlying error when {@link #type} is {@link #TYPE_SOURCE}.
* *
...@@ -113,7 +137,7 @@ public final class ExoPlaybackException extends Exception { ...@@ -113,7 +137,7 @@ public final class ExoPlaybackException extends Exception {
*/ */
public IOException getSourceException() { public IOException getSourceException() {
Assertions.checkState(type == TYPE_SOURCE); Assertions.checkState(type == TYPE_SOURCE);
return (IOException) cause; return (IOException) Assertions.checkNotNull(cause);
} }
/** /**
...@@ -123,7 +147,7 @@ public final class ExoPlaybackException extends Exception { ...@@ -123,7 +147,7 @@ public final class ExoPlaybackException extends Exception {
*/ */
public Exception getRendererException() { public Exception getRendererException() {
Assertions.checkState(type == TYPE_RENDERER); Assertions.checkState(type == TYPE_RENDERER);
return (Exception) cause; return (Exception) Assertions.checkNotNull(cause);
} }
/** /**
...@@ -133,7 +157,7 @@ public final class ExoPlaybackException extends Exception { ...@@ -133,7 +157,7 @@ public final class ExoPlaybackException extends Exception {
*/ */
public RuntimeException getUnexpectedException() { public RuntimeException getUnexpectedException() {
Assertions.checkState(type == TYPE_UNEXPECTED); Assertions.checkState(type == TYPE_UNEXPECTED);
return (RuntimeException) cause; return (RuntimeException) Assertions.checkNotNull(cause);
} }
} }
...@@ -21,10 +21,10 @@ import com.google.android.exoplayer2.audio.MediaCodecAudioRenderer; ...@@ -21,10 +21,10 @@ import com.google.android.exoplayer2.audio.MediaCodecAudioRenderer;
import com.google.android.exoplayer2.metadata.MetadataRenderer; import com.google.android.exoplayer2.metadata.MetadataRenderer;
import com.google.android.exoplayer2.source.ClippingMediaSource; import com.google.android.exoplayer2.source.ClippingMediaSource;
import com.google.android.exoplayer2.source.ConcatenatingMediaSource; import com.google.android.exoplayer2.source.ConcatenatingMediaSource;
import com.google.android.exoplayer2.source.ExtractorMediaSource;
import com.google.android.exoplayer2.source.LoopingMediaSource; import com.google.android.exoplayer2.source.LoopingMediaSource;
import com.google.android.exoplayer2.source.MediaSource; import com.google.android.exoplayer2.source.MediaSource;
import com.google.android.exoplayer2.source.MergingMediaSource; import com.google.android.exoplayer2.source.MergingMediaSource;
import com.google.android.exoplayer2.source.ProgressiveMediaSource;
import com.google.android.exoplayer2.source.SingleSampleMediaSource; import com.google.android.exoplayer2.source.SingleSampleMediaSource;
import com.google.android.exoplayer2.text.TextRenderer; import com.google.android.exoplayer2.text.TextRenderer;
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector; import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
...@@ -48,7 +48,7 @@ import com.google.android.exoplayer2.video.MediaCodecVideoRenderer; ...@@ -48,7 +48,7 @@ import com.google.android.exoplayer2.video.MediaCodecVideoRenderer;
* <li>A <b>{@link MediaSource}</b> that defines the media to be played, loads the media, and from * <li>A <b>{@link MediaSource}</b> that defines the media to be played, loads the media, and from
* which the loaded media can be read. A MediaSource is injected via {@link * which the loaded media can be read. A MediaSource is injected via {@link
* #prepare(MediaSource)} at the start of playback. The library modules provide default * #prepare(MediaSource)} at the start of playback. The library modules provide default
* implementations for regular media files ({@link ExtractorMediaSource}), DASH * implementations for progressive media files ({@link ProgressiveMediaSource}), DASH
* (DashMediaSource), SmoothStreaming (SsMediaSource) and HLS (HlsMediaSource), an * (DashMediaSource), SmoothStreaming (SsMediaSource) and HLS (HlsMediaSource), an
* implementation for loading single media samples ({@link SingleSampleMediaSource}) that's * implementation for loading single media samples ({@link SingleSampleMediaSource}) that's
* most often used for side-loaded subtitle files, and implementations for building more * most often used for side-loaded subtitle files, and implementations for building more
......
...@@ -58,7 +58,8 @@ public final class ExoPlayerFactory { ...@@ -58,7 +58,8 @@ public final class ExoPlayerFactory {
LoadControl loadControl, LoadControl loadControl,
@Nullable DrmSessionManager<FrameworkMediaCrypto> drmSessionManager, @Nullable DrmSessionManager<FrameworkMediaCrypto> drmSessionManager,
@DefaultRenderersFactory.ExtensionRendererMode int extensionRendererMode) { @DefaultRenderersFactory.ExtensionRendererMode int extensionRendererMode) {
RenderersFactory renderersFactory = new DefaultRenderersFactory(context, extensionRendererMode); RenderersFactory renderersFactory =
new DefaultRenderersFactory(context).setExtensionRendererMode(extensionRendererMode);
return newSimpleInstance( return newSimpleInstance(
context, renderersFactory, trackSelector, loadControl, drmSessionManager); context, renderersFactory, trackSelector, loadControl, drmSessionManager);
} }
...@@ -88,7 +89,9 @@ public final class ExoPlayerFactory { ...@@ -88,7 +89,9 @@ public final class ExoPlayerFactory {
@DefaultRenderersFactory.ExtensionRendererMode int extensionRendererMode, @DefaultRenderersFactory.ExtensionRendererMode int extensionRendererMode,
long allowedVideoJoiningTimeMs) { long allowedVideoJoiningTimeMs) {
RenderersFactory renderersFactory = RenderersFactory renderersFactory =
new DefaultRenderersFactory(context, extensionRendererMode, allowedVideoJoiningTimeMs); new DefaultRenderersFactory(context)
.setExtensionRendererMode(extensionRendererMode)
.setAllowedVideoJoiningTimeMs(allowedVideoJoiningTimeMs);
return newSimpleInstance( return newSimpleInstance(
context, renderersFactory, trackSelector, loadControl, drmSessionManager); context, renderersFactory, trackSelector, loadControl, drmSessionManager);
} }
......
...@@ -1376,12 +1376,34 @@ import java.util.concurrent.atomic.AtomicBoolean; ...@@ -1376,12 +1376,34 @@ import java.util.concurrent.atomic.AtomicBoolean;
} }
} }
if (!queue.updateQueuedPeriods(playingPeriodId, rendererPositionUs)) { if (!queue.updateQueuedPeriods(rendererPositionUs, getMaxRendererReadPositionUs())) {
seekToCurrentPosition(/* sendDiscontinuity= */ false); seekToCurrentPosition(/* sendDiscontinuity= */ false);
} }
handleLoadingMediaPeriodChanged(/* loadingTrackSelectionChanged= */ false); handleLoadingMediaPeriodChanged(/* loadingTrackSelectionChanged= */ false);
} }
private long getMaxRendererReadPositionUs() {
MediaPeriodHolder readingHolder = queue.getReadingPeriod();
if (readingHolder == null) {
return 0;
}
long maxReadPositionUs = readingHolder.getRendererOffset();
for (int i = 0; i < renderers.length; i++) {
if (renderers[i].getState() == Renderer.STATE_DISABLED
|| renderers[i].getStream() != readingHolder.sampleStreams[i]) {
// Ignore disabled renderers and renderers with sample streams from previous periods.
continue;
}
long readingPositionUs = renderers[i].getReadingPositionUs();
if (readingPositionUs == C.TIME_END_OF_SOURCE) {
return C.TIME_END_OF_SOURCE;
} else {
maxReadPositionUs = Math.max(readingPositionUs, maxReadPositionUs);
}
}
return maxReadPositionUs;
}
private void handleSourceInfoRefreshEndedPlayback() { private void handleSourceInfoRefreshEndedPlayback() {
setState(Player.STATE_ENDED); setState(Player.STATE_ENDED);
// Reset, but retain the source so that it can still be used should a seek occur. // Reset, but retain the source so that it can still be used should a seek occur.
......
...@@ -29,11 +29,11 @@ public final class ExoPlayerLibraryInfo { ...@@ -29,11 +29,11 @@ public final class ExoPlayerLibraryInfo {
/** The version of the library expressed as a string, for example "1.2.3". */ /** 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. // Intentionally hardcoded. Do not derive from other constants (e.g. VERSION_INT) or vice versa.
public static final String VERSION = "2.9.3"; public static final String VERSION = "2.9.5";
/** The version of the library expressed as {@code "ExoPlayerLib/" + VERSION}. */ /** The version of the library expressed as {@code "ExoPlayerLib/" + VERSION}. */
// Intentionally hardcoded. Do not derive from other constants (e.g. VERSION) or vice versa. // Intentionally hardcoded. Do not derive from other constants (e.g. VERSION) or vice versa.
public static final String VERSION_SLASHY = "ExoPlayerLib/2.9.3"; public static final String VERSION_SLASHY = "ExoPlayerLib/2.9.5";
/** /**
* The version of the library expressed as an integer, for example 1002003. * The version of the library expressed as an integer, for example 1002003.
...@@ -43,7 +43,7 @@ public final class ExoPlayerLibraryInfo { ...@@ -43,7 +43,7 @@ public final class ExoPlayerLibraryInfo {
* integer version 123045006 (123-045-006). * integer version 123045006 (123-045-006).
*/ */
// Intentionally hardcoded. Do not derive from other constants (e.g. VERSION) or vice versa. // Intentionally hardcoded. Do not derive from other constants (e.g. VERSION) or vice versa.
public static final int VERSION_INT = 2009003; public static final int VERSION_INT = 2009005;
/** /**
* Whether the library was compiled with {@link com.google.android.exoplayer2.util.Assertions} * Whether the library was compiled with {@link com.google.android.exoplayer2.util.Assertions}
......
...@@ -159,7 +159,7 @@ public final class Format implements Parcelable { ...@@ -159,7 +159,7 @@ public final class Format implements Parcelable {
@C.SelectionFlags @C.SelectionFlags
public final int selectionFlags; public final int selectionFlags;
/** The language, or null if unknown or not applicable. */ /** The language as ISO 639-2/T three-letter code, or null if unknown or not applicable. */
public final @Nullable String language; public final @Nullable String language;
/** /**
...@@ -932,7 +932,7 @@ public final class Format implements Parcelable { ...@@ -932,7 +932,7 @@ public final class Format implements Parcelable {
this.encoderDelay = encoderDelay == Format.NO_VALUE ? 0 : encoderDelay; this.encoderDelay = encoderDelay == Format.NO_VALUE ? 0 : encoderDelay;
this.encoderPadding = encoderPadding == Format.NO_VALUE ? 0 : encoderPadding; this.encoderPadding = encoderPadding == Format.NO_VALUE ? 0 : encoderPadding;
this.selectionFlags = selectionFlags; this.selectionFlags = selectionFlags;
this.language = language; this.language = Util.normalizeLanguageCode(language);
this.accessibilityChannel = accessibilityChannel; this.accessibilityChannel = accessibilityChannel;
this.subsampleOffsetUs = subsampleOffsetUs; this.subsampleOffsetUs = subsampleOffsetUs;
this.initializationData = this.initializationData =
......
...@@ -89,7 +89,9 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; ...@@ -89,7 +89,9 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
this.info = info; this.info = info;
sampleStreams = new SampleStream[rendererCapabilities.length]; sampleStreams = new SampleStream[rendererCapabilities.length];
mayRetainStreamFlags = new boolean[rendererCapabilities.length]; mayRetainStreamFlags = new boolean[rendererCapabilities.length];
mediaPeriod = createMediaPeriod(info.id, mediaSource, allocator); mediaPeriod =
createMediaPeriod(
info.id, mediaSource, allocator, info.startPositionUs, info.endPositionUs);
} }
/** /**
...@@ -294,7 +296,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; ...@@ -294,7 +296,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
public void release() { public void release() {
disableTrackSelectionsInResult(); disableTrackSelectionsInResult();
trackSelectorResult = null; trackSelectorResult = null;
releaseMediaPeriod(info.id, mediaSource, mediaPeriod); releaseMediaPeriod(info.endPositionUs, mediaSource, mediaPeriod);
} }
/** /**
...@@ -399,24 +401,25 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; ...@@ -399,24 +401,25 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
/** Returns a media period corresponding to the given {@code id}. */ /** Returns a media period corresponding to the given {@code id}. */
private static MediaPeriod createMediaPeriod( private static MediaPeriod createMediaPeriod(
MediaPeriodId id, MediaSource mediaSource, Allocator allocator) { MediaPeriodId id,
MediaPeriod mediaPeriod = mediaSource.createPeriod(id, allocator); MediaSource mediaSource,
if (id.endPositionUs != C.TIME_UNSET && id.endPositionUs != C.TIME_END_OF_SOURCE) { Allocator allocator,
long startPositionUs,
long endPositionUs) {
MediaPeriod mediaPeriod = mediaSource.createPeriod(id, allocator, startPositionUs);
if (endPositionUs != C.TIME_UNSET && endPositionUs != C.TIME_END_OF_SOURCE) {
mediaPeriod = mediaPeriod =
new ClippingMediaPeriod( new ClippingMediaPeriod(
mediaPeriod, mediaPeriod, /* enableInitialDiscontinuity= */ true, /* startUs= */ 0, endPositionUs);
/* enableInitialDiscontinuity= */ true,
/* startUs= */ 0,
id.endPositionUs);
} }
return mediaPeriod; return mediaPeriod;
} }
/** Releases the given {@code mediaPeriod}, logging and suppressing any errors. */ /** Releases the given {@code mediaPeriod}, logging and suppressing any errors. */
private static void releaseMediaPeriod( private static void releaseMediaPeriod(
MediaPeriodId id, MediaSource mediaSource, MediaPeriod mediaPeriod) { long endPositionUs, MediaSource mediaSource, MediaPeriod mediaPeriod) {
try { try {
if (id.endPositionUs != C.TIME_UNSET && id.endPositionUs != C.TIME_END_OF_SOURCE) { if (endPositionUs != C.TIME_UNSET && endPositionUs != C.TIME_END_OF_SOURCE) {
mediaSource.releasePeriod(((ClippingMediaPeriod) mediaPeriod).mediaPeriod); mediaSource.releasePeriod(((ClippingMediaPeriod) mediaPeriod).mediaPeriod);
} else { } else {
mediaSource.releasePeriod(mediaPeriod); mediaSource.releasePeriod(mediaPeriod);
......
...@@ -33,7 +33,14 @@ import com.google.android.exoplayer2.util.Util; ...@@ -33,7 +33,14 @@ import com.google.android.exoplayer2.util.Util;
*/ */
public final long contentPositionUs; public final long contentPositionUs;
/** /**
* The duration of the media period, like {@link MediaPeriodId#endPositionUs} but with {@link * The end position to which the media period's content is clipped in order to play a following ad
* group, in microseconds, or {@link C#TIME_UNSET} if there is no following ad group or if this
* media period is an ad. The value {@link C#TIME_END_OF_SOURCE} indicates that a postroll ad
* follows at the end of this content media period.
*/
public final long endPositionUs;
/**
* The duration of the media period, like {@link #endPositionUs} but with {@link
* C#TIME_END_OF_SOURCE} and {@link C#TIME_UNSET} resolved to the timeline period duration if * C#TIME_END_OF_SOURCE} and {@link C#TIME_UNSET} resolved to the timeline period duration if
* known. * known.
*/ */
...@@ -53,26 +60,51 @@ import com.google.android.exoplayer2.util.Util; ...@@ -53,26 +60,51 @@ import com.google.android.exoplayer2.util.Util;
MediaPeriodId id, MediaPeriodId id,
long startPositionUs, long startPositionUs,
long contentPositionUs, long contentPositionUs,
long endPositionUs,
long durationUs, long durationUs,
boolean isLastInTimelinePeriod, boolean isLastInTimelinePeriod,
boolean isFinal) { boolean isFinal) {
this.id = id; this.id = id;
this.startPositionUs = startPositionUs; this.startPositionUs = startPositionUs;
this.contentPositionUs = contentPositionUs; this.contentPositionUs = contentPositionUs;
this.endPositionUs = endPositionUs;
this.durationUs = durationUs; this.durationUs = durationUs;
this.isLastInTimelinePeriod = isLastInTimelinePeriod; this.isLastInTimelinePeriod = isLastInTimelinePeriod;
this.isFinal = isFinal; this.isFinal = isFinal;
} }
/** Returns a copy of this instance with the start position set to the specified value. */ /**
* Returns a copy of this instance with the start position set to the specified value. May return
* the same instance if nothing changed.
*/
public MediaPeriodInfo copyWithStartPositionUs(long startPositionUs) { public MediaPeriodInfo copyWithStartPositionUs(long startPositionUs) {
return new MediaPeriodInfo( return startPositionUs == this.startPositionUs
id, ? this
startPositionUs, : new MediaPeriodInfo(
contentPositionUs, id,
durationUs, startPositionUs,
isLastInTimelinePeriod, contentPositionUs,
isFinal); endPositionUs,
durationUs,
isLastInTimelinePeriod,
isFinal);
}
/**
* Returns a copy of this instance with the content position set to the specified value. May
* return the same instance if nothing changed.
*/
public MediaPeriodInfo copyWithContentPositionUs(long contentPositionUs) {
return contentPositionUs == this.contentPositionUs
? this
: new MediaPeriodInfo(
id,
startPositionUs,
contentPositionUs,
endPositionUs,
durationUs,
isLastInTimelinePeriod,
isFinal);
} }
@Override @Override
...@@ -86,6 +118,7 @@ import com.google.android.exoplayer2.util.Util; ...@@ -86,6 +118,7 @@ import com.google.android.exoplayer2.util.Util;
MediaPeriodInfo that = (MediaPeriodInfo) o; MediaPeriodInfo that = (MediaPeriodInfo) o;
return startPositionUs == that.startPositionUs return startPositionUs == that.startPositionUs
&& contentPositionUs == that.contentPositionUs && contentPositionUs == that.contentPositionUs
&& endPositionUs == that.endPositionUs
&& durationUs == that.durationUs && durationUs == that.durationUs
&& isLastInTimelinePeriod == that.isLastInTimelinePeriod && isLastInTimelinePeriod == that.isLastInTimelinePeriod
&& isFinal == that.isFinal && isFinal == that.isFinal
...@@ -98,6 +131,7 @@ import com.google.android.exoplayer2.util.Util; ...@@ -98,6 +131,7 @@ import com.google.android.exoplayer2.util.Util;
result = 31 * result + id.hashCode(); result = 31 * result + id.hashCode();
result = 31 * result + (int) startPositionUs; result = 31 * result + (int) startPositionUs;
result = 31 * result + (int) contentPositionUs; result = 31 * result + (int) contentPositionUs;
result = 31 * result + (int) endPositionUs;
result = 31 * result + (int) durationUs; result = 31 * result + (int) durationUs;
result = 31 * result + (isLastInTimelinePeriod ? 1 : 0); result = 31 * result + (isLastInTimelinePeriod ? 1 : 0);
result = 31 * result + (isFinal ? 1 : 0); result = 31 * result + (isFinal ? 1 : 0);
......
...@@ -123,6 +123,11 @@ public abstract class NoSampleRenderer implements Renderer, RendererCapabilities ...@@ -123,6 +123,11 @@ public abstract class NoSampleRenderer implements Renderer, RendererCapabilities
} }
@Override @Override
public long getReadingPositionUs() {
return C.TIME_END_OF_SOURCE;
}
@Override
public final void setCurrentStreamFinal() { public final void setCurrentStreamFinal() {
streamIsFinal = true; streamIsFinal = true;
} }
......
...@@ -161,6 +161,16 @@ public interface Renderer extends PlayerMessage.Target { ...@@ -161,6 +161,16 @@ public interface Renderer extends PlayerMessage.Target {
boolean hasReadStreamToEnd(); boolean hasReadStreamToEnd();
/** /**
* Returns the playback position up to which the renderer has read samples from the current {@link
* SampleStream}, in microseconds, or {@link C#TIME_END_OF_SOURCE} if the renderer has read the
* current {@link SampleStream} to the end.
*
* <p>This method may be called when the renderer is in the following states: {@link
* #STATE_ENABLED}, {@link #STATE_STARTED}.
*/
long getReadingPositionUs();
/**
* Signals to the renderer that the current {@link SampleStream} will be the final one supplied * Signals to the renderer that the current {@link SampleStream} will be the final one supplied
* before it is next disabled or reset. * before it is next disabled or reset.
* <p> * <p>
......
...@@ -63,7 +63,6 @@ import java.util.concurrent.CopyOnWriteArraySet; ...@@ -63,7 +63,6 @@ import java.util.concurrent.CopyOnWriteArraySet;
* An {@link ExoPlayer} implementation that uses default {@link Renderer} components. Instances can * An {@link ExoPlayer} implementation that uses default {@link Renderer} components. Instances can
* be obtained from {@link ExoPlayerFactory}. * be obtained from {@link ExoPlayerFactory}.
*/ */
@TargetApi(16)
public class SimpleExoPlayer extends BasePlayer public class SimpleExoPlayer extends BasePlayer
implements ExoPlayer, implements ExoPlayer,
Player.AudioComponent, Player.AudioComponent,
...@@ -94,25 +93,25 @@ public class SimpleExoPlayer extends BasePlayer ...@@ -94,25 +93,25 @@ public class SimpleExoPlayer extends BasePlayer
private final AudioFocusManager audioFocusManager; private final AudioFocusManager audioFocusManager;
private Format videoFormat; @Nullable private Format videoFormat;
private Format audioFormat; @Nullable private Format audioFormat;
private Surface surface; @Nullable private Surface surface;
private boolean ownsSurface; private boolean ownsSurface;
private @C.VideoScalingMode int videoScalingMode; private @C.VideoScalingMode int videoScalingMode;
private SurfaceHolder surfaceHolder; @Nullable private SurfaceHolder surfaceHolder;
private TextureView textureView; @Nullable private TextureView textureView;
private int surfaceWidth; private int surfaceWidth;
private int surfaceHeight; private int surfaceHeight;
private DecoderCounters videoDecoderCounters; @Nullable private DecoderCounters videoDecoderCounters;
private DecoderCounters audioDecoderCounters; @Nullable private DecoderCounters audioDecoderCounters;
private int audioSessionId; private int audioSessionId;
private AudioAttributes audioAttributes; private AudioAttributes audioAttributes;
private float audioVolume; private float audioVolume;
private MediaSource mediaSource; @Nullable private MediaSource mediaSource;
private List<Cue> currentCues; private List<Cue> currentCues;
private VideoFrameMetadataListener videoFrameMetadataListener; @Nullable private VideoFrameMetadataListener videoFrameMetadataListener;
private CameraMotionListener cameraMotionListener; @Nullable private CameraMotionListener cameraMotionListener;
private boolean hasNotifiedFullWrongThreadWarning; private boolean hasNotifiedFullWrongThreadWarning;
/** /**
...@@ -558,30 +557,26 @@ public class SimpleExoPlayer extends BasePlayer ...@@ -558,30 +557,26 @@ public class SimpleExoPlayer extends BasePlayer
setPlaybackParameters(playbackParameters); setPlaybackParameters(playbackParameters);
} }
/** /** Returns the video format currently being played, or null if no video is being played. */
* Returns the video format currently being played, or null if no video is being played. @Nullable
*/
public Format getVideoFormat() { public Format getVideoFormat() {
return videoFormat; return videoFormat;
} }
/** /** Returns the audio format currently being played, or null if no audio is being played. */
* Returns the audio format currently being played, or null if no audio is being played. @Nullable
*/
public Format getAudioFormat() { public Format getAudioFormat() {
return audioFormat; return audioFormat;
} }
/** /** Returns {@link DecoderCounters} for video, or null if no video is being played. */
* Returns {@link DecoderCounters} for video, or null if no video is being played. @Nullable
*/
public DecoderCounters getVideoDecoderCounters() { public DecoderCounters getVideoDecoderCounters() {
return videoDecoderCounters; return videoDecoderCounters;
} }
/** /** Returns {@link DecoderCounters} for audio, or null if no audio is being played. */
* Returns {@link DecoderCounters} for audio, or null if no audio is being played. @Nullable
*/
public DecoderCounters getAudioDecoderCounters() { public DecoderCounters getAudioDecoderCounters() {
return audioDecoderCounters; return audioDecoderCounters;
} }
...@@ -1053,7 +1048,8 @@ public class SimpleExoPlayer extends BasePlayer ...@@ -1053,7 +1048,8 @@ public class SimpleExoPlayer extends BasePlayer
} }
@Override @Override
public @Nullable Object getCurrentManifest() { @Nullable
public Object getCurrentManifest() {
verifyApplicationThread(); verifyApplicationThread();
return player.getCurrentManifest(); return player.getCurrentManifest();
} }
......
...@@ -129,12 +129,13 @@ public class AnalyticsCollector ...@@ -129,12 +129,13 @@ public class AnalyticsCollector
/** /**
* Sets the player for which data will be collected. Must only be called if no player has been set * Sets the player for which data will be collected. Must only be called if no player has been set
* yet. * yet or the current player is idle.
* *
* @param player The {@link Player} for which data will be collected. * @param player The {@link Player} for which data will be collected.
*/ */
public void setPlayer(Player player) { public void setPlayer(Player player) {
Assertions.checkState(this.player == null); Assertions.checkState(
this.player == null || mediaPeriodQueueTracker.mediaPeriodInfoQueue.isEmpty());
this.player = Assertions.checkNotNull(player); this.player = Assertions.checkNotNull(player);
} }
...@@ -488,7 +489,10 @@ public class AnalyticsCollector ...@@ -488,7 +489,10 @@ public class AnalyticsCollector
@Override @Override
public final void onPlayerError(ExoPlaybackException error) { public final void onPlayerError(ExoPlaybackException error) {
EventTime eventTime = generatePlayingMediaPeriodEventTime(); EventTime eventTime =
error.type == ExoPlaybackException.TYPE_SOURCE
? generateLoadingMediaPeriodEventTime()
: generatePlayingMediaPeriodEventTime();
for (AnalyticsListener listener : listeners) { for (AnalyticsListener listener : listeners) {
listener.onPlayerError(eventTime, error); listener.onPlayerError(eventTime, error);
} }
......
...@@ -147,6 +147,7 @@ public interface AudioRendererEventListener { ...@@ -147,6 +147,7 @@ public interface AudioRendererEventListener {
* Invokes {@link AudioRendererEventListener#onAudioDisabled(DecoderCounters)}. * Invokes {@link AudioRendererEventListener#onAudioDisabled(DecoderCounters)}.
*/ */
public void disabled(final DecoderCounters counters) { public void disabled(final DecoderCounters counters) {
counters.ensureUpdated();
if (listener != null) { if (listener != null) {
handler.post( handler.post(
() -> { () -> {
......
...@@ -418,7 +418,7 @@ public final class DefaultAudioSink implements AudioSink { ...@@ -418,7 +418,7 @@ public final class DefaultAudioSink implements AudioSink {
isInputPcm = Util.isEncodingLinearPcm(inputEncoding); isInputPcm = Util.isEncodingLinearPcm(inputEncoding);
shouldConvertHighResIntPcmToFloat = shouldConvertHighResIntPcmToFloat =
enableConvertHighResIntPcmToFloat enableConvertHighResIntPcmToFloat
&& supportsOutput(channelCount, C.ENCODING_PCM_32BIT) && supportsOutput(channelCount, C.ENCODING_PCM_FLOAT)
&& Util.isEncodingHighResolutionIntegerPcm(inputEncoding); && Util.isEncodingHighResolutionIntegerPcm(inputEncoding);
if (isInputPcm) { if (isInputPcm) {
pcmFrameSize = Util.getPcmFrameSize(inputEncoding, channelCount); pcmFrameSize = Util.getPcmFrameSize(inputEncoding, channelCount);
......
...@@ -16,7 +16,6 @@ ...@@ -16,7 +16,6 @@
package com.google.android.exoplayer2.audio; package com.google.android.exoplayer2.audio;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.content.Context; import android.content.Context;
import android.media.MediaCodec; import android.media.MediaCodec;
import android.media.MediaCrypto; import android.media.MediaCrypto;
...@@ -66,7 +65,6 @@ import java.util.List; ...@@ -66,7 +65,6 @@ import java.util.List;
* underlying audio track. * underlying audio track.
* </ul> * </ul>
*/ */
@TargetApi(16)
public class MediaCodecAudioRenderer extends MediaCodecRenderer implements MediaClock { public class MediaCodecAudioRenderer extends MediaCodecRenderer implements MediaClock {
/** /**
...@@ -548,7 +546,6 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media ...@@ -548,7 +546,6 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
try { try {
super.onDisabled(); super.onDisabled();
} finally { } finally {
decoderCounters.ensureUpdated();
eventDispatcher.disabled(decoderCounters); eventDispatcher.disabled(decoderCounters);
} }
} }
......
...@@ -106,8 +106,8 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements ...@@ -106,8 +106,8 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements
? extends AudioDecoderException> decoder; ? extends AudioDecoderException> decoder;
private DecoderInputBuffer inputBuffer; private DecoderInputBuffer inputBuffer;
private SimpleOutputBuffer outputBuffer; private SimpleOutputBuffer outputBuffer;
private DrmSession<ExoMediaCrypto> drmSession; @Nullable private DrmSession<ExoMediaCrypto> decoderDrmSession;
private DrmSession<ExoMediaCrypto> pendingDrmSession; @Nullable private DrmSession<ExoMediaCrypto> sourceDrmSession;
@ReinitializationState private int decoderReinitializationState; @ReinitializationState private int decoderReinitializationState;
private boolean decoderReceivedBuffers; private boolean decoderReceivedBuffers;
...@@ -462,12 +462,12 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements ...@@ -462,12 +462,12 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements
} }
private boolean shouldWaitForKeys(boolean bufferEncrypted) throws ExoPlaybackException { private boolean shouldWaitForKeys(boolean bufferEncrypted) throws ExoPlaybackException {
if (drmSession == null || (!bufferEncrypted && playClearSamplesWithoutKeys)) { if (decoderDrmSession == null || (!bufferEncrypted && playClearSamplesWithoutKeys)) {
return false; return false;
} }
@DrmSession.State int drmSessionState = drmSession.getState(); @DrmSession.State int drmSessionState = decoderDrmSession.getState();
if (drmSessionState == DrmSession.STATE_ERROR) { if (drmSessionState == DrmSession.STATE_ERROR) {
throw ExoPlaybackException.createForRenderer(drmSession.getError(), getIndex()); throw ExoPlaybackException.createForRenderer(decoderDrmSession.getError(), getIndex());
} }
return drmSessionState != DrmSession.STATE_OPENED_WITH_KEYS; return drmSessionState != DrmSession.STATE_OPENED_WITH_KEYS;
} }
...@@ -568,25 +568,11 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements ...@@ -568,25 +568,11 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements
audioTrackNeedsConfigure = true; audioTrackNeedsConfigure = true;
waitingForKeys = false; waitingForKeys = false;
try { try {
setSourceDrmSession(null);
releaseDecoder(); releaseDecoder();
audioSink.reset(); audioSink.reset();
} finally { } finally {
try { eventDispatcher.disabled(decoderCounters);
if (drmSession != null) {
drmSessionManager.releaseSession(drmSession);
}
} finally {
try {
if (pendingDrmSession != null && pendingDrmSession != drmSession) {
drmSessionManager.releaseSession(pendingDrmSession);
}
} finally {
drmSession = null;
pendingDrmSession = null;
decoderCounters.ensureUpdated();
eventDispatcher.disabled(decoderCounters);
}
}
} }
} }
...@@ -615,12 +601,13 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements ...@@ -615,12 +601,13 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements
return; return;
} }
drmSession = pendingDrmSession; setDecoderDrmSession(sourceDrmSession);
ExoMediaCrypto mediaCrypto = null; ExoMediaCrypto mediaCrypto = null;
if (drmSession != null) { if (decoderDrmSession != null) {
mediaCrypto = drmSession.getMediaCrypto(); mediaCrypto = decoderDrmSession.getMediaCrypto();
if (mediaCrypto == null) { if (mediaCrypto == null) {
DrmSessionException drmError = drmSession.getError(); DrmSessionException drmError = decoderDrmSession.getError();
if (drmError != null) { if (drmError != null) {
// Continue for now. We may be able to avoid failure if the session recovers, or if a new // Continue for now. We may be able to avoid failure if the session recovers, or if a new
// input format causes the session to be replaced before it's used. // input format causes the session to be replaced before it's used.
...@@ -646,17 +633,34 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements ...@@ -646,17 +633,34 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements
} }
private void releaseDecoder() { private void releaseDecoder() {
if (decoder == null) {
return;
}
inputBuffer = null; inputBuffer = null;
outputBuffer = null; outputBuffer = null;
decoder.release();
decoder = null;
decoderCounters.decoderReleaseCount++;
decoderReinitializationState = REINITIALIZATION_STATE_NONE; decoderReinitializationState = REINITIALIZATION_STATE_NONE;
decoderReceivedBuffers = false; decoderReceivedBuffers = false;
if (decoder != null) {
decoder.release();
decoder = null;
decoderCounters.decoderReleaseCount++;
}
setDecoderDrmSession(null);
}
private void setSourceDrmSession(@Nullable DrmSession<ExoMediaCrypto> session) {
DrmSession<ExoMediaCrypto> previous = sourceDrmSession;
sourceDrmSession = session;
releaseDrmSessionIfUnused(previous);
}
private void setDecoderDrmSession(@Nullable DrmSession<ExoMediaCrypto> session) {
DrmSession<ExoMediaCrypto> previous = decoderDrmSession;
decoderDrmSession = session;
releaseDrmSessionIfUnused(previous);
}
private void releaseDrmSessionIfUnused(@Nullable DrmSession<ExoMediaCrypto> session) {
if (session != null && session != decoderDrmSession && session != sourceDrmSession) {
drmSessionManager.releaseSession(session);
}
} }
private void onInputFormatChanged(Format newFormat) throws ExoPlaybackException { private void onInputFormatChanged(Format newFormat) throws ExoPlaybackException {
...@@ -671,13 +675,16 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements ...@@ -671,13 +675,16 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements
throw ExoPlaybackException.createForRenderer( throw ExoPlaybackException.createForRenderer(
new IllegalStateException("Media requires a DrmSessionManager"), getIndex()); new IllegalStateException("Media requires a DrmSessionManager"), getIndex());
} }
pendingDrmSession = drmSessionManager.acquireSession(Looper.myLooper(), DrmSession<ExoMediaCrypto> session =
inputFormat.drmInitData); drmSessionManager.acquireSession(Looper.myLooper(), newFormat.drmInitData);
if (pendingDrmSession == drmSession) { if (session == decoderDrmSession || session == sourceDrmSession) {
drmSessionManager.releaseSession(pendingDrmSession); // We already had this session. The manager must be reference counting, so release it once
// to get the count attributed to this renderer back down to 1.
drmSessionManager.releaseSession(session);
} }
setSourceDrmSession(session);
} else { } else {
pendingDrmSession = null; setSourceDrmSession(null);
} }
} }
......
/*
* 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.database;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteException;
/**
* Provides {@link SQLiteDatabase} instances to ExoPlayer components, which may read and write
* tables prefixed with {@link #TABLE_PREFIX}.
*/
public interface DatabaseProvider {
/** Prefix for tables that can be read and written by ExoPlayer components. */
String TABLE_PREFIX = "ExoPlayer";
/**
* Creates and/or opens a database that will be used for reading and writing.
*
* <p>Once opened successfully, the database is cached, so you can call this method every time you
* need to write to the database. Errors such as bad permissions or a full disk may cause this
* method to fail, but future attempts may succeed if the problem is fixed.
*
* @throws SQLiteException If the database cannot be opened for writing.
* @return A read/write database object.
*/
SQLiteDatabase getWritableDatabase();
/**
* Creates and/or opens a database. This will be the same object returned by {@link
* #getWritableDatabase()} unless some problem, such as a full disk, requires the database to be
* opened read-only. In that case, a read-only database object will be returned. If the problem is
* fixed, a future call to {@link #getWritableDatabase()} may succeed, in which case the read-only
* database object will be closed and the read/write object will be returned in the future.
*
* <p>Once opened successfully, the database is cached, so you can call this method every time you
* need to read from the database.
*
* @throws SQLiteException If the database cannot be opened.
* @return A database object valid until {@link #getWritableDatabase()} is called.
*/
SQLiteDatabase getReadableDatabase();
}
/*
* 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.database;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
/** A {@link DatabaseProvider} that provides instances obtained from a {@link SQLiteOpenHelper}. */
public final class DefaultDatabaseProvider implements DatabaseProvider {
private final SQLiteOpenHelper sqliteOpenHelper;
/**
* @param sqliteOpenHelper An {@link SQLiteOpenHelper} from which to obtain database instances.
*/
public DefaultDatabaseProvider(SQLiteOpenHelper sqliteOpenHelper) {
this.sqliteOpenHelper = sqliteOpenHelper;
}
@Override
public SQLiteDatabase getWritableDatabase() {
return sqliteOpenHelper.getWritableDatabase();
}
@Override
public SQLiteDatabase getReadableDatabase() {
return sqliteOpenHelper.getReadableDatabase();
}
}
/*
* 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.database;
import android.content.Context;
import android.content.ContextWrapper;
import android.database.Cursor;
import android.database.DatabaseErrorHandler;
import android.database.SQLException;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.support.annotation.Nullable;
import com.google.android.exoplayer2.util.Log;
import java.io.File;
/**
* An {@link SQLiteOpenHelper} that provides instances of a standalone ExoPlayer database.
*
* <p>Suitable for use by applications that do not already have their own database, or which would
* prefer to keep ExoPlayer tables isolated in their own database. Other applications should prefer
* to use {@link DefaultDatabaseProvider} with their own {@link SQLiteOpenHelper}.
*/
public final class ExoDatabaseProvider extends SQLiteOpenHelper implements DatabaseProvider {
/** The file name used for the standalone ExoPlayer database. */
public static final String DATABASE_NAME = "exoplayer_internal.db";
private static final int VERSION = 1;
private static final String TAG = "ExoDatabaseProvider";
/**
* Provides instances of the database located by passing {@link #DATABASE_NAME} to {@link
* Context#getDatabasePath(String)}.
*
* @param context Any context.
*/
public ExoDatabaseProvider(Context context) {
super(context.getApplicationContext(), DATABASE_NAME, /* factory= */ null, VERSION);
}
/**
* Provides instances of the database located at the specified file.
*
* @param file The database file.
*/
public ExoDatabaseProvider(File file) {
super(new DatabaseFileProvidingContext(file), file.getName(), /* factory= */ null, VERSION);
}
@Override
public void onCreate(SQLiteDatabase db) {
// Features create their own tables.
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
// Features handle their own upgrades.
}
@Override
public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
wipeDatabase(db);
}
/**
* Makes a best effort to wipe the existing database. The wipe may be incomplete if the database
* contains foreign key constraints.
*/
private static void wipeDatabase(SQLiteDatabase db) {
String[] columns = {"type", "name"};
try (Cursor cursor =
db.query(
"sqlite_master",
columns,
/* selection= */ null,
/* selectionArgs= */ null,
/* groupBy= */ null,
/* having= */ null,
/* orderBy= */ null)) {
while (cursor.moveToNext()) {
String type = cursor.getString(0);
String name = cursor.getString(1);
if (!"sqlite_sequence".equals(name)) {
// If it's not an SQL-controlled entity, drop it
String sql = "DROP " + type + " IF EXISTS " + name;
try {
db.execSQL(sql);
} catch (SQLException e) {
Log.e(TAG, "Error executing " + sql, e);
}
}
}
}
}
// TODO: This is fragile. Stop using it if/when SQLiteOpenHelper can be instantiated without a
// context [Internal ref: b/123351819], or by injecting a Context into all components that need
// to instantiate an ExoDatabaseProvider.
/** A {@link Context} that implements methods called by {@link SQLiteOpenHelper}. */
private static class DatabaseFileProvidingContext extends ContextWrapper {
private final File file;
@SuppressWarnings("nullness:argument.type.incompatible")
public DatabaseFileProvidingContext(File file) {
super(/* base= */ null);
this.file = file;
}
@Override
public File getDatabasePath(String name) {
return file;
}
@Override
public SQLiteDatabase openOrCreateDatabase(
String name, int mode, SQLiteDatabase.CursorFactory factory) {
return openOrCreateDatabase(name, mode, factory, /* errorHandler= */ null);
}
@Override
@SuppressWarnings("nullness:argument.type.incompatible")
public SQLiteDatabase openOrCreateDatabase(
String name,
int mode,
SQLiteDatabase.CursorFactory factory,
@Nullable DatabaseErrorHandler errorHandler) {
File databasePath = getDatabasePath(name);
int flags = SQLiteDatabase.CREATE_IF_NECESSARY;
if ((mode & MODE_ENABLE_WRITE_AHEAD_LOGGING) != 0) {
flags |= SQLiteDatabase.ENABLE_WRITE_AHEAD_LOGGING;
}
if ((mode & MODE_NO_LOCALIZED_COLLATORS) != 0) {
flags |= SQLiteDatabase.NO_LOCALIZED_COLLATORS;
}
return SQLiteDatabase.openDatabase(databasePath.getPath(), factory, flags, errorHandler);
}
}
}
/*
* 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.database;
import android.content.ContentValues;
import android.database.Cursor;
import android.database.DatabaseUtils;
import android.database.sqlite.SQLiteDatabase;
import android.support.annotation.IntDef;
import android.support.annotation.VisibleForTesting;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
* Utility methods for accessing versions of ExoPlayer database components. This allows them to be
* versioned independently to the version of the containing database.
*/
public final class VersionTable {
/** Returned by {@link #getVersion(SQLiteDatabase, int)} if the version is unset. */
public static final int VERSION_UNSET = -1;
/** Version of tables used for offline functionality. */
public static final int FEATURE_OFFLINE = 0;
/** Version of tables used for cache content metadata. */
public static final int FEATURE_CACHE_CONTENT_METADATA = 1;
/** Version of tables used for cache file metadata. */
public static final int FEATURE_CACHE_FILE_METADATA = 2;
private static final String TABLE_NAME = DatabaseProvider.TABLE_PREFIX + "Versions";
private static final String COLUMN_FEATURE = "feature";
private static final String COLUMN_VERSION = "version";
private static final String SQL_CREATE_TABLE_IF_NOT_EXISTS =
"CREATE TABLE IF NOT EXISTS "
+ TABLE_NAME
+ " ("
+ COLUMN_FEATURE
+ " INTEGER PRIMARY KEY NOT NULL,"
+ COLUMN_VERSION
+ " INTEGER NOT NULL)";
@Documented
@Retention(RetentionPolicy.SOURCE)
@IntDef({FEATURE_OFFLINE, FEATURE_CACHE_CONTENT_METADATA, FEATURE_CACHE_FILE_METADATA})
private @interface Feature {}
private VersionTable() {}
/**
* Sets the version of tables belonging to the specified feature.
*
* @param writableDatabase The database to update.
* @param feature The feature.
* @param version The version.
*/
public static void setVersion(
SQLiteDatabase writableDatabase, @Feature int feature, int version) {
writableDatabase.execSQL(SQL_CREATE_TABLE_IF_NOT_EXISTS);
ContentValues values = new ContentValues();
values.put(COLUMN_FEATURE, feature);
values.put(COLUMN_VERSION, version);
writableDatabase.replace(TABLE_NAME, /* nullColumnHack= */ null, values);
}
/**
* Returns the version of tables belonging to the specified feature, or {@link #VERSION_UNSET} if
* no version information is available.
*
* @param database The database to query.
* @param feature The feature.
*/
public static int getVersion(SQLiteDatabase database, @Feature int feature) {
if (!tableExists(database, TABLE_NAME)) {
return VERSION_UNSET;
}
String selection = COLUMN_FEATURE + " = ?";
String[] selectionArgs = {Integer.toString(feature)};
try (Cursor cursor =
database.query(
TABLE_NAME,
new String[] {COLUMN_VERSION},
selection,
selectionArgs,
/* groupBy= */ null,
/* having= */ null,
/* orderBy= */ null)) {
if (cursor.getCount() == 0) {
return VERSION_UNSET;
}
cursor.moveToNext();
return cursor.getInt(/* COLUMN_VERSION index */ 0);
}
}
@VisibleForTesting
/* package */ static boolean tableExists(SQLiteDatabase readableDatabase, String tableName) {
long count =
DatabaseUtils.queryNumEntries(
readableDatabase, "sqlite_master", "tbl_name = ?", new String[] {tableName});
return count > 0;
}
}
...@@ -62,7 +62,7 @@ public final class CryptoInfo { ...@@ -62,7 +62,7 @@ public final class CryptoInfo {
private final PatternHolderV24 patternHolder; private final PatternHolderV24 patternHolder;
public CryptoInfo() { public CryptoInfo() {
frameworkCryptoInfo = Util.SDK_INT >= 16 ? newFrameworkCryptoInfoV16() : null; frameworkCryptoInfo = new android.media.MediaCodec.CryptoInfo();
patternHolder = Util.SDK_INT >= 24 ? new PatternHolderV24(frameworkCryptoInfo) : null; patternHolder = Util.SDK_INT >= 24 ? new PatternHolderV24(frameworkCryptoInfo) : null;
} }
...@@ -79,43 +79,36 @@ public final class CryptoInfo { ...@@ -79,43 +79,36 @@ public final class CryptoInfo {
this.mode = mode; this.mode = mode;
this.encryptedBlocks = encryptedBlocks; this.encryptedBlocks = encryptedBlocks;
this.clearBlocks = clearBlocks; this.clearBlocks = clearBlocks;
if (Util.SDK_INT >= 16) { // Update frameworkCryptoInfo fields directly because CryptoInfo.set performs an unnecessary
updateFrameworkCryptoInfoV16(); // object allocation on Android N.
frameworkCryptoInfo.numSubSamples = numSubSamples;
frameworkCryptoInfo.numBytesOfClearData = numBytesOfClearData;
frameworkCryptoInfo.numBytesOfEncryptedData = numBytesOfEncryptedData;
frameworkCryptoInfo.key = key;
frameworkCryptoInfo.iv = iv;
frameworkCryptoInfo.mode = mode;
if (Util.SDK_INT >= 24) {
patternHolder.set(encryptedBlocks, clearBlocks);
} }
} }
/** /**
* Returns an equivalent {@link android.media.MediaCodec.CryptoInfo} instance. * Returns an equivalent {@link android.media.MediaCodec.CryptoInfo} instance.
* <p> *
* Successive calls to this method on a single {@link CryptoInfo} will return the same instance. * <p>Successive calls to this method on a single {@link CryptoInfo} will return the same
* Changes to the {@link CryptoInfo} will be reflected in the returned object. The return object * instance. Changes to the {@link CryptoInfo} will be reflected in the returned object. The
* should not be modified directly. * return object should not be modified directly.
* *
* @return The equivalent {@link android.media.MediaCodec.CryptoInfo} instance. * @return The equivalent {@link android.media.MediaCodec.CryptoInfo} instance.
*/ */
@TargetApi(16) public android.media.MediaCodec.CryptoInfo getFrameworkCryptoInfo() {
public android.media.MediaCodec.CryptoInfo getFrameworkCryptoInfoV16() {
return frameworkCryptoInfo; return frameworkCryptoInfo;
} }
@TargetApi(16) /** @deprecated Use {@link #getFrameworkCryptoInfo()}. */
private android.media.MediaCodec.CryptoInfo newFrameworkCryptoInfoV16() { @Deprecated
return new android.media.MediaCodec.CryptoInfo(); public android.media.MediaCodec.CryptoInfo getFrameworkCryptoInfoV16() {
} return getFrameworkCryptoInfo();
@TargetApi(16)
private void updateFrameworkCryptoInfoV16() {
// Update fields directly because the framework's CryptoInfo.set performs an unnecessary object
// allocation on Android N.
frameworkCryptoInfo.numSubSamples = numSubSamples;
frameworkCryptoInfo.numBytesOfClearData = numBytesOfClearData;
frameworkCryptoInfo.numBytesOfEncryptedData = numBytesOfEncryptedData;
frameworkCryptoInfo.key = key;
frameworkCryptoInfo.iv = iv;
frameworkCryptoInfo.mode = mode;
if (Util.SDK_INT >= 24) {
patternHolder.set(encryptedBlocks, clearBlocks);
}
} }
@TargetApi(24) @TargetApi(24)
......
...@@ -15,7 +15,6 @@ ...@@ -15,7 +15,6 @@
*/ */
package com.google.android.exoplayer2.drm; package com.google.android.exoplayer2.drm;
import android.annotation.TargetApi;
import android.media.MediaDrm; import android.media.MediaDrm;
import android.support.annotation.IntDef; import android.support.annotation.IntDef;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
...@@ -27,7 +26,6 @@ import java.util.Map; ...@@ -27,7 +26,6 @@ import java.util.Map;
/** /**
* A DRM session. * A DRM session.
*/ */
@TargetApi(16)
public interface DrmSession<T extends ExoMediaCrypto> { public interface DrmSession<T extends ExoMediaCrypto> {
/** /**
......
...@@ -15,14 +15,12 @@ ...@@ -15,14 +15,12 @@
*/ */
package com.google.android.exoplayer2.drm; package com.google.android.exoplayer2.drm;
import android.annotation.TargetApi;
import android.os.Looper; import android.os.Looper;
import com.google.android.exoplayer2.drm.DrmInitData.SchemeData; import com.google.android.exoplayer2.drm.DrmInitData.SchemeData;
/** /**
* Manages a DRM session. * Manages a DRM session.
*/ */
@TargetApi(16)
public interface DrmSessionManager<T extends ExoMediaCrypto> { public interface DrmSessionManager<T extends ExoMediaCrypto> {
/** /**
......
...@@ -15,14 +15,5 @@ ...@@ -15,14 +15,5 @@
*/ */
package com.google.android.exoplayer2.drm; package com.google.android.exoplayer2.drm;
/** /** An opaque {@link android.media.MediaCrypto} equivalent. */
* An opaque {@link android.media.MediaCrypto} equivalent. public interface ExoMediaCrypto {}
*/
public interface ExoMediaCrypto {
/**
* @see android.media.MediaCrypto#requiresSecureDecoderComponent(String)
*/
boolean requiresSecureDecoderComponent(String mimeType);
}
...@@ -265,11 +265,9 @@ public interface ExoMediaDrm<T extends ExoMediaCrypto> { ...@@ -265,11 +265,9 @@ public interface ExoMediaDrm<T extends ExoMediaCrypto> {
/** /**
* @see android.media.MediaCrypto#MediaCrypto(UUID, byte[]) * @see android.media.MediaCrypto#MediaCrypto(UUID, byte[])
* * @param sessionId The DRM session ID.
* @param initData Opaque initialization data specific to the crypto scheme.
* @return An object extends {@link ExoMediaCrypto}, using opaque crypto scheme specific data. * @return An object extends {@link ExoMediaCrypto}, using opaque crypto scheme specific data.
* @throws MediaCryptoException If the instance can't be created. * @throws MediaCryptoException If the instance can't be created.
*/ */
T createMediaCrypto(byte[] initData) throws MediaCryptoException; T createMediaCrypto(byte[] sessionId) throws MediaCryptoException;
} }
...@@ -15,50 +15,35 @@ ...@@ -15,50 +15,35 @@
*/ */
package com.google.android.exoplayer2.drm; package com.google.android.exoplayer2.drm;
import android.annotation.TargetApi;
import android.media.MediaCrypto; import android.media.MediaCrypto;
import com.google.android.exoplayer2.util.Assertions; import java.util.UUID;
/** /**
* An {@link ExoMediaCrypto} implementation that wraps the framework {@link MediaCrypto}. * An {@link ExoMediaCrypto} implementation that contains the necessary information to build or
* update a framework {@link MediaCrypto}.
*/ */
@TargetApi(16)
public final class FrameworkMediaCrypto implements ExoMediaCrypto { public final class FrameworkMediaCrypto implements ExoMediaCrypto {
private final MediaCrypto mediaCrypto; /** The DRM scheme UUID. */
private final boolean forceAllowInsecureDecoderComponents; public final UUID uuid;
/** The DRM session id. */
public final byte[] sessionId;
/** /**
* @param mediaCrypto The {@link MediaCrypto} to wrap. * Whether to allow use of insecure decoder components even if the underlying platform says
* otherwise.
*/ */
public FrameworkMediaCrypto(MediaCrypto mediaCrypto) { public final boolean forceAllowInsecureDecoderComponents;
this(mediaCrypto, false);
}
/** /**
* @param mediaCrypto The {@link MediaCrypto} to wrap. * @param uuid The DRM scheme UUID.
* @param forceAllowInsecureDecoderComponents Whether to force * @param sessionId The DRM session id.
* {@link #requiresSecureDecoderComponent(String)} to return {@code false}, rather than * @param forceAllowInsecureDecoderComponents Whether to allow use of insecure decoder components
* {@link MediaCrypto#requiresSecureDecoderComponent(String)} of the wrapped * even if the underlying platform says otherwise.
* {@link MediaCrypto}.
*/ */
public FrameworkMediaCrypto(MediaCrypto mediaCrypto, public FrameworkMediaCrypto(
boolean forceAllowInsecureDecoderComponents) { UUID uuid, byte[] sessionId, boolean forceAllowInsecureDecoderComponents) {
this.mediaCrypto = Assertions.checkNotNull(mediaCrypto); this.uuid = uuid;
this.sessionId = sessionId;
this.forceAllowInsecureDecoderComponents = forceAllowInsecureDecoderComponents; this.forceAllowInsecureDecoderComponents = forceAllowInsecureDecoderComponents;
} }
/**
* Returns the wrapped {@link MediaCrypto}.
*/
public MediaCrypto getWrappedMediaCrypto() {
return mediaCrypto;
}
@Override
public boolean requiresSecureDecoderComponent(String mimeType) {
return !forceAllowInsecureDecoderComponents
&& mediaCrypto.requiresSecureDecoderComponent(mimeType);
}
} }
...@@ -18,7 +18,6 @@ package com.google.android.exoplayer2.drm; ...@@ -18,7 +18,6 @@ package com.google.android.exoplayer2.drm;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.annotation.TargetApi; import android.annotation.TargetApi;
import android.media.DeniedByServerException; import android.media.DeniedByServerException;
import android.media.MediaCrypto;
import android.media.MediaCryptoException; import android.media.MediaCryptoException;
import android.media.MediaDrm; import android.media.MediaDrm;
import android.media.MediaDrmException; import android.media.MediaDrmException;
...@@ -210,7 +209,7 @@ public final class FrameworkMediaDrm implements ExoMediaDrm<FrameworkMediaCrypto ...@@ -210,7 +209,7 @@ public final class FrameworkMediaDrm implements ExoMediaDrm<FrameworkMediaCrypto
boolean forceAllowInsecureDecoderComponents = Util.SDK_INT < 21 boolean forceAllowInsecureDecoderComponents = Util.SDK_INT < 21
&& C.WIDEVINE_UUID.equals(uuid) && "L3".equals(getPropertyString("securityLevel")); && C.WIDEVINE_UUID.equals(uuid) && "L3".equals(getPropertyString("securityLevel"));
return new FrameworkMediaCrypto( return new FrameworkMediaCrypto(
new MediaCrypto(adjustUuid(uuid), initData), forceAllowInsecureDecoderComponents); adjustUuid(uuid), initData, forceAllowInsecureDecoderComponents);
} }
private static SchemeData getSchemeData(UUID uuid, List<SchemeData> schemeDatas) { private static SchemeData getSchemeData(UUID uuid, List<SchemeData> schemeDatas) {
......
...@@ -64,6 +64,9 @@ import com.google.android.exoplayer2.util.Util; ...@@ -64,6 +64,9 @@ import com.google.android.exoplayer2.util.Util;
this.flags = flags; this.flags = flags;
this.durationUs = durationUs; this.durationUs = durationUs;
sampleCount = offsets.length; sampleCount = offsets.length;
if (flags.length > 0) {
flags[flags.length - 1] |= C.BUFFER_FLAG_LAST_SAMPLE;
}
} }
/** /**
......
...@@ -50,7 +50,8 @@ public final class DefaultTsPayloadReaderFactory implements TsPayloadReader.Fact ...@@ -50,7 +50,8 @@ public final class DefaultTsPayloadReaderFactory implements TsPayloadReader.Fact
FLAG_IGNORE_H264_STREAM, FLAG_IGNORE_H264_STREAM,
FLAG_DETECT_ACCESS_UNITS, FLAG_DETECT_ACCESS_UNITS,
FLAG_IGNORE_SPLICE_INFO_STREAM, FLAG_IGNORE_SPLICE_INFO_STREAM,
FLAG_OVERRIDE_CAPTION_DESCRIPTORS FLAG_OVERRIDE_CAPTION_DESCRIPTORS,
FLAG_IGNORE_HDMV_DTS_STREAM
}) })
public @interface Flags {} public @interface Flags {}
...@@ -86,6 +87,12 @@ public final class DefaultTsPayloadReaderFactory implements TsPayloadReader.Fact ...@@ -86,6 +87,12 @@ public final class DefaultTsPayloadReaderFactory implements TsPayloadReader.Fact
* closedCaptionFormats} will be ignored if the PMT contains closed captions service descriptors. * closedCaptionFormats} will be ignored if the PMT contains closed captions service descriptors.
*/ */
public static final int FLAG_OVERRIDE_CAPTION_DESCRIPTORS = 1 << 5; public static final int FLAG_OVERRIDE_CAPTION_DESCRIPTORS = 1 << 5;
/**
* Prevents the creation of {@link DtsReader} instances when receiving {@link
* TsExtractor#TS_STREAM_TYPE_HDMV_DTS} as stream type. Enabling this flag prevents a stream type
* collision between HDMV DTS audio and SCTE-35 subtitles.
*/
public static final int FLAG_IGNORE_HDMV_DTS_STREAM = 1 << 6;
private static final int DESCRIPTOR_TAG_CAPTION_SERVICE = 0x86; private static final int DESCRIPTOR_TAG_CAPTION_SERVICE = 0x86;
...@@ -142,8 +149,12 @@ public final class DefaultTsPayloadReaderFactory implements TsPayloadReader.Fact ...@@ -142,8 +149,12 @@ public final class DefaultTsPayloadReaderFactory implements TsPayloadReader.Fact
case TsExtractor.TS_STREAM_TYPE_AC3: case TsExtractor.TS_STREAM_TYPE_AC3:
case TsExtractor.TS_STREAM_TYPE_E_AC3: case TsExtractor.TS_STREAM_TYPE_E_AC3:
return new PesReader(new Ac3Reader(esInfo.language)); return new PesReader(new Ac3Reader(esInfo.language));
case TsExtractor.TS_STREAM_TYPE_DTS:
case TsExtractor.TS_STREAM_TYPE_HDMV_DTS: case TsExtractor.TS_STREAM_TYPE_HDMV_DTS:
if (isSet(FLAG_IGNORE_HDMV_DTS_STREAM)) {
return null;
}
// Fall through.
case TsExtractor.TS_STREAM_TYPE_DTS:
return new PesReader(new DtsReader(esInfo.language)); return new PesReader(new DtsReader(esInfo.language));
case TsExtractor.TS_STREAM_TYPE_H262: case TsExtractor.TS_STREAM_TYPE_H262:
return new PesReader(new H262Reader(buildUserDataReader(esInfo))); return new PesReader(new H262Reader(buildUserDataReader(esInfo)));
......
...@@ -100,7 +100,7 @@ public interface TsPayloadReader { ...@@ -100,7 +100,7 @@ public interface TsPayloadReader {
public final byte[] initializationData; public final byte[] initializationData;
/** /**
* @param language The ISO 639-2 three character language. * @param language The ISO 639-2 three-letter language code.
* @param type The subtitling type. * @param type The subtitling type.
* @param initializationData The composition and ancillary page ids. * @param initializationData The composition and ancillary page ids.
*/ */
......
...@@ -31,7 +31,6 @@ import com.google.android.exoplayer2.util.MimeTypes; ...@@ -31,7 +31,6 @@ import com.google.android.exoplayer2.util.MimeTypes;
import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.util.Util;
/** Information about a {@link MediaCodec} for a given mime type. */ /** Information about a {@link MediaCodec} for a given mime type. */
@TargetApi(16)
@SuppressWarnings("InlinedApi") @SuppressWarnings("InlinedApi")
public final class MediaCodecInfo { public final class MediaCodecInfo {
......
...@@ -39,7 +39,6 @@ import java.util.regex.Pattern; ...@@ -39,7 +39,6 @@ import java.util.regex.Pattern;
/** /**
* A utility class for querying the available codecs. * A utility class for querying the available codecs.
*/ */
@TargetApi(16)
@SuppressLint("InlinedApi") @SuppressLint("InlinedApi")
public final class MediaCodecUtil { public final class MediaCodecUtil {
...@@ -59,8 +58,6 @@ public final class MediaCodecUtil { ...@@ -59,8 +58,6 @@ public final class MediaCodecUtil {
private static final String TAG = "MediaCodecUtil"; private static final String TAG = "MediaCodecUtil";
private static final Pattern PROFILE_PATTERN = Pattern.compile("^\\D?(\\d+)$"); private static final Pattern PROFILE_PATTERN = Pattern.compile("^\\D?(\\d+)$");
private static final RawAudioCodecComparator RAW_AUDIO_CODEC_COMPARATOR =
new RawAudioCodecComparator();
private static final HashMap<CodecKey, List<MediaCodecInfo>> decoderInfosCache = new HashMap<>(); private static final HashMap<CodecKey, List<MediaCodecInfo>> decoderInfosCache = new HashMap<>();
...@@ -312,30 +309,6 @@ public final class MediaCodecUtil { ...@@ -312,30 +309,6 @@ public final class MediaCodecUtil {
return false; return false;
} }
// Work around https://github.com/google/ExoPlayer/issues/398.
if (Util.SDK_INT < 18 && "OMX.SEC.MP3.Decoder".equals(name)) {
return false;
}
// Work around https://github.com/google/ExoPlayer/issues/4519.
if ("OMX.SEC.mp3.dec".equals(name)
&& (Util.MODEL.startsWith("GT-I9152")
|| Util.MODEL.startsWith("GT-I9515")
|| Util.MODEL.startsWith("GT-P5220")
|| Util.MODEL.startsWith("GT-S7580")
|| Util.MODEL.startsWith("SM-G350")
|| Util.MODEL.startsWith("SM-G386")
|| Util.MODEL.startsWith("SM-T231")
|| Util.MODEL.startsWith("SM-T530"))) {
return false;
}
if ("OMX.brcm.audio.mp3.decoder".equals(name)
&& (Util.MODEL.startsWith("GT-I9152")
|| Util.MODEL.startsWith("GT-S7580")
|| Util.MODEL.startsWith("SM-G350"))) {
return false;
}
// Work around https://github.com/google/ExoPlayer/issues/1528 and // Work around https://github.com/google/ExoPlayer/issues/1528 and
// https://github.com/google/ExoPlayer/issues/3171. // https://github.com/google/ExoPlayer/issues/3171.
if (Util.SDK_INT < 18 && "OMX.MTK.AUDIO.DECODER.AAC".equals(name) if (Util.SDK_INT < 18 && "OMX.MTK.AUDIO.DECODER.AAC".equals(name)
...@@ -422,7 +395,18 @@ public final class MediaCodecUtil { ...@@ -422,7 +395,18 @@ public final class MediaCodecUtil {
*/ */
private static void applyWorkarounds(String mimeType, List<MediaCodecInfo> decoderInfos) { private static void applyWorkarounds(String mimeType, List<MediaCodecInfo> decoderInfos) {
if (MimeTypes.AUDIO_RAW.equals(mimeType)) { if (MimeTypes.AUDIO_RAW.equals(mimeType)) {
Collections.sort(decoderInfos, RAW_AUDIO_CODEC_COMPARATOR); Collections.sort(decoderInfos, new RawAudioCodecComparator());
} else if (Util.SDK_INT < 21 && decoderInfos.size() > 1) {
String firstCodecName = decoderInfos.get(0).name;
if ("OMX.SEC.mp3.dec".equals(firstCodecName)
|| "OMX.SEC.MP3.Decoder".equals(firstCodecName)
|| "OMX.brcm.audio.mp3.decoder".equals(firstCodecName)) {
// Prefer OMX.google codecs over OMX.SEC.mp3.dec, OMX.SEC.MP3.Decoder and
// OMX.brcm.audio.mp3.decoder on older devices. See:
// https://github.com/google/ExoPlayer/issues/398 and
// https://github.com/google/ExoPlayer/issues/4519.
Collections.sort(decoderInfos, new PreferOmxGoogleCodecComparator());
}
} }
} }
...@@ -461,9 +445,10 @@ public final class MediaCodecUtil { ...@@ -461,9 +445,10 @@ public final class MediaCodecUtil {
Log.w(TAG, "Unknown HEVC profile string: " + profileString); Log.w(TAG, "Unknown HEVC profile string: " + profileString);
return null; return null;
} }
Integer level = HEVC_CODEC_STRING_TO_PROFILE_LEVEL.get(parts[3]); String levelString = parts[3];
Integer level = HEVC_CODEC_STRING_TO_PROFILE_LEVEL.get(levelString);
if (level == null) { if (level == null) {
Log.w(TAG, "Unknown HEVC level string: " + matcher.group(1)); Log.w(TAG, "Unknown HEVC level string: " + levelString);
return null; return null;
} }
return new Pair<>(profile, level); return new Pair<>(profile, level);
...@@ -728,6 +713,18 @@ public final class MediaCodecUtil { ...@@ -728,6 +713,18 @@ public final class MediaCodecUtil {
} }
} }
/** Comparator for preferring OMX.google media codecs. */
private static final class PreferOmxGoogleCodecComparator implements Comparator<MediaCodecInfo> {
@Override
public int compare(MediaCodecInfo a, MediaCodecInfo b) {
return scoreMediaCodecInfo(a) - scoreMediaCodecInfo(b);
}
private static int scoreMediaCodecInfo(MediaCodecInfo mediaCodecInfo) {
return mediaCodecInfo.name.startsWith("OMX.google") ? -1 : 0;
}
}
static { static {
AVC_PROFILE_NUMBER_TO_CONST = new SparseIntArray(); AVC_PROFILE_NUMBER_TO_CONST = new SparseIntArray();
AVC_PROFILE_NUMBER_TO_CONST.put(66, CodecProfileLevel.AVCProfileBaseline); AVC_PROFILE_NUMBER_TO_CONST.put(66, CodecProfileLevel.AVCProfileBaseline);
......
...@@ -15,7 +15,6 @@ ...@@ -15,7 +15,6 @@
*/ */
package com.google.android.exoplayer2.mediacodec; package com.google.android.exoplayer2.mediacodec;
import android.annotation.TargetApi;
import android.media.MediaFormat; import android.media.MediaFormat;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.Format;
...@@ -24,7 +23,6 @@ import java.nio.ByteBuffer; ...@@ -24,7 +23,6 @@ import java.nio.ByteBuffer;
import java.util.List; import java.util.List;
/** Helper class for configuring {@link MediaFormat} instances. */ /** Helper class for configuring {@link MediaFormat} instances. */
@TargetApi(16)
public final class MediaFormatUtil { public final class MediaFormatUtil {
private MediaFormatUtil() {} private MediaFormatUtil() {}
......
...@@ -156,7 +156,7 @@ public final class DownloadAction { ...@@ -156,7 +156,7 @@ public final class DownloadAction {
ArrayList<StreamKey> mutableKeys = new ArrayList<>(keys); ArrayList<StreamKey> mutableKeys = new ArrayList<>(keys);
Collections.sort(mutableKeys); Collections.sort(mutableKeys);
this.keys = Collections.unmodifiableList(mutableKeys); this.keys = Collections.unmodifiableList(mutableKeys);
this.data = data != null ? data : Util.EMPTY_BYTE_ARRAY; this.data = data != null ? Arrays.copyOf(data, data.length) : Util.EMPTY_BYTE_ARRAY;
} }
} }
......
/*
* Copyright (C) 2019 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.offline;
import android.support.annotation.Nullable;
/** Persists {@link DownloadState}s. */
interface DownloadIndex {
/**
* Returns the {@link DownloadState} with the given {@code id}, or null.
*
* @param id ID of a {@link DownloadState}.
* @return The {@link DownloadState} with the given {@code id}, or null if a download state with
* this id doesn't exist.
*/
@Nullable
DownloadState getDownloadState(String id);
/**
* Returns a {@link DownloadStateCursor} to {@link DownloadState}s with the given {@code states}.
*
* @param states Returns only the {@link DownloadState}s with this states. If empty, returns all.
* @return A cursor to {@link DownloadState}s with the given {@code states}.
*/
DownloadStateCursor getDownloadStates(@DownloadState.State int... states);
/**
* Adds or replaces a {@link DownloadState}.
*
* @param downloadState The {@link DownloadState} to be added.
*/
void putDownloadState(DownloadState downloadState);
/** Removes the {@link DownloadState} with the given {@code id}. */
void removeDownloadState(String id);
}
/*
* Copyright (C) 2019 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.offline;
import android.support.annotation.Nullable;
import java.io.IOException;
/** {@link DownloadIndex} related utility methods. */
public final class DownloadIndexUtil {
/** An interface to provide custom download ids during ActionFile upgrade. */
public interface DownloadIdProvider {
/**
* Returns a custom download id for given action.
*
* @param downloadAction The action which is an id requested for.
* @return A custom download id for given action.
*/
String getId(DownloadAction downloadAction);
}
private DownloadIndexUtil() {}
/**
* Upgrades an {@link ActionFile} to {@link DownloadIndex}.
*
* <p>This method shouldn't be called while {@link DownloadIndex} is used by {@link
* DownloadManager}.
*
* @param actionFile The action file to upgrade.
* @param downloadIndex Actions are converted to {@link DownloadState}s and stored in this index.
* @param downloadIdProvider A nullable custom download id provider.
* @throws IOException If there is an error during loading actions.
*/
public static void upgradeActionFile(
ActionFile actionFile,
DownloadIndex downloadIndex,
@Nullable DownloadIdProvider downloadIdProvider)
throws IOException {
if (downloadIdProvider == null) {
downloadIdProvider = downloadAction -> downloadAction.id;
}
for (DownloadAction action : actionFile.load()) {
addAction(downloadIndex, downloadIdProvider.getId(action), action);
}
}
/**
* Converts a {@link DownloadAction} to {@link DownloadState} and stored in the given {@link
* DownloadIndex}.
*
* <p>This method shouldn't be called while {@link DownloadIndex} is used by {@link
* DownloadManager}.
*
* @param downloadIndex The action is converted to {@link DownloadState} and stored in this index.
* @param id A nullable custom download id which overwrites {@link DownloadAction#id}.
* @param action The action to be stored in {@link DownloadIndex}.
*/
public static void addAction(
DownloadIndex downloadIndex, @Nullable String id, DownloadAction action) {
DownloadState downloadState = downloadIndex.getDownloadState(id != null ? id : action.id);
if (downloadState != null) {
downloadState = downloadState.mergeAction(action);
} else {
downloadState = new DownloadState(action);
}
downloadIndex.putDownloadState(downloadState);
}
}
...@@ -13,17 +13,20 @@ ...@@ -13,17 +13,20 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package com.google.android.exoplayer2.offline; package com.google.android.exoplayer2.offline;
import android.net.Uri; import android.net.Uri;
import android.support.annotation.IntDef; import android.support.annotation.IntDef;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.scheduler.Requirements;
import com.google.android.exoplayer2.scheduler.Requirements.RequirementFlags;
import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Assertions;
import java.lang.annotation.Documented; import java.lang.annotation.Documented;
import java.lang.annotation.Retention; import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy;
import java.util.Collections;
import java.util.HashSet;
/** Represents state of a download. */ /** Represents state of a download. */
public final class DownloadState { public final class DownloadState {
...@@ -74,19 +77,19 @@ public final class DownloadState { ...@@ -74,19 +77,19 @@ public final class DownloadState {
public static final int FAILURE_REASON_UNKNOWN = 1; public static final int FAILURE_REASON_UNKNOWN = 1;
/** /**
* Download stop flags. Possible flag values are {@link #STOP_FLAG_DOWNLOAD_MANAGER_NOT_READY} and * Download stop flags. Possible flag values are {@link #STOP_FLAG_MANUAL} and {@link
* {@link #STOP_FLAG_STOPPED}. * #STOP_FLAG_REQUIREMENTS_NOT_MET}.
*/ */
@Documented @Documented
@Retention(RetentionPolicy.SOURCE) @Retention(RetentionPolicy.SOURCE)
@IntDef( @IntDef(
flag = true, flag = true,
value = {STOP_FLAG_DOWNLOAD_MANAGER_NOT_READY, STOP_FLAG_STOPPED}) value = {STOP_FLAG_MANUAL, STOP_FLAG_REQUIREMENTS_NOT_MET})
public @interface StopFlags {} public @interface StopFlags {}
/** Download can't be started as the manager isn't ready. */ /** Download is stopped by the application. */
public static final int STOP_FLAG_DOWNLOAD_MANAGER_NOT_READY = 1; public static final int STOP_FLAG_MANUAL = 1;
/** All downloads are stopped by the application. */ /** Download is stopped as the requirements are not met. */
public static final int STOP_FLAG_STOPPED = 1 << 1; public static final int STOP_FLAG_REQUIREMENTS_NOT_MET = 1 << 1;
/** Returns the state string for the given state value. */ /** Returns the state string for the given state value. */
public static String getStateString(@State int state) { public static String getStateString(@State int state) {
...@@ -154,7 +157,40 @@ public final class DownloadState { ...@@ -154,7 +157,40 @@ public final class DownloadState {
*/ */
@FailureReason public final int failureReason; @FailureReason public final int failureReason;
/** Download stop flags. These flags stop downloading any content. */ /** Download stop flags. These flags stop downloading any content. */
public final int stopFlags; @StopFlags public final int stopFlags;
/** Not met requirements to download. */
@Requirements.RequirementFlags public final int notMetRequirements;
/** If {@link #STOP_FLAG_MANUAL} is set then this field holds the manual stop reason. */
public final int manualStopReason;
/**
* Creates a {@link DownloadState} using a {@link DownloadAction}.
*
* @param action The {@link DownloadAction}.
*/
public DownloadState(DownloadAction action) {
this(action, System.currentTimeMillis());
}
private DownloadState(DownloadAction action, long currentTimeMs) {
this(
action.id,
action.type,
action.uri,
action.customCacheKey,
/* state= */ action.isRemoveAction ? STATE_REMOVING : STATE_QUEUED,
/* downloadPercentage= */ C.PERCENTAGE_UNSET,
/* downloadedBytes= */ 0,
/* totalBytes= */ C.LENGTH_UNSET,
FAILURE_REASON_NONE,
/* stopFlags= */ 0,
/* notMetRequirements= */ 0,
/* manualStopReason= */ 0,
/* startTimeMs= */ currentTimeMs,
/* updateTimeMs= */ currentTimeMs,
action.keys.toArray(new StreamKey[0]),
action.data);
}
/* package */ DownloadState( /* package */ DownloadState(
String id, String id,
...@@ -167,28 +203,91 @@ public final class DownloadState { ...@@ -167,28 +203,91 @@ public final class DownloadState {
long totalBytes, long totalBytes,
@FailureReason int failureReason, @FailureReason int failureReason,
@StopFlags int stopFlags, @StopFlags int stopFlags,
@RequirementFlags int notMetRequirements,
int manualStopReason,
long startTimeMs, long startTimeMs,
long updateTimeMs, long updateTimeMs,
StreamKey[] streamKeys, StreamKey[] streamKeys,
byte[] customMetadata) { byte[] customMetadata) {
this.stopFlags = stopFlags; Assertions.checkState((failureReason == FAILURE_REASON_NONE) == (state != STATE_FAILED));
Assertions.checkState(stopFlags == 0 || (state != STATE_DOWNLOADING && state != STATE_QUEUED));
Assertions.checkState( Assertions.checkState(
failureReason == FAILURE_REASON_NONE ? state != STATE_FAILED : state == STATE_FAILED); ((stopFlags & STOP_FLAG_REQUIREMENTS_NOT_MET) == 0) == (notMetRequirements == 0));
// TODO enable this when we start changing state immediately Assertions.checkState(((stopFlags & STOP_FLAG_MANUAL) != 0) || (manualStopReason == 0));
// Assertions.checkState(stopFlags == 0 || (state != STATE_DOWNLOADING && state !=
// STATE_QUEUED));
this.id = id; this.id = id;
this.type = type; this.type = type;
this.uri = uri; this.uri = uri;
this.cacheKey = cacheKey; this.cacheKey = cacheKey;
this.streamKeys = streamKeys;
this.customMetadata = customMetadata;
this.state = state; this.state = state;
this.downloadPercentage = downloadPercentage; this.downloadPercentage = downloadPercentage;
this.downloadedBytes = downloadedBytes; this.downloadedBytes = downloadedBytes;
this.totalBytes = totalBytes; this.totalBytes = totalBytes;
this.failureReason = failureReason; this.failureReason = failureReason;
this.stopFlags = stopFlags;
this.notMetRequirements = notMetRequirements;
this.manualStopReason = manualStopReason;
this.startTimeMs = startTimeMs; this.startTimeMs = startTimeMs;
this.updateTimeMs = updateTimeMs; this.updateTimeMs = updateTimeMs;
this.streamKeys = streamKeys;
this.customMetadata = customMetadata;
}
/**
* Merges the given {@link DownloadAction} and creates a new {@link DownloadState}. The action
* must have the same id and type.
*
* @param action The {@link DownloadAction} to be merged.
* @return A new {@link DownloadState}.
*/
public DownloadState mergeAction(DownloadAction action) {
Assertions.checkArgument(action.id.equals(id));
Assertions.checkArgument(action.type.equals(type));
return new DownloadState(
id,
type,
action.uri,
action.customCacheKey,
getNextState(action, state),
/* downloadPercentage= */ C.PERCENTAGE_UNSET,
downloadedBytes,
/* totalBytes= */ C.LENGTH_UNSET,
FAILURE_REASON_NONE,
stopFlags,
notMetRequirements,
manualStopReason,
startTimeMs,
updateTimeMs,
mergeStreamKeys(this, action),
action.data);
}
private static int getNextState(DownloadAction action, int currentState) {
int newState;
if (action.isRemoveAction) {
newState = STATE_REMOVING;
} else {
if (currentState == STATE_REMOVING || currentState == STATE_RESTARTING) {
newState = STATE_RESTARTING;
} else if (currentState == STATE_STOPPED) {
newState = STATE_STOPPED;
} else {
newState = STATE_QUEUED;
}
}
return newState;
}
private static StreamKey[] mergeStreamKeys(DownloadState downloadState, DownloadAction action) {
StreamKey[] streamKeys = downloadState.streamKeys;
if (!action.isRemoveAction && streamKeys.length > 0) {
if (action.keys.isEmpty()) {
streamKeys = new StreamKey[0];
} else {
HashSet<StreamKey> keys = new HashSet<>(action.keys);
Collections.addAll(keys, downloadState.streamKeys);
streamKeys = keys.toArray(new StreamKey[0]);
}
}
return streamKeys;
} }
} }
/*
* Copyright (C) 2019 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.offline;
/** Provides random read-write access to the result set returned by a database query. */
public interface DownloadStateCursor {
/** Returns the DownloadState at the current position. */
DownloadState getDownloadState();
/** Returns the numbers of DownloadStates in the cursor. */
int getCount();
/**
* Returns the current position of the cursor in the DownloadState set. The value is zero-based.
* When the DownloadState set is first returned the cursor will be at positon -1, which is before
* the first DownloadState. After the last DownloadState is returned another call to next() will
* leave the cursor past the last entry, at a position of count().
*
* @return the current cursor position.
*/
int getPosition();
/**
* Move the cursor to an absolute position. The valid range of values is -1 &lt;= position &lt;=
* count.
*
* <p>This method will return true if the request destination was reachable, otherwise, it returns
* false.
*
* @param position the zero-based position to move to.
* @return whether the requested move fully succeeded.
*/
boolean moveToPosition(int position);
/**
* Move the cursor to the first DownloadState.
*
* <p>This method will return false if the cursor is empty.
*
* @return whether the move succeeded.
*/
default boolean moveToFirst() {
return moveToPosition(0);
}
/**
* Move the cursor to the last DownloadState.
*
* <p>This method will return false if the cursor is empty.
*
* @return whether the move succeeded.
*/
default boolean moveToLast() {
return moveToPosition(getCount() - 1);
}
/**
* Move the cursor to the next DownloadState.
*
* <p>This method will return false if the cursor is already past the last entry in the result
* set.
*
* @return whether the move succeeded.
*/
default boolean moveToNext() {
return moveToPosition(getPosition() + 1);
}
/**
* Move the cursor to the previous DownloadState.
*
* <p>This method will return false if the cursor is already before the first entry in the result
* set.
*
* @return whether the move succeeded.
*/
default boolean moveToPrevious() {
return moveToPosition(getPosition() - 1);
}
/** Returns whether the cursor is pointing to the first DownloadState. */
default boolean isFirst() {
return getPosition() == 0 && getCount() != 0;
}
/** Returns whether the cursor is pointing to the last DownloadState. */
default boolean isLast() {
int count = getCount();
return getPosition() == (count - 1) && count != 0;
}
/** Returns whether the cursor is pointing to the position before the first DownloadState. */
default boolean isBeforeFirst() {
if (getCount() == 0) {
return true;
}
return getPosition() == -1;
}
/** Returns whether the cursor is pointing to the position after the last DownloadState. */
default boolean isAfterLast() {
if (getCount() == 0) {
return true;
}
return getPosition() == getCount();
}
/** Closes the Cursor, releasing all of its resources and making it completely invalid. */
void close();
/** Returns whether the cursor is closed */
boolean isClosed();
}
...@@ -109,16 +109,16 @@ public final class DownloaderConstructorHelper { ...@@ -109,16 +109,16 @@ public final class DownloaderConstructorHelper {
cacheReadDataSourceFactory != null cacheReadDataSourceFactory != null
? cacheReadDataSourceFactory ? cacheReadDataSourceFactory
: new FileDataSourceFactory(); : new FileDataSourceFactory();
DataSink.Factory writeDataSinkFactory = if (cacheWriteDataSinkFactory == null) {
cacheWriteDataSinkFactory != null cacheWriteDataSinkFactory =
? cacheWriteDataSinkFactory new CacheDataSinkFactory(cache, CacheDataSink.DEFAULT_FRAGMENT_SIZE);
: new CacheDataSinkFactory(cache, CacheDataSink.DEFAULT_MAX_CACHE_FILE_SIZE); }
onlineCacheDataSourceFactory = onlineCacheDataSourceFactory =
new CacheDataSourceFactory( new CacheDataSourceFactory(
cache, cache,
upstreamFactory, upstreamFactory,
readDataSourceFactory, readDataSourceFactory,
writeDataSinkFactory, cacheWriteDataSinkFactory,
CacheDataSource.FLAG_BLOCK_ON_CACHE, CacheDataSource.FLAG_BLOCK_ON_CACHE,
/* eventListener= */ null, /* eventListener= */ null,
cacheKeyFactory); cacheKeyFactory);
......
/*
* 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.offline;
import android.net.Uri;
import android.support.annotation.Nullable;
import com.google.android.exoplayer2.Renderer;
import com.google.android.exoplayer2.source.TrackGroupArray;
/** A {@link DownloadHelper} for progressive streams. */
public final class ProgressiveDownloadHelper extends DownloadHelper<Void> {
/**
* Creates download helper for progressive streams.
*
* @param uri The stream {@link Uri}.
*/
public ProgressiveDownloadHelper(Uri uri) {
this(uri, /* cacheKey= */ null);
}
/**
* Creates download helper for progressive streams.
*
* @param uri The stream {@link Uri}.
* @param cacheKey An optional cache key.
*/
public ProgressiveDownloadHelper(Uri uri, @Nullable String cacheKey) {
super(
DownloadAction.TYPE_PROGRESSIVE,
uri,
cacheKey,
DownloadHelper.DEFAULT_TRACK_SELECTOR_PARAMETERS,
(handler, videoListener, audioListener, metadata, text, drm) -> new Renderer[0],
/* drmSessionManager= */ null);
}
@Override
protected Void loadManifest(Uri uri) {
return null;
}
@Override
protected TrackGroupArray[] getTrackGroupArrays(Void manifest) {
return new TrackGroupArray[] {TrackGroupArray.EMPTY};
}
@Override
protected StreamKey toStreamKey(
int periodIndex, int trackGroupIndex, int trackIndexInTrackGroup) {
return new StreamKey(periodIndex, trackGroupIndex, trackIndexInTrackGroup);
}
}
...@@ -53,7 +53,11 @@ public final class ProgressiveDownloader implements Downloader { ...@@ -53,7 +53,11 @@ public final class ProgressiveDownloader implements Downloader {
Uri uri, @Nullable String customCacheKey, DownloaderConstructorHelper constructorHelper) { Uri uri, @Nullable String customCacheKey, DownloaderConstructorHelper constructorHelper) {
this.dataSpec = this.dataSpec =
new DataSpec( new DataSpec(
uri, /* absoluteStreamPosition= */ 0, C.LENGTH_UNSET, customCacheKey, /* flags= */ 0); uri,
/* absoluteStreamPosition= */ 0,
C.LENGTH_UNSET,
customCacheKey,
/* flags= */ DataSpec.FLAG_ALLOW_CACHE_FRAGMENTATION);
this.cache = constructorHelper.getCache(); this.cache = constructorHelper.getCache();
this.dataSource = constructorHelper.createCacheDataSource(); this.dataSource = constructorHelper.createCacheDataSource();
this.cacheKeyFactory = constructorHelper.getCacheKeyFactory(); this.cacheKeyFactory = constructorHelper.getCacheKeyFactory();
......
...@@ -155,16 +155,14 @@ public final class Requirements { ...@@ -155,16 +155,14 @@ public final class Requirements {
* @return Whether the requirements are met. * @return Whether the requirements are met.
*/ */
public boolean checkRequirements(Context context) { public boolean checkRequirements(Context context) {
return checkNetworkRequirements(context) return getNotMetRequirements(context) == 0;
&& checkChargingRequirement(context)
&& checkIdleRequirement(context);
} }
/** /**
* Returns the requirement flags that are not met, or 0. * Returns {@link RequirementFlags} that are not met, or 0.
* *
* @param context Any context. * @param context Any context.
* @return The requirement flags that are not met, or 0. * @return RequirementFlags that are not met, or 0.
*/ */
@RequirementFlags @RequirementFlags
public int getNotMetRequirements(Context context) { public int getNotMetRequirements(Context context) {
...@@ -202,7 +200,7 @@ public final class Requirements { ...@@ -202,7 +200,7 @@ public final class Requirements {
logd("Roaming: " + roaming); logd("Roaming: " + roaming);
return !roaming; return !roaming;
} }
boolean activeNetworkMetered = isActiveNetworkMetered(connectivityManager, networkInfo); boolean activeNetworkMetered = connectivityManager.isActiveNetworkMetered();
logd("Metered network: " + activeNetworkMetered); logd("Metered network: " + activeNetworkMetered);
if (networkRequirement == NETWORK_TYPE_UNMETERED) { if (networkRequirement == NETWORK_TYPE_UNMETERED) {
return !activeNetworkMetered; return !activeNetworkMetered;
...@@ -257,17 +255,6 @@ public final class Requirements { ...@@ -257,17 +255,6 @@ public final class Requirements {
return !validated; return !validated;
} }
private static boolean isActiveNetworkMetered(
ConnectivityManager connectivityManager, NetworkInfo networkInfo) {
if (Util.SDK_INT >= 16) {
return connectivityManager.isActiveNetworkMetered();
}
int type = networkInfo.getType();
return type != ConnectivityManager.TYPE_WIFI
&& type != ConnectivityManager.TYPE_BLUETOOTH
&& type != ConnectivityManager.TYPE_ETHERNET;
}
private static void logd(String message) { private static void logd(String message) {
if (Scheduler.DEBUG) { if (Scheduler.DEBUG) {
Log.d(TAG, message); Log.d(TAG, message);
...@@ -285,4 +272,20 @@ public final class Requirements { ...@@ -285,4 +272,20 @@ public final class Requirements {
+ (isIdleRequired() ? ",idle" : "") + (isIdleRequired() ? ",idle" : "")
+ '}'; + '}';
} }
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
return requirements == ((Requirements) o).requirements;
}
@Override
public int hashCode() {
return requirements;
}
} }
...@@ -42,21 +42,16 @@ public final class RequirementsWatcher { ...@@ -42,21 +42,16 @@ public final class RequirementsWatcher {
* Requirements} are met. * Requirements} are met.
*/ */
public interface Listener { public interface Listener {
/** /**
* Called when all of the requirements are met. * Called when there is a change on the met requirements.
* *
* @param requirementsWatcher Calling instance. * @param requirementsWatcher Calling instance.
* @param notMetRequirements {@link Requirements.RequirementFlags RequirementFlags} that are not
* met, or 0.
*/ */
void requirementsMet(RequirementsWatcher requirementsWatcher); void onRequirementsStateChanged(
RequirementsWatcher requirementsWatcher,
/** @Requirements.RequirementFlags int notMetRequirements);
* Called when there is at least one not met requirement and there is a change on which of the
* requirements are not met.
*
* @param requirementsWatcher Calling instance.
*/
void requirementsNotMet(RequirementsWatcher requirementsWatcher);
} }
private static final String TAG = "RequirementsWatcher"; private static final String TAG = "RequirementsWatcher";
...@@ -66,8 +61,9 @@ public final class RequirementsWatcher { ...@@ -66,8 +61,9 @@ public final class RequirementsWatcher {
private final Requirements requirements; private final Requirements requirements;
private DeviceStatusChangeReceiver receiver; private DeviceStatusChangeReceiver receiver;
private int notMetRequirements; @Requirements.RequirementFlags private int notMetRequirements;
private CapabilityValidatedCallback networkCallback; private CapabilityValidatedCallback networkCallback;
private Handler handler;
/** /**
* @param context Any context. * @param context Any context.
...@@ -84,9 +80,13 @@ public final class RequirementsWatcher { ...@@ -84,9 +80,13 @@ public final class RequirementsWatcher {
/** /**
* Starts watching for changes. Must be called from a thread that has an associated {@link * Starts watching for changes. Must be called from a thread that has an associated {@link
* Looper}. Listener methods are called on the caller thread. * Looper}. Listener methods are called on the caller thread.
*
* @return Initial {@link Requirements.RequirementFlags RequirementFlags} that are not met, or 0.
*/ */
public void start() { @Requirements.RequirementFlags
public int start() {
Assertions.checkNotNull(Looper.myLooper()); Assertions.checkNotNull(Looper.myLooper());
handler = new Handler();
notMetRequirements = requirements.getNotMetRequirements(context); notMetRequirements = requirements.getNotMetRequirements(context);
...@@ -111,8 +111,9 @@ public final class RequirementsWatcher { ...@@ -111,8 +111,9 @@ public final class RequirementsWatcher {
} }
} }
receiver = new DeviceStatusChangeReceiver(); receiver = new DeviceStatusChangeReceiver();
context.registerReceiver(receiver, filter, null, new Handler()); context.registerReceiver(receiver, filter, null, handler);
logd(this + " started"); logd(this + " started");
return notMetRequirements;
} }
/** Stops watching for changes. */ /** Stops watching for changes. */
...@@ -160,18 +161,12 @@ public final class RequirementsWatcher { ...@@ -160,18 +161,12 @@ public final class RequirementsWatcher {
} }
private void checkRequirements() { private void checkRequirements() {
@Requirements.RequirementFlags
int notMetRequirements = requirements.getNotMetRequirements(context); int notMetRequirements = requirements.getNotMetRequirements(context);
if (this.notMetRequirements == notMetRequirements) { if (this.notMetRequirements != notMetRequirements) {
logd("notMetRequirements hasn't changed: " + notMetRequirements); this.notMetRequirements = notMetRequirements;
return; logd("notMetRequirements has changed: " + notMetRequirements);
} listener.onRequirementsStateChanged(this, notMetRequirements);
this.notMetRequirements = notMetRequirements;
if (notMetRequirements == 0) {
logd("start job");
listener.requirementsMet(this);
} else {
logd("stop job");
listener.requirementsNotMet(this);
} }
} }
...@@ -195,16 +190,22 @@ public final class RequirementsWatcher { ...@@ -195,16 +190,22 @@ public final class RequirementsWatcher {
private final class CapabilityValidatedCallback extends ConnectivityManager.NetworkCallback { private final class CapabilityValidatedCallback extends ConnectivityManager.NetworkCallback {
@Override @Override
public void onAvailable(Network network) { public void onAvailable(Network network) {
super.onAvailable(network); onNetworkCallback();
logd(RequirementsWatcher.this + " NetworkCallback.onAvailable");
checkRequirements();
} }
@Override @Override
public void onLost(Network network) { public void onLost(Network network) {
super.onLost(network); onNetworkCallback();
logd(RequirementsWatcher.this + " NetworkCallback.onLost"); }
checkRequirements();
private void onNetworkCallback() {
handler.post(
() -> {
if (networkCallback != null) {
logd(RequirementsWatcher.this + " NetworkCallback");
checkRequirements();
}
});
} }
} }
} }
...@@ -206,10 +206,10 @@ public final class ClippingMediaSource extends CompositeMediaSource<Void> { ...@@ -206,10 +206,10 @@ public final class ClippingMediaSource extends CompositeMediaSource<Void> {
} }
@Override @Override
public MediaPeriod createPeriod(MediaPeriodId id, Allocator allocator) { public MediaPeriod createPeriod(MediaPeriodId id, Allocator allocator, long startPositionUs) {
ClippingMediaPeriod mediaPeriod = ClippingMediaPeriod mediaPeriod =
new ClippingMediaPeriod( new ClippingMediaPeriod(
mediaSource.createPeriod(id, allocator), mediaSource.createPeriod(id, allocator, startPositionUs),
enableInitialDiscontinuity, enableInitialDiscontinuity,
periodStartUs, periodStartUs,
periodEndUs); periodEndUs);
......
...@@ -25,7 +25,7 @@ import java.io.IOException; ...@@ -25,7 +25,7 @@ import java.io.IOException;
/** /**
* Media period that wraps a media source and defers calling its {@link * Media period that wraps a media source and defers calling its {@link
* MediaSource#createPeriod(MediaPeriodId, Allocator)} method until {@link * MediaSource#createPeriod(MediaPeriodId, Allocator, long)} method until {@link
* #createPeriod(MediaPeriodId)} has been called. This is useful if you need to return a media * #createPeriod(MediaPeriodId)} has been called. This is useful if you need to return a media
* period immediately but the media source that should create it is not yet prepared. * period immediately but the media source that should create it is not yet prepared.
*/ */
...@@ -60,11 +60,14 @@ public final class DeferredMediaPeriod implements MediaPeriod, MediaPeriod.Callb ...@@ -60,11 +60,14 @@ public final class DeferredMediaPeriod implements MediaPeriod, MediaPeriod.Callb
* @param mediaSource The media source to wrap. * @param mediaSource The media source to wrap.
* @param id The identifier used to create the deferred media period. * @param id The identifier used to create the deferred media period.
* @param allocator The allocator used to create the media period. * @param allocator The allocator used to create the media period.
* @param preparePositionUs The expected start position, in microseconds.
*/ */
public DeferredMediaPeriod(MediaSource mediaSource, MediaPeriodId id, Allocator allocator) { public DeferredMediaPeriod(
MediaSource mediaSource, MediaPeriodId id, Allocator allocator, long preparePositionUs) {
this.id = id; this.id = id;
this.allocator = allocator; this.allocator = allocator;
this.mediaSource = mediaSource; this.mediaSource = mediaSource;
this.preparePositionUs = preparePositionUs;
preparePositionOverrideUs = C.TIME_UNSET; preparePositionOverrideUs = C.TIME_UNSET;
} }
...@@ -86,28 +89,25 @@ public final class DeferredMediaPeriod implements MediaPeriod, MediaPeriod.Callb ...@@ -86,28 +89,25 @@ public final class DeferredMediaPeriod implements MediaPeriod, MediaPeriod.Callb
/** /**
* Overrides the default prepare position at which to prepare the media period. This value is only * 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. * used if called before {@link #createPeriod(MediaPeriodId)}.
* *
* @param defaultPreparePositionUs The default prepare position to use, in microseconds. * @param preparePositionUs The default prepare position to use, in microseconds.
*/ */
public void overridePreparePositionUs(long defaultPreparePositionUs) { public void overridePreparePositionUs(long preparePositionUs) {
preparePositionOverrideUs = defaultPreparePositionUs; preparePositionOverrideUs = preparePositionUs;
} }
/** /**
* Calls {@link MediaSource#createPeriod(MediaPeriodId, Allocator)} on the wrapped source then * Calls {@link MediaSource#createPeriod(MediaPeriodId, Allocator, long)} on the wrapped source
* prepares it if {@link #prepare(Callback, long)} has been called. Call {@link #releasePeriod()} * then prepares it if {@link #prepare(Callback, long)} has been called. Call {@link
* to release the period. * #releasePeriod()} to release the period.
* *
* @param id The identifier that should be used to create the media period from the media source. * @param id The identifier that should be used to create the media period from the media source.
*/ */
public void createPeriod(MediaPeriodId id) { public void createPeriod(MediaPeriodId id) {
mediaPeriod = mediaSource.createPeriod(id, allocator); long preparePositionUs = getPreparePositionWithOverride(this.preparePositionUs);
mediaPeriod = mediaSource.createPeriod(id, allocator, preparePositionUs);
if (callback != null) { if (callback != null) {
long preparePositionUs =
preparePositionOverrideUs != C.TIME_UNSET
? preparePositionOverrideUs
: this.preparePositionUs;
mediaPeriod.prepare(this, preparePositionUs); mediaPeriod.prepare(this, preparePositionUs);
} }
} }
...@@ -124,9 +124,8 @@ public final class DeferredMediaPeriod implements MediaPeriod, MediaPeriod.Callb ...@@ -124,9 +124,8 @@ public final class DeferredMediaPeriod implements MediaPeriod, MediaPeriod.Callb
@Override @Override
public void prepare(Callback callback, long preparePositionUs) { public void prepare(Callback callback, long preparePositionUs) {
this.callback = callback; this.callback = callback;
this.preparePositionUs = preparePositionUs;
if (mediaPeriod != null) { if (mediaPeriod != null) {
mediaPeriod.prepare(this, preparePositionUs); mediaPeriod.prepare(this, getPreparePositionWithOverride(this.preparePositionUs));
} }
} }
...@@ -217,4 +216,9 @@ public final class DeferredMediaPeriod implements MediaPeriod, MediaPeriod.Callb ...@@ -217,4 +216,9 @@ public final class DeferredMediaPeriod implements MediaPeriod, MediaPeriod.Callb
callback.onPrepared(this); callback.onPrepared(this);
} }
private long getPreparePositionWithOverride(long preparePositionUs) {
return preparePositionOverrideUs != C.TIME_UNSET
? preparePositionOverrideUs
: preparePositionUs;
}
} }
...@@ -20,6 +20,7 @@ import android.os.Handler; ...@@ -20,6 +20,7 @@ import android.os.Handler;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Player; import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.extractor.DefaultExtractorsFactory; import com.google.android.exoplayer2.extractor.DefaultExtractorsFactory;
import com.google.android.exoplayer2.extractor.Extractor; import com.google.android.exoplayer2.extractor.Extractor;
import com.google.android.exoplayer2.extractor.ExtractorsFactory; import com.google.android.exoplayer2.extractor.ExtractorsFactory;
...@@ -32,25 +33,13 @@ import com.google.android.exoplayer2.upstream.TransferListener; ...@@ -32,25 +33,13 @@ import com.google.android.exoplayer2.upstream.TransferListener;
import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Assertions;
import java.io.IOException; import java.io.IOException;
/** /** @deprecated Use {@link ProgressiveMediaSource} instead. */
* Provides one period that loads data from a {@link Uri} and extracted using an {@link Extractor}. @Deprecated
* @SuppressWarnings("deprecation")
* <p>If the possible input stream container formats are known, pass a factory that instantiates
* extractors for them to the constructor. Otherwise, pass a {@link DefaultExtractorsFactory} to use
* the default extractors. When reading a new stream, the first {@link Extractor} in the array of
* extractors created by the factory that returns {@code true} from {@link Extractor#sniff} will be
* used to extract samples from the input stream.
*
* <p>Note that the built-in extractors for AAC, MPEG PS/TS and FLV streams do not support seeking.
*/
public final class ExtractorMediaSource extends BaseMediaSource public final class ExtractorMediaSource extends BaseMediaSource
implements ExtractorMediaPeriod.Listener { implements MediaSource.SourceInfoRefreshListener {
/** /** @deprecated Use {@link MediaSourceEventListener} instead. */
* Listener of {@link ExtractorMediaSource} events.
*
* @deprecated Use {@link MediaSourceEventListener}.
*/
@Deprecated @Deprecated
public interface EventListener { public interface EventListener {
...@@ -70,7 +59,8 @@ public final class ExtractorMediaSource extends BaseMediaSource ...@@ -70,7 +59,8 @@ public final class ExtractorMediaSource extends BaseMediaSource
} }
/** Factory for {@link ExtractorMediaSource}s. */ /** Use {@link ProgressiveMediaSource.Factory} instead. */
@Deprecated
public static final class Factory implements AdsMediaSource.MediaSourceFactory { public static final class Factory implements AdsMediaSource.MediaSourceFactory {
private final DataSource.Factory dataSourceFactory; private final DataSource.Factory dataSourceFactory;
...@@ -232,23 +222,11 @@ public final class ExtractorMediaSource extends BaseMediaSource ...@@ -232,23 +222,11 @@ public final class ExtractorMediaSource extends BaseMediaSource
} }
} }
/** @Deprecated
* The default number of bytes that should be loaded between each each invocation of {@link public static final int DEFAULT_LOADING_CHECK_INTERVAL_BYTES =
* MediaPeriod.Callback#onContinueLoadingRequested(SequenceableLoader)}. ProgressiveMediaSource.DEFAULT_LOADING_CHECK_INTERVAL_BYTES;
*/
public static final int DEFAULT_LOADING_CHECK_INTERVAL_BYTES = 1024 * 1024;
private final Uri uri;
private final DataSource.Factory dataSourceFactory;
private final ExtractorsFactory extractorsFactory;
private final LoadErrorHandlingPolicy loadableLoadErrorHandlingPolicy;
private final String customCacheKey;
private final int continueLoadingCheckIntervalBytes;
private final @Nullable Object tag;
private long timelineDurationUs; private final ProgressiveMediaSource progressiveMediaSource;
private boolean timelineIsSeekable;
private @Nullable TransferListener transferListener;
/** /**
* @param uri The {@link Uri} of the media stream. * @param uri The {@link Uri} of the media stream.
...@@ -261,7 +239,6 @@ public final class ExtractorMediaSource extends BaseMediaSource ...@@ -261,7 +239,6 @@ public final class ExtractorMediaSource extends BaseMediaSource
* @deprecated Use {@link Factory} instead. * @deprecated Use {@link Factory} instead.
*/ */
@Deprecated @Deprecated
@SuppressWarnings("deprecation")
public ExtractorMediaSource( public ExtractorMediaSource(
Uri uri, Uri uri,
DataSource.Factory dataSourceFactory, DataSource.Factory dataSourceFactory,
...@@ -284,7 +261,6 @@ public final class ExtractorMediaSource extends BaseMediaSource ...@@ -284,7 +261,6 @@ public final class ExtractorMediaSource extends BaseMediaSource
* @deprecated Use {@link Factory} instead. * @deprecated Use {@link Factory} instead.
*/ */
@Deprecated @Deprecated
@SuppressWarnings("deprecation")
public ExtractorMediaSource( public ExtractorMediaSource(
Uri uri, Uri uri,
DataSource.Factory dataSourceFactory, DataSource.Factory dataSourceFactory,
...@@ -317,7 +293,6 @@ public final class ExtractorMediaSource extends BaseMediaSource ...@@ -317,7 +293,6 @@ public final class ExtractorMediaSource extends BaseMediaSource
* @deprecated Use {@link Factory} instead. * @deprecated Use {@link Factory} instead.
*/ */
@Deprecated @Deprecated
@SuppressWarnings("deprecation")
public ExtractorMediaSource( public ExtractorMediaSource(
Uri uri, Uri uri,
DataSource.Factory dataSourceFactory, DataSource.Factory dataSourceFactory,
...@@ -347,93 +322,57 @@ public final class ExtractorMediaSource extends BaseMediaSource ...@@ -347,93 +322,57 @@ public final class ExtractorMediaSource extends BaseMediaSource
@Nullable String customCacheKey, @Nullable String customCacheKey,
int continueLoadingCheckIntervalBytes, int continueLoadingCheckIntervalBytes,
@Nullable Object tag) { @Nullable Object tag) {
this.uri = uri; progressiveMediaSource =
this.dataSourceFactory = dataSourceFactory; new ProgressiveMediaSource(
this.extractorsFactory = extractorsFactory; uri,
this.loadableLoadErrorHandlingPolicy = loadableLoadErrorHandlingPolicy; dataSourceFactory,
this.customCacheKey = customCacheKey; extractorsFactory,
this.continueLoadingCheckIntervalBytes = continueLoadingCheckIntervalBytes; loadableLoadErrorHandlingPolicy,
this.timelineDurationUs = C.TIME_UNSET; customCacheKey,
this.tag = tag; continueLoadingCheckIntervalBytes,
tag);
} }
@Override @Override
@Nullable @Nullable
public Object getTag() { public Object getTag() {
return tag; return progressiveMediaSource.getTag();
} }
@Override @Override
public void prepareSourceInternal(@Nullable TransferListener mediaTransferListener) { public void prepareSourceInternal(@Nullable TransferListener mediaTransferListener) {
transferListener = mediaTransferListener; progressiveMediaSource.prepareSource(/* listener= */ this, mediaTransferListener);
notifySourceInfoRefreshed(timelineDurationUs, /* isSeekable= */ false);
} }
@Override @Override
public void maybeThrowSourceInfoRefreshError() throws IOException { public void maybeThrowSourceInfoRefreshError() throws IOException {
// Do nothing. progressiveMediaSource.maybeThrowSourceInfoRefreshError();
} }
@Override @Override
public MediaPeriod createPeriod(MediaPeriodId id, Allocator allocator) { public MediaPeriod createPeriod(MediaPeriodId id, Allocator allocator, long startPositionUs) {
DataSource dataSource = dataSourceFactory.createDataSource(); return progressiveMediaSource.createPeriod(id, allocator, startPositionUs);
if (transferListener != null) {
dataSource.addTransferListener(transferListener);
}
return new ExtractorMediaPeriod(
uri,
dataSource,
extractorsFactory.createExtractors(),
loadableLoadErrorHandlingPolicy,
createEventDispatcher(id),
this,
allocator,
customCacheKey,
continueLoadingCheckIntervalBytes);
} }
@Override @Override
public void releasePeriod(MediaPeriod mediaPeriod) { public void releasePeriod(MediaPeriod mediaPeriod) {
((ExtractorMediaPeriod) mediaPeriod).release(); progressiveMediaSource.releasePeriod(mediaPeriod);
} }
@Override @Override
public void releaseSourceInternal() { public void releaseSourceInternal() {
// Do nothing. progressiveMediaSource.releaseSource(/* listener= */ this);
} }
// ExtractorMediaPeriod.Listener implementation.
@Override @Override
public void onSourceInfoRefreshed(long durationUs, boolean isSeekable) { public void onSourceInfoRefreshed(
// If we already have the duration from a previous source info refresh, use it. MediaSource source, Timeline timeline, @Nullable Object manifest) {
durationUs = durationUs == C.TIME_UNSET ? timelineDurationUs : durationUs; refreshSourceInfo(timeline, manifest);
if (timelineDurationUs == durationUs && timelineIsSeekable == isSeekable) {
// Suppress no-op source info changes.
return;
}
notifySourceInfoRefreshed(durationUs, isSeekable);
}
// Internal methods.
private void notifySourceInfoRefreshed(long durationUs, boolean isSeekable) {
timelineDurationUs = durationUs;
timelineIsSeekable = isSeekable;
// TODO: Make timeline dynamic until its duration is known. This is non-trivial. See b/69703223.
refreshSourceInfo(
new SinglePeriodTimeline(
timelineDurationUs, timelineIsSeekable, /* isDynamic= */ false, tag),
/* manifest= */ null);
} }
/**
* Wraps a deprecated {@link EventListener}, invoking its callback from the equivalent callback in
* {@link MediaSourceEventListener}.
*/
@Deprecated @Deprecated
@SuppressWarnings("deprecation")
private static final class EventListenerWrapper extends DefaultMediaSourceEventListener { private static final class EventListenerWrapper extends DefaultMediaSourceEventListener {
private final EventListener eventListener; private final EventListener eventListener;
public EventListenerWrapper(EventListener eventListener) { public EventListenerWrapper(EventListener eventListener) {
......
...@@ -31,7 +31,7 @@ import java.util.Map; ...@@ -31,7 +31,7 @@ import java.util.Map;
* Loops a {@link MediaSource} a specified number of times. * Loops a {@link MediaSource} a specified number of times.
* *
* <p>Note: To loop a {@link MediaSource} indefinitely, it is usually better to use {@link * <p>Note: To loop a {@link MediaSource} indefinitely, it is usually better to use {@link
* ExoPlayer#setRepeatMode(int)}. * ExoPlayer#setRepeatMode(int)} instead of this class.
*/ */
public final class LoopingMediaSource extends CompositeMediaSource<Void> { public final class LoopingMediaSource extends CompositeMediaSource<Void> {
...@@ -77,14 +77,15 @@ public final class LoopingMediaSource extends CompositeMediaSource<Void> { ...@@ -77,14 +77,15 @@ public final class LoopingMediaSource extends CompositeMediaSource<Void> {
} }
@Override @Override
public MediaPeriod createPeriod(MediaPeriodId id, Allocator allocator) { public MediaPeriod createPeriod(MediaPeriodId id, Allocator allocator, long startPositionUs) {
if (loopCount == Integer.MAX_VALUE) { if (loopCount == Integer.MAX_VALUE) {
return childSource.createPeriod(id, allocator); return childSource.createPeriod(id, allocator, startPositionUs);
} }
Object childPeriodUid = LoopingTimeline.getChildPeriodUidFromConcatenatedUid(id.periodUid); Object childPeriodUid = LoopingTimeline.getChildPeriodUidFromConcatenatedUid(id.periodUid);
MediaPeriodId childMediaPeriodId = id.copyWithPeriodUid(childPeriodUid); MediaPeriodId childMediaPeriodId = id.copyWithPeriodUid(childPeriodUid);
childMediaPeriodIdToMediaPeriodId.put(childMediaPeriodId, id); childMediaPeriodIdToMediaPeriodId.put(childMediaPeriodId, id);
MediaPeriod mediaPeriod = childSource.createPeriod(childMediaPeriodId, allocator); MediaPeriod mediaPeriod =
childSource.createPeriod(childMediaPeriodId, allocator, startPositionUs);
mediaPeriodToChildMediaPeriodId.put(mediaPeriod, childMediaPeriodId); mediaPeriodToChildMediaPeriodId.put(mediaPeriod, childMediaPeriodId);
return mediaPeriod; return mediaPeriod;
} }
......
...@@ -87,18 +87,18 @@ public interface MediaPeriod extends SequenceableLoader { ...@@ -87,18 +87,18 @@ public interface MediaPeriod extends SequenceableLoader {
TrackGroupArray getTrackGroups(); TrackGroupArray getTrackGroups();
/** /**
* Returns a list of {@link StreamKey stream keys} which allow to filter the media in this period * Returns a list of {@link StreamKey StreamKeys} which allow to filter the media in this period
* to load only the parts needed to play the provided {@link TrackSelection}. * to load only the parts needed to play the provided {@link TrackSelection TrackSelections}.
* *
* <p>This method is only called after the period has been prepared. * <p>This method is only called after the period has been prepared.
* *
* @param trackSelection The {@link TrackSelection} describing the tracks for which stream keys * @param trackSelections The {@link TrackSelection TrackSelections} describing the tracks for
* are requested. * which stream keys are requested.
* @return The corresponding {@link StreamKey stream keys} for the selected tracks, or an empty * @return The corresponding {@link StreamKey StreamKeys} for the selected tracks, or an empty
* list if filtering is not possible and the entire media needs to be loaded to play the * list if filtering is not possible and the entire media needs to be loaded to play the
* selected tracks. * selected tracks.
*/ */
default List<StreamKey> getStreamKeys(TrackSelection trackSelection) { default List<StreamKey> getStreamKeys(List<TrackSelection> trackSelections) {
return Collections.emptyList(); return Collections.emptyList();
} }
......
...@@ -18,6 +18,7 @@ package com.google.android.exoplayer2.source; ...@@ -18,6 +18,7 @@ package com.google.android.exoplayer2.source;
import android.os.Handler; import android.os.Handler;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ExoPlayer;
import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.upstream.Allocator; import com.google.android.exoplayer2.upstream.Allocator;
import com.google.android.exoplayer2.upstream.TransferListener; import com.google.android.exoplayer2.upstream.TransferListener;
...@@ -34,8 +35,8 @@ import java.io.IOException; ...@@ -34,8 +35,8 @@ import java.io.IOException;
* on the {@link SourceInfoRefreshListener}s passed to {@link * on the {@link SourceInfoRefreshListener}s passed to {@link
* #prepareSource(SourceInfoRefreshListener, TransferListener)}. * #prepareSource(SourceInfoRefreshListener, TransferListener)}.
* <li>To provide {@link MediaPeriod} instances for the periods in its timeline. MediaPeriods are * <li>To provide {@link MediaPeriod} instances for the periods in its timeline. MediaPeriods are
* obtained by calling {@link #createPeriod(MediaPeriodId, Allocator)}, and provide a way for * obtained by calling {@link #createPeriod(MediaPeriodId, Allocator, long)}, and provide a
* the player to load and read the media. * way for the player to load and read the media.
* </ul> * </ul>
* *
* All methods are called on the player's internal playback thread, as described in the {@link * All methods are called on the player's internal playback thread, as described in the {@link
...@@ -89,12 +90,10 @@ public interface MediaSource { ...@@ -89,12 +90,10 @@ public interface MediaSource {
public final long windowSequenceNumber; public final long windowSequenceNumber;
/** /**
* The end position to which the media period's content is clipped in order to play a following * The index of the next ad group to which the media period's content is clipped, or {@link
* ad group, in microseconds, or {@link C#TIME_UNSET} if there is no following ad group or if * C#INDEX_UNSET} if there is no following ad group or if this media period is an ad.
* this media period is an ad. The value {@link C#TIME_END_OF_SOURCE} indicates that a postroll
* ad follows at the end of this content media period.
*/ */
public final long endPositionUs; public final int nextAdGroupIndex;
/** /**
* Creates a media period identifier for a dummy period which is not part of a buffered sequence * Creates a media period identifier for a dummy period which is not part of a buffered sequence
...@@ -103,7 +102,7 @@ public interface MediaSource { ...@@ -103,7 +102,7 @@ public interface MediaSource {
* @param periodUid The unique id of the timeline period. * @param periodUid The unique id of the timeline period.
*/ */
public MediaPeriodId(Object periodUid) { public MediaPeriodId(Object periodUid) {
this(periodUid, C.INDEX_UNSET); this(periodUid, /* windowSequenceNumber= */ C.INDEX_UNSET);
} }
/** /**
...@@ -114,7 +113,12 @@ public interface MediaSource { ...@@ -114,7 +113,12 @@ public interface MediaSource {
* windows this media period is part of. * windows this media period is part of.
*/ */
public MediaPeriodId(Object periodUid, long windowSequenceNumber) { public MediaPeriodId(Object periodUid, long windowSequenceNumber) {
this(periodUid, C.INDEX_UNSET, C.INDEX_UNSET, windowSequenceNumber, C.TIME_UNSET); this(
periodUid,
/* adGroupIndex= */ C.INDEX_UNSET,
/* adIndexInAdGroup= */ C.INDEX_UNSET,
windowSequenceNumber,
/* nextAdGroupIndex= */ C.INDEX_UNSET);
} }
/** /**
...@@ -123,11 +127,16 @@ public interface MediaSource { ...@@ -123,11 +127,16 @@ public interface MediaSource {
* @param periodUid The unique id of the timeline period. * @param periodUid The unique id of the timeline period.
* @param windowSequenceNumber The sequence number of the window in the buffered sequence of * @param windowSequenceNumber The sequence number of the window in the buffered sequence of
* windows this media period is part of. * windows this media period is part of.
* @param endPositionUs The end position of the media period within the timeline period, in * @param nextAdGroupIndex The index of the next ad group to which the media period's content is
* microseconds. * clipped.
*/ */
public MediaPeriodId(Object periodUid, long windowSequenceNumber, long endPositionUs) { public MediaPeriodId(Object periodUid, long windowSequenceNumber, int nextAdGroupIndex) {
this(periodUid, C.INDEX_UNSET, C.INDEX_UNSET, windowSequenceNumber, endPositionUs); this(
periodUid,
/* adGroupIndex= */ C.INDEX_UNSET,
/* adIndexInAdGroup= */ C.INDEX_UNSET,
windowSequenceNumber,
nextAdGroupIndex);
} }
/** /**
...@@ -142,7 +151,12 @@ public interface MediaSource { ...@@ -142,7 +151,12 @@ public interface MediaSource {
*/ */
public MediaPeriodId( public MediaPeriodId(
Object periodUid, int adGroupIndex, int adIndexInAdGroup, long windowSequenceNumber) { Object periodUid, int adGroupIndex, int adIndexInAdGroup, long windowSequenceNumber) {
this(periodUid, adGroupIndex, adIndexInAdGroup, windowSequenceNumber, C.TIME_UNSET); this(
periodUid,
adGroupIndex,
adIndexInAdGroup,
windowSequenceNumber,
/* nextAdGroupIndex= */ C.INDEX_UNSET);
} }
private MediaPeriodId( private MediaPeriodId(
...@@ -150,12 +164,12 @@ public interface MediaSource { ...@@ -150,12 +164,12 @@ public interface MediaSource {
int adGroupIndex, int adGroupIndex,
int adIndexInAdGroup, int adIndexInAdGroup,
long windowSequenceNumber, long windowSequenceNumber,
long endPositionUs) { int nextAdGroupIndex) {
this.periodUid = periodUid; this.periodUid = periodUid;
this.adGroupIndex = adGroupIndex; this.adGroupIndex = adGroupIndex;
this.adIndexInAdGroup = adIndexInAdGroup; this.adIndexInAdGroup = adIndexInAdGroup;
this.windowSequenceNumber = windowSequenceNumber; this.windowSequenceNumber = windowSequenceNumber;
this.endPositionUs = endPositionUs; this.nextAdGroupIndex = nextAdGroupIndex;
} }
/** Returns a copy of this period identifier but with {@code newPeriodUid} as its period uid. */ /** Returns a copy of this period identifier but with {@code newPeriodUid} as its period uid. */
...@@ -163,7 +177,7 @@ public interface MediaSource { ...@@ -163,7 +177,7 @@ public interface MediaSource {
return periodUid.equals(newPeriodUid) return periodUid.equals(newPeriodUid)
? this ? this
: new MediaPeriodId( : new MediaPeriodId(
newPeriodUid, adGroupIndex, adIndexInAdGroup, windowSequenceNumber, endPositionUs); newPeriodUid, adGroupIndex, adIndexInAdGroup, windowSequenceNumber, nextAdGroupIndex);
} }
/** /**
...@@ -187,7 +201,7 @@ public interface MediaSource { ...@@ -187,7 +201,7 @@ public interface MediaSource {
&& adGroupIndex == periodId.adGroupIndex && adGroupIndex == periodId.adGroupIndex
&& adIndexInAdGroup == periodId.adIndexInAdGroup && adIndexInAdGroup == periodId.adIndexInAdGroup
&& windowSequenceNumber == periodId.windowSequenceNumber && windowSequenceNumber == periodId.windowSequenceNumber
&& endPositionUs == periodId.endPositionUs; && nextAdGroupIndex == periodId.nextAdGroupIndex;
} }
@Override @Override
...@@ -197,7 +211,7 @@ public interface MediaSource { ...@@ -197,7 +211,7 @@ public interface MediaSource {
result = 31 * result + adGroupIndex; result = 31 * result + adGroupIndex;
result = 31 * result + adIndexInAdGroup; result = 31 * result + adIndexInAdGroup;
result = 31 * result + (int) windowSequenceNumber; result = 31 * result + (int) windowSequenceNumber;
result = 31 * result + (int) endPositionUs; result = 31 * result + nextAdGroupIndex;
return result; return result;
} }
} }
...@@ -224,7 +238,6 @@ public interface MediaSource { ...@@ -224,7 +238,6 @@ public interface MediaSource {
default Object getTag() { default Object getTag() {
return null; return null;
} }
/** /**
* Starts source preparation if not yet started, and adds a listener for timeline and/or manifest * Starts source preparation if not yet started, and adds a listener for timeline and/or manifest
* updates. * updates.
...@@ -243,8 +256,7 @@ public interface MediaSource { ...@@ -243,8 +256,7 @@ public interface MediaSource {
* and other data. * and other data.
*/ */
void prepareSource( void prepareSource(
SourceInfoRefreshListener listener, SourceInfoRefreshListener listener, @Nullable TransferListener mediaTransferListener);
@Nullable TransferListener mediaTransferListener);
/** /**
* Throws any pending error encountered while loading or refreshing source information. * Throws any pending error encountered while loading or refreshing source information.
...@@ -261,9 +273,10 @@ public interface MediaSource { ...@@ -261,9 +273,10 @@ public interface MediaSource {
* *
* @param id The identifier of the period. * @param id The identifier of the period.
* @param allocator An {@link Allocator} from which to obtain media buffer allocations. * @param allocator An {@link Allocator} from which to obtain media buffer allocations.
* @param startPositionUs The expected start position, in microseconds.
* @return A new {@link MediaPeriod}. * @return A new {@link MediaPeriod}.
*/ */
MediaPeriod createPeriod(MediaPeriodId id, Allocator allocator); MediaPeriod createPeriod(MediaPeriodId id, Allocator allocator, long startPositionUs);
/** /**
* Releases the period. * Releases the period.
......
...@@ -120,13 +120,13 @@ public final class MergingMediaSource extends CompositeMediaSource<Integer> { ...@@ -120,13 +120,13 @@ public final class MergingMediaSource extends CompositeMediaSource<Integer> {
} }
@Override @Override
public MediaPeriod createPeriod(MediaPeriodId id, Allocator allocator) { public MediaPeriod createPeriod(MediaPeriodId id, Allocator allocator, long startPositionUs) {
MediaPeriod[] periods = new MediaPeriod[mediaSources.length]; MediaPeriod[] periods = new MediaPeriod[mediaSources.length];
int periodIndex = timelines[0].getIndexOfPeriod(id.periodUid); int periodIndex = timelines[0].getIndexOfPeriod(id.periodUid);
for (int i = 0; i < periods.length; i++) { for (int i = 0; i < periods.length; i++) {
MediaPeriodId childMediaPeriodId = MediaPeriodId childMediaPeriodId =
id.copyWithPeriodUid(timelines[i].getUidOfPeriod(periodIndex)); id.copyWithPeriodUid(timelines[i].getUidOfPeriod(periodIndex));
periods[i] = mediaSources[i].createPeriod(childMediaPeriodId, allocator); periods[i] = mediaSources[i].createPeriod(childMediaPeriodId, allocator, startPositionUs);
} }
return new MergingMediaPeriod(compositeSequenceableLoaderFactory, periods); return new MergingMediaPeriod(compositeSequenceableLoaderFactory, periods);
} }
......
...@@ -54,12 +54,13 @@ import java.io.IOException; ...@@ -54,12 +54,13 @@ import java.io.IOException;
import java.util.Arrays; import java.util.Arrays;
import org.checkerframework.checker.nullness.compatqual.NullableType; import org.checkerframework.checker.nullness.compatqual.NullableType;
/** /** A {@link MediaPeriod} that extracts data using an {@link Extractor}. */
* A {@link MediaPeriod} that extracts data using an {@link Extractor}. /* package */ final class ProgressiveMediaPeriod
*/ implements MediaPeriod,
/* package */ final class ExtractorMediaPeriod implements MediaPeriod, ExtractorOutput, ExtractorOutput,
Loader.Callback<ExtractorMediaPeriod.ExtractingLoadable>, Loader.ReleaseCallback, Loader.Callback<ProgressiveMediaPeriod.ExtractingLoadable>,
UpstreamFormatChangedListener { Loader.ReleaseCallback,
UpstreamFormatChangedListener {
/** /**
* Listener for information about the period. * Listener for information about the period.
...@@ -145,7 +146,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; ...@@ -145,7 +146,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
"nullness:argument.type.incompatible", "nullness:argument.type.incompatible",
"nullness:methodref.receiver.bound.invalid" "nullness:methodref.receiver.bound.invalid"
}) })
public ExtractorMediaPeriod( public ProgressiveMediaPeriod(
Uri uri, Uri uri,
DataSource dataSource, DataSource dataSource,
Extractor[] extractors, Extractor[] extractors,
...@@ -163,14 +164,15 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; ...@@ -163,14 +164,15 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
this.allocator = allocator; this.allocator = allocator;
this.customCacheKey = customCacheKey; this.customCacheKey = customCacheKey;
this.continueLoadingCheckIntervalBytes = continueLoadingCheckIntervalBytes; this.continueLoadingCheckIntervalBytes = continueLoadingCheckIntervalBytes;
loader = new Loader("Loader:ExtractorMediaPeriod"); loader = new Loader("Loader:ProgressiveMediaPeriod");
extractorHolder = new ExtractorHolder(extractors); extractorHolder = new ExtractorHolder(extractors);
loadCondition = new ConditionVariable(); loadCondition = new ConditionVariable();
maybeFinishPrepareRunnable = this::maybeFinishPrepare; maybeFinishPrepareRunnable = this::maybeFinishPrepare;
onContinueLoadingRequestedRunnable = onContinueLoadingRequestedRunnable =
() -> { () -> {
if (!released) { if (!released) {
Assertions.checkNotNull(callback).onContinueLoadingRequested(ExtractorMediaPeriod.this); Assertions.checkNotNull(callback)
.onContinueLoadingRequested(ProgressiveMediaPeriod.this);
} }
}; };
handler = new Handler(); handler = new Handler();
...@@ -356,18 +358,18 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; ...@@ -356,18 +358,18 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
} else if (isPendingReset()) { } else if (isPendingReset()) {
return pendingResetPositionUs; return pendingResetPositionUs;
} }
long largestQueuedTimestampUs; long largestQueuedTimestampUs = Long.MAX_VALUE;
if (haveAudioVideoTracks) { if (haveAudioVideoTracks) {
// Ignore non-AV tracks, which may be sparse or poorly interleaved. // Ignore non-AV tracks, which may be sparse or poorly interleaved.
largestQueuedTimestampUs = Long.MAX_VALUE;
int trackCount = sampleQueues.length; int trackCount = sampleQueues.length;
for (int i = 0; i < trackCount; i++) { for (int i = 0; i < trackCount; i++) {
if (trackIsAudioVideoFlags[i]) { if (trackIsAudioVideoFlags[i] && !sampleQueues[i].isLastSampleQueued()) {
largestQueuedTimestampUs = Math.min(largestQueuedTimestampUs, largestQueuedTimestampUs = Math.min(largestQueuedTimestampUs,
sampleQueues[i].getLargestQueuedTimestampUs()); sampleQueues[i].getLargestQueuedTimestampUs());
} }
} }
} else { }
if (largestQueuedTimestampUs == Long.MAX_VALUE) {
largestQueuedTimestampUs = getLargestQueuedTimestampUs(); largestQueuedTimestampUs = getLargestQueuedTimestampUs();
} }
return largestQueuedTimestampUs == Long.MIN_VALUE ? lastSeekPositionUs return largestQueuedTimestampUs == Long.MIN_VALUE ? lastSeekPositionUs
...@@ -851,23 +853,23 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; ...@@ -851,23 +853,23 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
@Override @Override
public boolean isReady() { public boolean isReady() {
return ExtractorMediaPeriod.this.isReady(track); return ProgressiveMediaPeriod.this.isReady(track);
} }
@Override @Override
public void maybeThrowError() throws IOException { public void maybeThrowError() throws IOException {
ExtractorMediaPeriod.this.maybeThrowError(); ProgressiveMediaPeriod.this.maybeThrowError();
} }
@Override @Override
public int readData(FormatHolder formatHolder, DecoderInputBuffer buffer, public int readData(FormatHolder formatHolder, DecoderInputBuffer buffer,
boolean formatRequired) { boolean formatRequired) {
return ExtractorMediaPeriod.this.readData(track, formatHolder, buffer, formatRequired); return ProgressiveMediaPeriod.this.readData(track, formatHolder, buffer, formatRequired);
} }
@Override @Override
public int skipData(long positionUs) { public int skipData(long positionUs) {
return ExtractorMediaPeriod.this.skipData(track, positionUs); return ProgressiveMediaPeriod.this.skipData(track, positionUs);
} }
} }
...@@ -988,7 +990,9 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; ...@@ -988,7 +990,9 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
position, position,
C.LENGTH_UNSET, C.LENGTH_UNSET,
customCacheKey, customCacheKey,
DataSpec.FLAG_ALLOW_ICY_METADATA | DataSpec.FLAG_DONT_CACHE_IF_LENGTH_UNKNOWN); DataSpec.FLAG_ALLOW_ICY_METADATA
| DataSpec.FLAG_DONT_CACHE_IF_LENGTH_UNKNOWN
| DataSpec.FLAG_ALLOW_CACHE_FRAGMENTATION);
} }
private void setLoadPosition(long position, long timeUs) { private void setLoadPosition(long position, long timeUs) {
......
...@@ -57,6 +57,7 @@ import com.google.android.exoplayer2.util.Util; ...@@ -57,6 +57,7 @@ import com.google.android.exoplayer2.util.Util;
private long largestDiscardedTimestampUs; private long largestDiscardedTimestampUs;
private long largestQueuedTimestampUs; private long largestQueuedTimestampUs;
private boolean isLastSampleQueued;
private boolean upstreamKeyframeRequired; private boolean upstreamKeyframeRequired;
private boolean upstreamFormatRequired; private boolean upstreamFormatRequired;
private Format upstreamFormat; private Format upstreamFormat;
...@@ -93,6 +94,7 @@ import com.google.android.exoplayer2.util.Util; ...@@ -93,6 +94,7 @@ import com.google.android.exoplayer2.util.Util;
upstreamKeyframeRequired = true; upstreamKeyframeRequired = true;
largestDiscardedTimestampUs = Long.MIN_VALUE; largestDiscardedTimestampUs = Long.MIN_VALUE;
largestQueuedTimestampUs = Long.MIN_VALUE; largestQueuedTimestampUs = Long.MIN_VALUE;
isLastSampleQueued = false;
if (resetUpstreamFormat) { if (resetUpstreamFormat) {
upstreamFormat = null; upstreamFormat = null;
upstreamFormatRequired = true; upstreamFormatRequired = true;
...@@ -118,6 +120,7 @@ import com.google.android.exoplayer2.util.Util; ...@@ -118,6 +120,7 @@ import com.google.android.exoplayer2.util.Util;
Assertions.checkArgument(0 <= discardCount && discardCount <= (length - readPosition)); Assertions.checkArgument(0 <= discardCount && discardCount <= (length - readPosition));
length -= discardCount; length -= discardCount;
largestQueuedTimestampUs = Math.max(largestDiscardedTimestampUs, getLargestTimestamp(length)); largestQueuedTimestampUs = Math.max(largestDiscardedTimestampUs, getLargestTimestamp(length));
isLastSampleQueued = discardCount == 0 && isLastSampleQueued;
if (length == 0) { if (length == 0) {
return 0; return 0;
} else { } else {
...@@ -186,6 +189,19 @@ import com.google.android.exoplayer2.util.Util; ...@@ -186,6 +189,19 @@ import com.google.android.exoplayer2.util.Util;
return largestQueuedTimestampUs; return largestQueuedTimestampUs;
} }
/**
* Returns whether the last sample of the stream has knowingly been queued. A return value of
* {@code false} means that the last sample had not been queued or that it's unknown whether the
* last sample has been queued.
*
* <p>Samples that were discarded by calling {@link #discardUpstreamSamples(int)} are not
* considered as having been queued. Samples that were dequeued from the front of the queue are
* considered as having been queued.
*/
public synchronized boolean isLastSampleQueued() {
return isLastSampleQueued;
}
/** Returns the timestamp of the first sample, or {@link Long#MIN_VALUE} if the queue is empty. */ /** Returns the timestamp of the first sample, or {@link Long#MIN_VALUE} if the queue is empty. */
public synchronized long getFirstTimestampUs() { public synchronized long getFirstTimestampUs() {
return length == 0 ? Long.MIN_VALUE : timesUs[relativeFirstIndex]; return length == 0 ? Long.MIN_VALUE : timesUs[relativeFirstIndex];
...@@ -224,7 +240,7 @@ import com.google.android.exoplayer2.util.Util; ...@@ -224,7 +240,7 @@ import com.google.android.exoplayer2.util.Util;
boolean formatRequired, boolean loadingFinished, Format downstreamFormat, boolean formatRequired, boolean loadingFinished, Format downstreamFormat,
SampleExtrasHolder extrasHolder) { SampleExtrasHolder extrasHolder) {
if (!hasNextSample()) { if (!hasNextSample()) {
if (loadingFinished) { if (loadingFinished || isLastSampleQueued) {
buffer.setFlags(C.BUFFER_FLAG_END_OF_STREAM); buffer.setFlags(C.BUFFER_FLAG_END_OF_STREAM);
return C.RESULT_BUFFER_READ; return C.RESULT_BUFFER_READ;
} else if (upstreamFormat != null } else if (upstreamFormat != null
...@@ -388,7 +404,9 @@ import com.google.android.exoplayer2.util.Util; ...@@ -388,7 +404,9 @@ import com.google.android.exoplayer2.util.Util;
upstreamKeyframeRequired = false; upstreamKeyframeRequired = false;
} }
Assertions.checkState(!upstreamFormatRequired); Assertions.checkState(!upstreamFormatRequired);
commitSampleTimestamp(timeUs);
isLastSampleQueued = (sampleFlags & C.BUFFER_FLAG_LAST_SAMPLE) != 0;
largestQueuedTimestampUs = Math.max(largestQueuedTimestampUs, timeUs);
int relativeEndIndex = getRelativeIndex(length); int relativeEndIndex = getRelativeIndex(length);
timesUs[relativeEndIndex] = timeUs; timesUs[relativeEndIndex] = timeUs;
...@@ -439,10 +457,6 @@ import com.google.android.exoplayer2.util.Util; ...@@ -439,10 +457,6 @@ import com.google.android.exoplayer2.util.Util;
} }
} }
public synchronized void commitSampleTimestamp(long timeUs) {
largestQueuedTimestampUs = Math.max(largestQueuedTimestampUs, timeUs);
}
/** /**
* Attempts to discard samples from the end of the queue to allow samples starting from the * Attempts to discard samples from the end of the queue to allow samples starting from the
* specified timestamp to be spliced in. Samples will not be discarded prior to the read position. * specified timestamp to be spliced in. Samples will not be discarded prior to the read position.
......
...@@ -224,6 +224,15 @@ public class SampleQueue implements TrackOutput { ...@@ -224,6 +224,15 @@ public class SampleQueue implements TrackOutput {
return metadataQueue.getLargestQueuedTimestampUs(); return metadataQueue.getLargestQueuedTimestampUs();
} }
/**
* Returns whether the last sample of the stream has knowingly been queued. A return value of
* {@code false} means that the last sample had not been queued or that it's unknown whether the
* last sample has been queued.
*/
public boolean isLastSampleQueued() {
return metadataQueue.isLastSampleQueued();
}
/** Returns the timestamp of the first sample, or {@link Long#MIN_VALUE} if the queue is empty. */ /** Returns the timestamp of the first sample, or {@link Long#MIN_VALUE} if the queue is empty. */
public long getFirstTimestampUs() { public long getFirstTimestampUs() {
return metadataQueue.getFirstTimestampUs(); return metadataQueue.getFirstTimestampUs();
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment