Merge pull request #24 from google/dev-v2

Dev v2
This commit is contained in:
ybai001 2022-06-08 08:32:43 +08:00 committed by GitHub
commit 867b107835
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
499 changed files with 27336 additions and 8577 deletions

View file

@ -17,7 +17,7 @@ buildscript {
mavenCentral() mavenCentral()
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:7.0.3' classpath 'com.android.tools.build:gradle:7.2.1'
classpath 'com.google.android.gms:strict-version-matcher-plugin:1.2.2' classpath 'com.google.android.gms:strict-version-matcher-plugin:1.2.2'
} }
} }

View file

@ -29,5 +29,10 @@ android {
targetCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8
} }
testOptions.unitTests.includeAndroidResources = true testOptions {
unitTests.all {
jvmArgs "-Xmx2g"
}
unitTests.includeAndroidResources true
}
} }

View file

@ -20,14 +20,14 @@ project.ext {
// Upgrading this requires [Internal ref: b/193254928] to be fixed, or some // Upgrading this requires [Internal ref: b/193254928] to be fixed, or some
// additional robolectric config. // additional robolectric config.
targetSdkVersion = 30 targetSdkVersion = 30
compileSdkVersion = 31 compileSdkVersion = 32
dexmakerVersion = '2.28.1' dexmakerVersion = '2.28.1'
junitVersion = '4.13.2' junitVersion = '4.13.2'
// Use the same Guava version as the Android repo: // Use the same Guava version as the Android repo:
// https://cs.android.com/android/platform/superproject/+/master:external/guava/METADATA // https://cs.android.com/android/platform/superproject/+/master:external/guava/METADATA
guavaVersion = '31.0.1-android' guavaVersion = '31.0.1-android'
mockitoVersion = '3.12.4' mockitoVersion = '3.12.4'
robolectricVersion = '4.6.1' robolectricVersion = '4.8.1'
// Keep this in sync with Google's internal Checker Framework version. // Keep this in sync with Google's internal Checker Framework version.
checkerframeworkVersion = '3.13.0' checkerframeworkVersion = '3.13.0'
checkerframeworkCompatVersion = '2.5.5' checkerframeworkCompatVersion = '2.5.5'

View file

@ -230,8 +230,8 @@ public class MainActivity extends AppCompatActivity
@Override @Override
public boolean onMove( public boolean onMove(
RecyclerView list, RecyclerView.ViewHolder origin, RecyclerView.ViewHolder target) { RecyclerView list, RecyclerView.ViewHolder origin, RecyclerView.ViewHolder target) {
int fromPosition = origin.getAdapterPosition(); int fromPosition = origin.getBindingAdapterPosition();
int toPosition = target.getAdapterPosition(); int toPosition = target.getBindingAdapterPosition();
if (draggingFromPosition == C.INDEX_UNSET) { if (draggingFromPosition == C.INDEX_UNSET) {
// A drag has started, but changes to the media queue will be reflected in clearView(). // A drag has started, but changes to the media queue will be reflected in clearView().
draggingFromPosition = fromPosition; draggingFromPosition = fromPosition;
@ -243,7 +243,7 @@ public class MainActivity extends AppCompatActivity
@Override @Override
public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) { public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
int position = viewHolder.getAdapterPosition(); int position = viewHolder.getBindingAdapterPosition();
QueueItemViewHolder queueItemHolder = (QueueItemViewHolder) viewHolder; QueueItemViewHolder queueItemHolder = (QueueItemViewHolder) viewHolder;
if (playerManager.removeItem(queueItemHolder.item)) { if (playerManager.removeItem(queueItemHolder.item)) {
mediaQueueListAdapter.notifyItemRemoved(position); mediaQueueListAdapter.notifyItemRemoved(position);
@ -282,7 +282,7 @@ public class MainActivity extends AppCompatActivity
@Override @Override
public void onClick(View v) { public void onClick(View v) {
playerManager.selectQueueItem(getAdapterPosition()); playerManager.selectQueueItem(getBindingAdapterPosition());
} }
} }

View file

@ -25,7 +25,7 @@ import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.Player.DiscontinuityReason; import com.google.android.exoplayer2.Player.DiscontinuityReason;
import com.google.android.exoplayer2.Player.TimelineChangeReason; import com.google.android.exoplayer2.Player.TimelineChangeReason;
import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.TracksInfo; import com.google.android.exoplayer2.Tracks;
import com.google.android.exoplayer2.ext.cast.CastPlayer; import com.google.android.exoplayer2.ext.cast.CastPlayer;
import com.google.android.exoplayer2.ext.cast.SessionAvailabilityListener; import com.google.android.exoplayer2.ext.cast.SessionAvailabilityListener;
import com.google.android.exoplayer2.ui.StyledPlayerControlView; import com.google.android.exoplayer2.ui.StyledPlayerControlView;
@ -57,7 +57,7 @@ import java.util.ArrayList;
private final ArrayList<MediaItem> mediaQueue; private final ArrayList<MediaItem> mediaQueue;
private final Listener listener; private final Listener listener;
private TracksInfo lastSeenTrackGroupInfo; private Tracks lastSeenTracks;
private int currentItemIndex; private int currentItemIndex;
private Player currentPlayer; private Player currentPlayer;
@ -219,19 +219,19 @@ import java.util.ArrayList;
} }
@Override @Override
public void onTracksInfoChanged(TracksInfo tracksInfo) { public void onTracksChanged(Tracks tracks) {
if (currentPlayer != localPlayer || tracksInfo == lastSeenTrackGroupInfo) { if (currentPlayer != localPlayer || tracks == lastSeenTracks) {
return; return;
} }
if (!tracksInfo.isTypeSupportedOrEmpty( if (tracks.containsType(C.TRACK_TYPE_VIDEO)
C.TRACK_TYPE_VIDEO, /* allowExceedsCapabilities= */ true)) { && !tracks.isTypeSupported(C.TRACK_TYPE_VIDEO, /* allowExceedsCapabilities= */ true)) {
listener.onUnsupportedTrack(C.TRACK_TYPE_VIDEO); listener.onUnsupportedTrack(C.TRACK_TYPE_VIDEO);
} }
if (!tracksInfo.isTypeSupportedOrEmpty( if (tracks.containsType(C.TRACK_TYPE_AUDIO)
C.TRACK_TYPE_AUDIO, /* allowExceedsCapabilities= */ true)) { && !tracks.isTypeSupported(C.TRACK_TYPE_AUDIO, /* allowExceedsCapabilities= */ true)) {
listener.onUnsupportedTrack(C.TRACK_TYPE_AUDIO); listener.onUnsupportedTrack(C.TRACK_TYPE_AUDIO);
} }
lastSeenTrackGroupInfo = tracksInfo; lastSeenTracks = tracks;
} }
// CastPlayer.SessionAvailabilityListener implementation. // CastPlayer.SessionAvailabilityListener implementation.

View file

@ -27,6 +27,7 @@ import android.graphics.drawable.BitmapDrawable;
import android.opengl.GLES20; import android.opengl.GLES20;
import android.opengl.GLUtils; import android.opengl.GLUtils;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.util.GlProgram;
import com.google.android.exoplayer2.util.GlUtil; import com.google.android.exoplayer2.util.GlUtil;
import java.io.IOException; import java.io.IOException;
import java.util.Locale; import java.util.Locale;
@ -50,7 +51,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
private final Bitmap logoBitmap; private final Bitmap logoBitmap;
private final Canvas overlayCanvas; private final Canvas overlayCanvas;
private GlUtil.@MonotonicNonNull Program program; private @MonotonicNonNull GlProgram program;
private float bitmapScaleX; private float bitmapScaleX;
private float bitmapScaleY; private float bitmapScaleY;
@ -78,7 +79,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
public void initialize() { public void initialize() {
try { try {
program = program =
new GlUtil.Program( new GlProgram(
context, context,
/* vertexShaderFilePath= */ "bitmap_overlay_video_processor_vertex.glsl", /* vertexShaderFilePath= */ "bitmap_overlay_video_processor_vertex.glsl",
/* fragmentShaderFilePath= */ "bitmap_overlay_video_processor_fragment.glsl"); /* fragmentShaderFilePath= */ "bitmap_overlay_video_processor_fragment.glsl");
@ -86,9 +87,13 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
throw new IllegalStateException(e); throw new IllegalStateException(e);
} }
program.setBufferAttribute( program.setBufferAttribute(
"aFramePosition", GlUtil.getNormalizedCoordinateBounds(), GlUtil.RECTANGLE_VERTICES_COUNT); "aFramePosition",
GlUtil.getNormalizedCoordinateBounds(),
GlUtil.HOMOGENEOUS_COORDINATE_VECTOR_SIZE);
program.setBufferAttribute( program.setBufferAttribute(
"aTexCoords", GlUtil.getTextureCoordinateBounds(), GlUtil.RECTANGLE_VERTICES_COUNT); "aTexCoords",
GlUtil.getTextureCoordinateBounds(),
GlUtil.HOMOGENEOUS_COORDINATE_VECTOR_SIZE);
GLES20.glGenTextures(1, textures, 0); GLES20.glGenTextures(1, textures, 0);
GLES20.glBindTexture(GL10.GL_TEXTURE_2D, textures[0]); GLES20.glBindTexture(GL10.GL_TEXTURE_2D, textures[0]);
GLES20.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_NEAREST); GLES20.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_NEAREST);
@ -117,9 +122,9 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
GlUtil.checkGlError(); GlUtil.checkGlError();
// Run the shader program. // Run the shader program.
GlUtil.Program program = checkNotNull(this.program); GlProgram program = checkNotNull(this.program);
program.setSamplerTexIdUniform("uTexSampler0", frameTexture, /* unit= */ 0); program.setSamplerTexIdUniform("uTexSampler0", frameTexture, /* texUnitIndex= */ 0);
program.setSamplerTexIdUniform("uTexSampler1", textures[0], /* unit= */ 1); program.setSamplerTexIdUniform("uTexSampler1", textures[0], /* texUnitIndex= */ 1);
program.setFloatUniform("uScaleX", bitmapScaleX); program.setFloatUniform("uScaleX", bitmapScaleX);
program.setFloatUniform("uScaleY", bitmapScaleY); program.setFloatUniform("uScaleY", bitmapScaleY);
program.setFloatsUniform("uTexTransform", transformMatrix); program.setFloatsUniform("uTexTransform", transformMatrix);

View file

@ -20,6 +20,7 @@ import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.net.Uri; import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.text.TextUtils;
import android.widget.FrameLayout; import android.widget.FrameLayout;
import android.widget.Toast; import android.widget.Toast;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
@ -38,7 +39,6 @@ import com.google.android.exoplayer2.ui.StyledPlayerView;
import com.google.android.exoplayer2.upstream.DataSource; import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.DefaultDataSource; import com.google.android.exoplayer2.upstream.DefaultDataSource;
import com.google.android.exoplayer2.upstream.DefaultHttpDataSource; import com.google.android.exoplayer2.upstream.DefaultHttpDataSource;
import com.google.android.exoplayer2.upstream.HttpDataSource;
import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.EventLogger; import com.google.android.exoplayer2.util.EventLogger;
import com.google.android.exoplayer2.util.GlUtil; import com.google.android.exoplayer2.util.GlUtil;
@ -144,7 +144,7 @@ public final class MainActivity extends Activity {
String drmScheme = Assertions.checkNotNull(intent.getStringExtra(DRM_SCHEME_EXTRA)); String drmScheme = Assertions.checkNotNull(intent.getStringExtra(DRM_SCHEME_EXTRA));
String drmLicenseUrl = Assertions.checkNotNull(intent.getStringExtra(DRM_LICENSE_URL_EXTRA)); String drmLicenseUrl = Assertions.checkNotNull(intent.getStringExtra(DRM_LICENSE_URL_EXTRA));
UUID drmSchemeUuid = Assertions.checkNotNull(Util.getDrmUuid(drmScheme)); UUID drmSchemeUuid = Assertions.checkNotNull(Util.getDrmUuid(drmScheme));
HttpDataSource.Factory licenseDataSourceFactory = new DefaultHttpDataSource.Factory(); DataSource.Factory licenseDataSourceFactory = new DefaultHttpDataSource.Factory();
HttpMediaDrmCallback drmCallback = HttpMediaDrmCallback drmCallback =
new HttpMediaDrmCallback(drmLicenseUrl, licenseDataSourceFactory); new HttpMediaDrmCallback(drmLicenseUrl, licenseDataSourceFactory);
drmSessionManager = drmSessionManager =
@ -157,13 +157,18 @@ public final class MainActivity extends Activity {
DataSource.Factory dataSourceFactory = new DefaultDataSource.Factory(this); DataSource.Factory dataSourceFactory = new DefaultDataSource.Factory(this);
MediaSource mediaSource; MediaSource mediaSource;
@C.ContentType int type = Util.inferContentType(uri, intent.getStringExtra(EXTENSION_EXTRA)); @Nullable String fileExtension = intent.getStringExtra(EXTENSION_EXTRA);
if (type == C.TYPE_DASH) { @C.ContentType
int type =
TextUtils.isEmpty(fileExtension)
? Util.inferContentType(uri)
: Util.inferContentTypeForExtension(fileExtension);
if (type == C.CONTENT_TYPE_DASH) {
mediaSource = mediaSource =
new DashMediaSource.Factory(dataSourceFactory) new DashMediaSource.Factory(dataSourceFactory)
.setDrmSessionManagerProvider(unusedMediaItem -> drmSessionManager) .setDrmSessionManagerProvider(unusedMediaItem -> drmSessionManager)
.createMediaSource(MediaItem.fromUri(uri)); .createMediaSource(MediaItem.fromUri(uri));
} else if (type == C.TYPE_OTHER) { } else if (type == C.CONTENT_TYPE_OTHER) {
mediaSource = mediaSource =
new ProgressiveMediaSource.Factory(dataSourceFactory) new ProgressiveMediaSource.Factory(dataSourceFactory)
.setDrmSessionManagerProvider(unusedMediaItem -> drmSessionManager) .setDrmSessionManagerProvider(unusedMediaItem -> drmSessionManager)
@ -181,7 +186,7 @@ public final class MainActivity extends Activity {
Assertions.checkNotNull(this.videoProcessingGLSurfaceView); Assertions.checkNotNull(this.videoProcessingGLSurfaceView);
videoProcessingGLSurfaceView.setPlayer(player); videoProcessingGLSurfaceView.setPlayer(player);
Assertions.checkNotNull(playerView).setPlayer(player); Assertions.checkNotNull(playerView).setPlayer(player);
player.addAnalyticsListener(new EventLogger(/* trackSelector= */ null)); player.addAnalyticsListener(new EventLogger());
this.player = player; this.player = player;
} }

View file

@ -27,7 +27,6 @@ import com.google.android.exoplayer2.ui.DownloadNotificationHelper;
import com.google.android.exoplayer2.upstream.DataSource; import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.DefaultDataSource; import com.google.android.exoplayer2.upstream.DefaultDataSource;
import com.google.android.exoplayer2.upstream.DefaultHttpDataSource; import com.google.android.exoplayer2.upstream.DefaultHttpDataSource;
import com.google.android.exoplayer2.upstream.HttpDataSource;
import com.google.android.exoplayer2.upstream.cache.Cache; import com.google.android.exoplayer2.upstream.cache.Cache;
import com.google.android.exoplayer2.upstream.cache.CacheDataSource; import com.google.android.exoplayer2.upstream.cache.CacheDataSource;
import com.google.android.exoplayer2.upstream.cache.NoOpCacheEvictor; import com.google.android.exoplayer2.upstream.cache.NoOpCacheEvictor;
@ -59,7 +58,7 @@ public final class DemoUtil {
private static final String DOWNLOAD_CONTENT_DIRECTORY = "downloads"; private static final String DOWNLOAD_CONTENT_DIRECTORY = "downloads";
private static DataSource.@MonotonicNonNull Factory dataSourceFactory; private static DataSource.@MonotonicNonNull Factory dataSourceFactory;
private static HttpDataSource.@MonotonicNonNull Factory httpDataSourceFactory; private static DataSource.@MonotonicNonNull Factory httpDataSourceFactory;
private static @MonotonicNonNull DatabaseProvider databaseProvider; private static @MonotonicNonNull DatabaseProvider databaseProvider;
private static @MonotonicNonNull File downloadDirectory; private static @MonotonicNonNull File downloadDirectory;
private static @MonotonicNonNull Cache downloadCache; private static @MonotonicNonNull Cache downloadCache;
@ -85,7 +84,7 @@ public final class DemoUtil {
.setExtensionRendererMode(extensionRendererMode); .setExtensionRendererMode(extensionRendererMode);
} }
public static synchronized HttpDataSource.Factory getHttpDataSourceFactory(Context context) { public static synchronized DataSource.Factory getHttpDataSourceFactory(Context context) {
if (httpDataSourceFactory == null) { if (httpDataSourceFactory == null) {
if (USE_CRONET_FOR_NETWORKING) { if (USE_CRONET_FOR_NETWORKING) {
context = context.getApplicationContext(); context = context.getApplicationContext();

View file

@ -15,8 +15,7 @@
*/ */
package com.google.android.exoplayer2.demo; package com.google.android.exoplayer2.demo;
import static com.google.android.exoplayer2.util.Assertions.checkNotNull; import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.android.exoplayer2.util.Assertions.checkStateNotNull;
import android.content.Context; import android.content.Context;
import android.content.DialogInterface; import android.content.DialogInterface;
@ -29,6 +28,7 @@ import androidx.fragment.app.FragmentManager;
import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.MediaItem; import com.google.android.exoplayer2.MediaItem;
import com.google.android.exoplayer2.RenderersFactory; import com.google.android.exoplayer2.RenderersFactory;
import com.google.android.exoplayer2.Tracks;
import com.google.android.exoplayer2.drm.DrmInitData; import com.google.android.exoplayer2.drm.DrmInitData;
import com.google.android.exoplayer2.drm.DrmSession; import com.google.android.exoplayer2.drm.DrmSession;
import com.google.android.exoplayer2.drm.DrmSessionEventListener; import com.google.android.exoplayer2.drm.DrmSessionEventListener;
@ -43,9 +43,9 @@ import com.google.android.exoplayer2.offline.DownloadRequest;
import com.google.android.exoplayer2.offline.DownloadService; import com.google.android.exoplayer2.offline.DownloadService;
import com.google.android.exoplayer2.source.TrackGroup; import com.google.android.exoplayer2.source.TrackGroup;
import com.google.android.exoplayer2.source.TrackGroupArray; import com.google.android.exoplayer2.source.TrackGroupArray;
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
import com.google.android.exoplayer2.trackselection.MappingTrackSelector.MappedTrackInfo; import com.google.android.exoplayer2.trackselection.MappingTrackSelector.MappedTrackInfo;
import com.google.android.exoplayer2.upstream.HttpDataSource; import com.google.android.exoplayer2.trackselection.TrackSelectionParameters;
import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.util.Log; import com.google.android.exoplayer2.util.Log;
import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.util.Util;
import java.io.IOException; import java.io.IOException;
@ -65,31 +65,26 @@ public class DownloadTracker {
private static final String TAG = "DownloadTracker"; private static final String TAG = "DownloadTracker";
private final Context context; private final Context context;
private final HttpDataSource.Factory httpDataSourceFactory; private final DataSource.Factory dataSourceFactory;
private final CopyOnWriteArraySet<Listener> listeners; private final CopyOnWriteArraySet<Listener> listeners;
private final HashMap<Uri, Download> downloads; private final HashMap<Uri, Download> downloads;
private final DownloadIndex downloadIndex; private final DownloadIndex downloadIndex;
private final DefaultTrackSelector.Parameters trackSelectorParameters;
@Nullable private StartDownloadDialogHelper startDownloadDialogHelper; @Nullable private StartDownloadDialogHelper startDownloadDialogHelper;
public DownloadTracker( public DownloadTracker(
Context context, Context context, DataSource.Factory dataSourceFactory, DownloadManager downloadManager) {
HttpDataSource.Factory httpDataSourceFactory,
DownloadManager downloadManager) {
this.context = context.getApplicationContext(); this.context = context.getApplicationContext();
this.httpDataSourceFactory = httpDataSourceFactory; this.dataSourceFactory = dataSourceFactory;
listeners = new CopyOnWriteArraySet<>(); listeners = new CopyOnWriteArraySet<>();
downloads = new HashMap<>(); downloads = new HashMap<>();
downloadIndex = downloadManager.getDownloadIndex(); downloadIndex = downloadManager.getDownloadIndex();
trackSelectorParameters = DownloadHelper.getDefaultTrackSelectorParameters(context);
downloadManager.addListener(new DownloadManagerListener()); downloadManager.addListener(new DownloadManagerListener());
loadDownloads(); loadDownloads();
} }
public void addListener(Listener listener) { public void addListener(Listener listener) {
checkNotNull(listener); listeners.add(checkNotNull(listener));
listeners.add(listener);
} }
public void removeListener(Listener listener) { public void removeListener(Listener listener) {
@ -120,8 +115,7 @@ public class DownloadTracker {
startDownloadDialogHelper = startDownloadDialogHelper =
new StartDownloadDialogHelper( new StartDownloadDialogHelper(
fragmentManager, fragmentManager,
DownloadHelper.forMediaItem( DownloadHelper.forMediaItem(context, mediaItem, renderersFactory, dataSourceFactory),
context, mediaItem, renderersFactory, httpDataSourceFactory),
mediaItem); mediaItem);
} }
} }
@ -159,7 +153,7 @@ public class DownloadTracker {
private final class StartDownloadDialogHelper private final class StartDownloadDialogHelper
implements DownloadHelper.Callback, implements DownloadHelper.Callback,
DialogInterface.OnClickListener, TrackSelectionDialog.TrackSelectionListener,
DialogInterface.OnDismissListener { DialogInterface.OnDismissListener {
private final FragmentManager fragmentManager; private final FragmentManager fragmentManager;
@ -167,7 +161,6 @@ public class DownloadTracker {
private final MediaItem mediaItem; private final MediaItem mediaItem;
private TrackSelectionDialog trackSelectionDialog; private TrackSelectionDialog trackSelectionDialog;
private MappedTrackInfo mappedTrackInfo;
private WidevineOfflineLicenseFetchTask widevineOfflineLicenseFetchTask; private WidevineOfflineLicenseFetchTask widevineOfflineLicenseFetchTask;
@Nullable private byte[] keySetId; @Nullable private byte[] keySetId;
@ -220,7 +213,7 @@ public class DownloadTracker {
new WidevineOfflineLicenseFetchTask( new WidevineOfflineLicenseFetchTask(
format, format,
mediaItem.localConfiguration.drmConfiguration, mediaItem.localConfiguration.drmConfiguration,
httpDataSourceFactory, dataSourceFactory,
/* dialogHelper= */ this, /* dialogHelper= */ this,
helper); helper);
widevineOfflineLicenseFetchTask.execute(); widevineOfflineLicenseFetchTask.execute();
@ -237,21 +230,13 @@ public class DownloadTracker {
Log.e(TAG, logMessage, e); Log.e(TAG, logMessage, e);
} }
// DialogInterface.OnClickListener implementation. // TrackSelectionListener implementation.
@Override @Override
public void onClick(DialogInterface dialog, int which) { public void onTracksSelected(TrackSelectionParameters trackSelectionParameters) {
for (int periodIndex = 0; periodIndex < downloadHelper.getPeriodCount(); periodIndex++) { for (int periodIndex = 0; periodIndex < downloadHelper.getPeriodCount(); periodIndex++) {
downloadHelper.clearTrackSelections(periodIndex); downloadHelper.clearTrackSelections(periodIndex);
for (int i = 0; i < mappedTrackInfo.getRendererCount(); i++) { downloadHelper.addTrackSelection(periodIndex, trackSelectionParameters);
if (!trackSelectionDialog.getIsDisabled(/* rendererIndex= */ i)) {
downloadHelper.addTrackSelectionForSingleRenderer(
periodIndex,
/* rendererIndex= */ i,
trackSelectorParameters,
trackSelectionDialog.getOverrides(/* rendererIndex= */ i));
}
}
} }
DownloadRequest downloadRequest = buildDownloadRequest(); DownloadRequest downloadRequest = buildDownloadRequest();
if (downloadRequest.streamKeys.isEmpty()) { if (downloadRequest.streamKeys.isEmpty()) {
@ -316,21 +301,21 @@ public class DownloadTracker {
return; return;
} }
mappedTrackInfo = downloadHelper.getMappedTrackInfo(/* periodIndex= */ 0); Tracks tracks = downloadHelper.getTracks(/* periodIndex= */ 0);
if (!TrackSelectionDialog.willHaveContent(mappedTrackInfo)) { if (!TrackSelectionDialog.willHaveContent(tracks)) {
Log.d(TAG, "No dialog content. Downloading entire stream."); Log.d(TAG, "No dialog content. Downloading entire stream.");
startDownload(); startDownload();
downloadHelper.release(); downloadHelper.release();
return; return;
} }
trackSelectionDialog = trackSelectionDialog =
TrackSelectionDialog.createForMappedTrackInfoAndParameters( TrackSelectionDialog.createForTracksAndParameters(
/* titleId= */ R.string.exo_download_description, /* titleId= */ R.string.exo_download_description,
mappedTrackInfo, tracks,
trackSelectorParameters, DownloadHelper.getDefaultTrackSelectorParameters(context),
/* allowAdaptiveSelections= */ false, /* allowAdaptiveSelections= */ false,
/* allowMultipleOverrides= */ true, /* allowMultipleOverrides= */ true,
/* onClickListener= */ this, /* onTracksSelectedListener= */ this,
/* onDismissListener= */ this); /* onDismissListener= */ this);
trackSelectionDialog.show(fragmentManager, /* tag= */ null); trackSelectionDialog.show(fragmentManager, /* tag= */ null);
} }
@ -371,7 +356,7 @@ public class DownloadTracker {
private final Format format; private final Format format;
private final MediaItem.DrmConfiguration drmConfiguration; private final MediaItem.DrmConfiguration drmConfiguration;
private final HttpDataSource.Factory httpDataSourceFactory; private final DataSource.Factory dataSourceFactory;
private final StartDownloadDialogHelper dialogHelper; private final StartDownloadDialogHelper dialogHelper;
private final DownloadHelper downloadHelper; private final DownloadHelper downloadHelper;
@ -381,12 +366,12 @@ public class DownloadTracker {
public WidevineOfflineLicenseFetchTask( public WidevineOfflineLicenseFetchTask(
Format format, Format format,
MediaItem.DrmConfiguration drmConfiguration, MediaItem.DrmConfiguration drmConfiguration,
HttpDataSource.Factory httpDataSourceFactory, DataSource.Factory dataSourceFactory,
StartDownloadDialogHelper dialogHelper, StartDownloadDialogHelper dialogHelper,
DownloadHelper downloadHelper) { DownloadHelper downloadHelper) {
this.format = format; this.format = format;
this.drmConfiguration = drmConfiguration; this.drmConfiguration = drmConfiguration;
this.httpDataSourceFactory = httpDataSourceFactory; this.dataSourceFactory = dataSourceFactory;
this.dialogHelper = dialogHelper; this.dialogHelper = dialogHelper;
this.downloadHelper = downloadHelper; this.downloadHelper = downloadHelper;
} }
@ -397,7 +382,7 @@ public class DownloadTracker {
OfflineLicenseHelper.newWidevineInstance( OfflineLicenseHelper.newWidevineInstance(
drmConfiguration.licenseUri.toString(), drmConfiguration.licenseUri.toString(),
drmConfiguration.forceDefaultLicenseUri, drmConfiguration.forceDefaultLicenseUri,
httpDataSourceFactory, dataSourceFactory,
drmConfiguration.licenseRequestHeaders, drmConfiguration.licenseRequestHeaders,
new DrmSessionEventListener.EventDispatcher()); new DrmSessionEventListener.EventDispatcher());
try { try {
@ -415,7 +400,7 @@ public class DownloadTracker {
if (drmSessionException != null) { if (drmSessionException != null) {
dialogHelper.onOfflineLicenseFetchedError(drmSessionException); dialogHelper.onOfflineLicenseFetchedError(drmSessionException);
} else { } else {
dialogHelper.onOfflineLicenseFetched(downloadHelper, checkStateNotNull(keySetId)); dialogHelper.onOfflineLicenseFetched(downloadHelper, checkNotNull(keySetId));
} }
} }
} }

View file

@ -15,8 +15,9 @@
*/ */
package com.google.android.exoplayer2.demo; package com.google.android.exoplayer2.demo;
import static com.google.android.exoplayer2.util.Assertions.checkNotNull; import static com.google.common.base.Preconditions.checkArgument;
import static com.google.android.exoplayer2.util.Assertions.checkState; import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import android.content.Intent; import android.content.Intent;
import android.net.Uri; import android.net.Uri;
@ -26,7 +27,6 @@ import com.google.android.exoplayer2.MediaItem;
import com.google.android.exoplayer2.MediaItem.ClippingConfiguration; import com.google.android.exoplayer2.MediaItem.ClippingConfiguration;
import com.google.android.exoplayer2.MediaItem.SubtitleConfiguration; import com.google.android.exoplayer2.MediaItem.SubtitleConfiguration;
import com.google.android.exoplayer2.MediaMetadata; import com.google.android.exoplayer2.MediaMetadata;
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.util.Util;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import java.util.ArrayList; import java.util.ArrayList;
@ -87,7 +87,7 @@ public class IntentUtil {
/** Populates the intent with the given list of {@link MediaItem media items}. */ /** Populates the intent with the given list of {@link MediaItem media items}. */
public static void addToIntent(List<MediaItem> mediaItems, Intent intent) { public static void addToIntent(List<MediaItem> mediaItems, Intent intent) {
Assertions.checkArgument(!mediaItems.isEmpty()); checkArgument(!mediaItems.isEmpty());
if (mediaItems.size() == 1) { if (mediaItems.size() == 1) {
MediaItem mediaItem = mediaItems.get(0); MediaItem mediaItem = mediaItems.get(0);
MediaItem.LocalConfiguration localConfiguration = checkNotNull(mediaItem.localConfiguration); MediaItem.LocalConfiguration localConfiguration = checkNotNull(mediaItem.localConfiguration);
@ -178,7 +178,7 @@ public class IntentUtil {
headers.put(keyRequestPropertiesArray[i], keyRequestPropertiesArray[i + 1]); headers.put(keyRequestPropertiesArray[i], keyRequestPropertiesArray[i + 1]);
} }
} }
@Nullable UUID drmUuid = Util.getDrmUuid(Util.castNonNull(drmSchemeExtra)); @Nullable UUID drmUuid = Util.getDrmUuid(drmSchemeExtra);
if (drmUuid != null) { if (drmUuid != null) {
builder.setDrmConfiguration( builder.setDrmConfiguration(
new MediaItem.DrmConfiguration.Builder(drmUuid) new MediaItem.DrmConfiguration.Builder(drmUuid)
@ -189,7 +189,7 @@ public class IntentUtil {
intent.getBooleanExtra( intent.getBooleanExtra(
DRM_FORCE_DEFAULT_LICENSE_URI_EXTRA + extrasKeySuffix, false)) DRM_FORCE_DEFAULT_LICENSE_URI_EXTRA + extrasKeySuffix, false))
.setLicenseRequestHeaders(headers) .setLicenseRequestHeaders(headers)
.forceSessionsForAudioAndVideoTracks( .setForceSessionsForAudioAndVideoTracks(
intent.getBooleanExtra(DRM_SESSION_FOR_CLEAR_CONTENT + extrasKeySuffix, false)) intent.getBooleanExtra(DRM_SESSION_FOR_CLEAR_CONTENT + extrasKeySuffix, false))
.build()); .build());
} }
@ -242,7 +242,7 @@ public class IntentUtil {
drmConfiguration.forcedSessionTrackTypes; drmConfiguration.forcedSessionTrackTypes;
if (!forcedDrmSessionTrackTypes.isEmpty()) { if (!forcedDrmSessionTrackTypes.isEmpty()) {
// Only video and audio together are supported. // Only video and audio together are supported.
Assertions.checkState( checkState(
forcedDrmSessionTrackTypes.size() == 2 forcedDrmSessionTrackTypes.size() == 2
&& forcedDrmSessionTrackTypes.contains(C.TRACK_TYPE_VIDEO) && forcedDrmSessionTrackTypes.contains(C.TRACK_TYPE_VIDEO)
&& forcedDrmSessionTrackTypes.contains(C.TRACK_TYPE_AUDIO)); && forcedDrmSessionTrackTypes.contains(C.TRACK_TYPE_AUDIO));

View file

@ -17,6 +17,7 @@ package com.google.android.exoplayer2.demo;
import android.content.Intent; import android.content.Intent;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.util.Pair; import android.util.Pair;
import android.view.KeyEvent; import android.view.KeyEvent;
@ -34,8 +35,9 @@ import com.google.android.exoplayer2.MediaItem;
import com.google.android.exoplayer2.PlaybackException; import com.google.android.exoplayer2.PlaybackException;
import com.google.android.exoplayer2.Player; import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.RenderersFactory; import com.google.android.exoplayer2.RenderersFactory;
import com.google.android.exoplayer2.TracksInfo; import com.google.android.exoplayer2.Tracks;
import com.google.android.exoplayer2.audio.AudioAttributes; import com.google.android.exoplayer2.audio.AudioAttributes;
import com.google.android.exoplayer2.drm.DefaultDrmSessionManagerProvider;
import com.google.android.exoplayer2.drm.FrameworkMediaDrm; import com.google.android.exoplayer2.drm.FrameworkMediaDrm;
import com.google.android.exoplayer2.ext.ima.ImaAdsLoader; import com.google.android.exoplayer2.ext.ima.ImaAdsLoader;
import com.google.android.exoplayer2.ext.ima.ImaServerSideAdInsertionMediaSource; import com.google.android.exoplayer2.ext.ima.ImaServerSideAdInsertionMediaSource;
@ -45,8 +47,7 @@ import com.google.android.exoplayer2.offline.DownloadRequest;
import com.google.android.exoplayer2.source.DefaultMediaSourceFactory; import com.google.android.exoplayer2.source.DefaultMediaSourceFactory;
import com.google.android.exoplayer2.source.MediaSource; import com.google.android.exoplayer2.source.MediaSource;
import com.google.android.exoplayer2.source.ads.AdsLoader; import com.google.android.exoplayer2.source.ads.AdsLoader;
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector; import com.google.android.exoplayer2.trackselection.TrackSelectionParameters;
import com.google.android.exoplayer2.ui.StyledPlayerControlView;
import com.google.android.exoplayer2.ui.StyledPlayerView; import com.google.android.exoplayer2.ui.StyledPlayerView;
import com.google.android.exoplayer2.upstream.DataSource; import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.util.DebugTextViewHelper; import com.google.android.exoplayer2.util.DebugTextViewHelper;
@ -60,7 +61,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
/** An activity that plays media using {@link ExoPlayer}. */ /** An activity that plays media using {@link ExoPlayer}. */
public class PlayerActivity extends AppCompatActivity public class PlayerActivity extends AppCompatActivity
implements OnClickListener, StyledPlayerControlView.VisibilityListener { implements OnClickListener, StyledPlayerView.ControllerVisibilityListener {
// Saved instance state keys. // Saved instance state keys.
@ -79,10 +80,9 @@ public class PlayerActivity extends AppCompatActivity
private Button selectTracksButton; private Button selectTracksButton;
private DataSource.Factory dataSourceFactory; private DataSource.Factory dataSourceFactory;
private List<MediaItem> mediaItems; private List<MediaItem> mediaItems;
private DefaultTrackSelector trackSelector; private TrackSelectionParameters trackSelectionParameters;
private DefaultTrackSelector.Parameters trackSelectionParameters;
private DebugTextViewHelper debugViewHelper; private DebugTextViewHelper debugViewHelper;
private TracksInfo lastSeenTracksInfo; private Tracks lastSeenTracks;
private boolean startAutoPlay; private boolean startAutoPlay;
private int startItemIndex; private int startItemIndex;
private long startPosition; private long startPosition;
@ -90,7 +90,12 @@ public class PlayerActivity extends AppCompatActivity
// For ad playback only. // For ad playback only.
@Nullable private AdsLoader clientSideAdsLoader; @Nullable private AdsLoader clientSideAdsLoader;
// TODO: Annotate this and serverSideAdsLoaderState below with @OptIn when it can be applied to
// fields (needs http://r.android.com/2004032 to be released into a version of
// androidx.annotation:annotation-experimental).
@Nullable private ImaServerSideAdInsertionMediaSource.AdsLoader serverSideAdsLoader; @Nullable private ImaServerSideAdInsertionMediaSource.AdsLoader serverSideAdsLoader;
private ImaServerSideAdInsertionMediaSource.AdsLoader.@MonotonicNonNull State private ImaServerSideAdInsertionMediaSource.AdsLoader.@MonotonicNonNull State
serverSideAdsLoaderState; serverSideAdsLoaderState;
@ -113,22 +118,15 @@ public class PlayerActivity extends AppCompatActivity
playerView.requestFocus(); playerView.requestFocus();
if (savedInstanceState != null) { if (savedInstanceState != null) {
// Restore as DefaultTrackSelector.Parameters in case ExoPlayer specific parameters were set.
trackSelectionParameters = trackSelectionParameters =
DefaultTrackSelector.Parameters.CREATOR.fromBundle( TrackSelectionParameters.fromBundle(
savedInstanceState.getBundle(KEY_TRACK_SELECTION_PARAMETERS)); savedInstanceState.getBundle(KEY_TRACK_SELECTION_PARAMETERS));
startAutoPlay = savedInstanceState.getBoolean(KEY_AUTO_PLAY); startAutoPlay = savedInstanceState.getBoolean(KEY_AUTO_PLAY);
startItemIndex = savedInstanceState.getInt(KEY_ITEM_INDEX); startItemIndex = savedInstanceState.getInt(KEY_ITEM_INDEX);
startPosition = savedInstanceState.getLong(KEY_POSITION); startPosition = savedInstanceState.getLong(KEY_POSITION);
Bundle adsLoaderStateBundle = savedInstanceState.getBundle(KEY_SERVER_SIDE_ADS_LOADER_STATE); restoreServerSideAdsLoaderState(savedInstanceState);
if (adsLoaderStateBundle != null) {
serverSideAdsLoaderState =
ImaServerSideAdInsertionMediaSource.AdsLoader.State.CREATOR.fromBundle(
adsLoaderStateBundle);
}
} else { } else {
trackSelectionParameters = trackSelectionParameters = new TrackSelectionParameters.Builder(/* context= */ this).build();
new DefaultTrackSelector.ParametersBuilder(/* context= */ this).build();
clearStartPosition(); clearStartPosition();
} }
} }
@ -145,7 +143,7 @@ public class PlayerActivity extends AppCompatActivity
@Override @Override
public void onStart() { public void onStart() {
super.onStart(); super.onStart();
if (Util.SDK_INT > 23) { if (Build.VERSION.SDK_INT > 23) {
initializePlayer(); initializePlayer();
if (playerView != null) { if (playerView != null) {
playerView.onResume(); playerView.onResume();
@ -156,7 +154,7 @@ public class PlayerActivity extends AppCompatActivity
@Override @Override
public void onResume() { public void onResume() {
super.onResume(); super.onResume();
if (Util.SDK_INT <= 23 || player == null) { if (Build.VERSION.SDK_INT <= 23 || player == null) {
initializePlayer(); initializePlayer();
if (playerView != null) { if (playerView != null) {
playerView.onResume(); playerView.onResume();
@ -167,7 +165,7 @@ public class PlayerActivity extends AppCompatActivity
@Override @Override
public void onPause() { public void onPause() {
super.onPause(); super.onPause();
if (Util.SDK_INT <= 23) { if (Build.VERSION.SDK_INT <= 23) {
if (playerView != null) { if (playerView != null) {
playerView.onPause(); playerView.onPause();
} }
@ -178,7 +176,7 @@ public class PlayerActivity extends AppCompatActivity
@Override @Override
public void onStop() { public void onStop() {
super.onStop(); super.onStop();
if (Util.SDK_INT > 23) { if (Build.VERSION.SDK_INT > 23) {
if (playerView != null) { if (playerView != null) {
playerView.onPause(); playerView.onPause();
} }
@ -218,9 +216,7 @@ public class PlayerActivity extends AppCompatActivity
outState.putBoolean(KEY_AUTO_PLAY, startAutoPlay); outState.putBoolean(KEY_AUTO_PLAY, startAutoPlay);
outState.putInt(KEY_ITEM_INDEX, startItemIndex); outState.putInt(KEY_ITEM_INDEX, startItemIndex);
outState.putLong(KEY_POSITION, startPosition); outState.putLong(KEY_POSITION, startPosition);
if (serverSideAdsLoaderState != null) { saveServerSideAdsLoaderState(outState);
outState.putBundle(KEY_SERVER_SIDE_ADS_LOADER_STATE, serverSideAdsLoaderState.toBundle());
}
} }
// Activity input // Activity input
@ -237,20 +233,20 @@ public class PlayerActivity extends AppCompatActivity
public void onClick(View view) { public void onClick(View view) {
if (view == selectTracksButton if (view == selectTracksButton
&& !isShowingTrackSelectionDialog && !isShowingTrackSelectionDialog
&& TrackSelectionDialog.willHaveContent(trackSelector)) { && TrackSelectionDialog.willHaveContent(player)) {
isShowingTrackSelectionDialog = true; isShowingTrackSelectionDialog = true;
TrackSelectionDialog trackSelectionDialog = TrackSelectionDialog trackSelectionDialog =
TrackSelectionDialog.createForTrackSelector( TrackSelectionDialog.createForPlayer(
trackSelector, player,
/* onDismissListener= */ dismissedDialog -> isShowingTrackSelectionDialog = false); /* onDismissListener= */ dismissedDialog -> isShowingTrackSelectionDialog = false);
trackSelectionDialog.show(getSupportFragmentManager(), /* tag= */ null); trackSelectionDialog.show(getSupportFragmentManager(), /* tag= */ null);
} }
} }
// StyledPlayerControlView.VisibilityListener implementation // StyledPlayerView.ControllerVisibilityListener implementation
@Override @Override
public void onVisibilityChange(int visibility) { public void onVisibilityChanged(int visibility) {
debugRootView.setVisibility(visibility); debugRootView.setVisibility(visibility);
} }
@ -260,7 +256,9 @@ public class PlayerActivity extends AppCompatActivity
setContentView(R.layout.player_activity); setContentView(R.layout.player_activity);
} }
/** @return Whether initialization was successful. */ /**
* @return Whether initialization was successful.
*/
protected boolean initializePlayer() { protected boolean initializePlayer() {
if (player == null) { if (player == null) {
Intent intent = getIntent(); Intent intent = getIntent();
@ -270,26 +268,20 @@ public class PlayerActivity extends AppCompatActivity
return false; return false;
} }
boolean preferExtensionDecoders = lastSeenTracks = Tracks.EMPTY;
intent.getBooleanExtra(IntentUtil.PREFER_EXTENSION_DECODERS_EXTRA, false); ExoPlayer.Builder playerBuilder =
RenderersFactory renderersFactory =
DemoUtil.buildRenderersFactory(/* context= */ this, preferExtensionDecoders);
trackSelector = new DefaultTrackSelector(/* context= */ this);
lastSeenTracksInfo = TracksInfo.EMPTY;
player =
new ExoPlayer.Builder(/* context= */ this) new ExoPlayer.Builder(/* context= */ this)
.setRenderersFactory(renderersFactory) .setMediaSourceFactory(createMediaSourceFactory());
.setMediaSourceFactory(createMediaSourceFactory()) setRenderersFactory(
.setTrackSelector(trackSelector) playerBuilder, intent.getBooleanExtra(IntentUtil.PREFER_EXTENSION_DECODERS_EXTRA, false));
.build(); player = playerBuilder.build();
player.setTrackSelectionParameters(trackSelectionParameters); player.setTrackSelectionParameters(trackSelectionParameters);
player.addListener(new PlayerEventListener()); player.addListener(new PlayerEventListener());
player.addAnalyticsListener(new EventLogger(trackSelector)); player.addAnalyticsListener(new EventLogger());
player.setAudioAttributes(AudioAttributes.DEFAULT, /* handleAudioFocus= */ true); player.setAudioAttributes(AudioAttributes.DEFAULT, /* handleAudioFocus= */ true);
player.setPlayWhenReady(startAutoPlay); player.setPlayWhenReady(startAutoPlay);
playerView.setPlayer(player); playerView.setPlayer(player);
serverSideAdsLoader.setPlayer(player); configurePlayerWithServerSideAdsLoader();
debugViewHelper = new DebugTextViewHelper(player, debugTextView); debugViewHelper = new DebugTextViewHelper(player, debugTextView);
debugViewHelper.start(); debugViewHelper.start();
} }
@ -304,6 +296,10 @@ public class PlayerActivity extends AppCompatActivity
} }
private MediaSource.Factory createMediaSourceFactory() { private MediaSource.Factory createMediaSourceFactory() {
DefaultDrmSessionManagerProvider drmSessionManagerProvider =
new DefaultDrmSessionManagerProvider();
drmSessionManagerProvider.setDrmHttpDataSourceFactory(
DemoUtil.getHttpDataSourceFactory(/* context= */ this));
ImaServerSideAdInsertionMediaSource.AdsLoader.Builder serverSideAdLoaderBuilder = ImaServerSideAdInsertionMediaSource.AdsLoader.Builder serverSideAdLoaderBuilder =
new ImaServerSideAdInsertionMediaSource.AdsLoader.Builder(/* context= */ this, playerView); new ImaServerSideAdInsertionMediaSource.AdsLoader.Builder(/* context= */ this, playerView);
if (serverSideAdsLoaderState != null) { if (serverSideAdsLoaderState != null) {
@ -312,13 +308,28 @@ public class PlayerActivity extends AppCompatActivity
serverSideAdsLoader = serverSideAdLoaderBuilder.build(); serverSideAdsLoader = serverSideAdLoaderBuilder.build();
ImaServerSideAdInsertionMediaSource.Factory imaServerSideAdInsertionMediaSourceFactory = ImaServerSideAdInsertionMediaSource.Factory imaServerSideAdInsertionMediaSourceFactory =
new ImaServerSideAdInsertionMediaSource.Factory( new ImaServerSideAdInsertionMediaSource.Factory(
serverSideAdsLoader, new DefaultMediaSourceFactory(dataSourceFactory)); serverSideAdsLoader,
return new DefaultMediaSourceFactory(dataSourceFactory) new DefaultMediaSourceFactory(/* context= */ this)
.setAdsLoaderProvider(this::getClientSideAdsLoader) .setDataSourceFactory(dataSourceFactory));
.setAdViewProvider(playerView) return new DefaultMediaSourceFactory(/* context= */ this)
.setDataSourceFactory(dataSourceFactory)
.setDrmSessionManagerProvider(drmSessionManagerProvider)
.setLocalAdInsertionComponents(
this::getClientSideAdsLoader, /* adViewProvider= */ playerView)
.setServerSideAdInsertionMediaSourceFactory(imaServerSideAdInsertionMediaSourceFactory); .setServerSideAdInsertionMediaSourceFactory(imaServerSideAdInsertionMediaSourceFactory);
} }
private void setRenderersFactory(
ExoPlayer.Builder playerBuilder, boolean preferExtensionDecoders) {
RenderersFactory renderersFactory =
DemoUtil.buildRenderersFactory(/* context= */ this, preferExtensionDecoders);
playerBuilder.setRenderersFactory(renderersFactory);
}
private void configurePlayerWithServerSideAdsLoader() {
serverSideAdsLoader.setPlayer(player);
}
private List<MediaItem> createMediaItems(Intent intent) { private List<MediaItem> createMediaItems(Intent intent) {
String action = intent.getAction(); String action = intent.getAction();
boolean actionIsListView = IntentUtil.ACTION_VIEW_LIST.equals(action); boolean actionIsListView = IntentUtil.ACTION_VIEW_LIST.equals(action);
@ -345,7 +356,7 @@ public class PlayerActivity extends AppCompatActivity
MediaItem.DrmConfiguration drmConfiguration = mediaItem.localConfiguration.drmConfiguration; MediaItem.DrmConfiguration drmConfiguration = mediaItem.localConfiguration.drmConfiguration;
if (drmConfiguration != null) { if (drmConfiguration != null) {
if (Util.SDK_INT < 18) { if (Build.VERSION.SDK_INT < 18) {
showToast(R.string.error_drm_unsupported_before_api_18); showToast(R.string.error_drm_unsupported_before_api_18);
finish(); finish();
return Collections.emptyList(); return Collections.emptyList();
@ -372,8 +383,7 @@ public class PlayerActivity extends AppCompatActivity
if (player != null) { if (player != null) {
updateTrackSelectorParameters(); updateTrackSelectorParameters();
updateStartPosition(); updateStartPosition();
serverSideAdsLoaderState = serverSideAdsLoader.release(); releaseServerSideAdsLoader();
serverSideAdsLoader = null;
debugViewHelper.stop(); debugViewHelper.stop();
debugViewHelper = null; debugViewHelper = null;
player.release(); player.release();
@ -388,6 +398,11 @@ public class PlayerActivity extends AppCompatActivity
} }
} }
private void releaseServerSideAdsLoader() {
serverSideAdsLoaderState = serverSideAdsLoader.release();
serverSideAdsLoader = null;
}
private void releaseClientSideAdsLoader() { private void releaseClientSideAdsLoader() {
if (clientSideAdsLoader != null) { if (clientSideAdsLoader != null) {
clientSideAdsLoader.release(); clientSideAdsLoader.release();
@ -396,12 +411,24 @@ public class PlayerActivity extends AppCompatActivity
} }
} }
private void saveServerSideAdsLoaderState(Bundle outState) {
if (serverSideAdsLoaderState != null) {
outState.putBundle(KEY_SERVER_SIDE_ADS_LOADER_STATE, serverSideAdsLoaderState.toBundle());
}
}
private void restoreServerSideAdsLoaderState(Bundle savedInstanceState) {
Bundle adsLoaderStateBundle = savedInstanceState.getBundle(KEY_SERVER_SIDE_ADS_LOADER_STATE);
if (adsLoaderStateBundle != null) {
serverSideAdsLoaderState =
ImaServerSideAdInsertionMediaSource.AdsLoader.State.CREATOR.fromBundle(
adsLoaderStateBundle);
}
}
private void updateTrackSelectorParameters() { private void updateTrackSelectorParameters() {
if (player != null) { if (player != null) {
// Until the demo app is fully migrated to TrackSelectionParameters, rely on ExoPlayer to use trackSelectionParameters = player.getTrackSelectionParameters();
// DefaultTrackSelector by default.
trackSelectionParameters =
(DefaultTrackSelector.Parameters) player.getTrackSelectionParameters();
} }
} }
@ -422,8 +449,7 @@ public class PlayerActivity extends AppCompatActivity
// User controls // User controls
private void updateButtonVisibility() { private void updateButtonVisibility() {
selectTracksButton.setEnabled( selectTracksButton.setEnabled(player != null && TrackSelectionDialog.willHaveContent(player));
player != null && TrackSelectionDialog.willHaveContent(trackSelector));
} }
private void showControls() { private void showControls() {
@ -461,20 +487,20 @@ public class PlayerActivity extends AppCompatActivity
@Override @Override
@SuppressWarnings("ReferenceEquality") @SuppressWarnings("ReferenceEquality")
public void onTracksInfoChanged(TracksInfo tracksInfo) { public void onTracksChanged(Tracks tracks) {
updateButtonVisibility(); updateButtonVisibility();
if (tracksInfo == lastSeenTracksInfo) { if (tracks == lastSeenTracks) {
return; return;
} }
if (!tracksInfo.isTypeSupportedOrEmpty( if (tracks.containsType(C.TRACK_TYPE_VIDEO)
C.TRACK_TYPE_VIDEO, /* allowExceedsCapabilities= */ true)) { && !tracks.isTypeSupported(C.TRACK_TYPE_VIDEO, /* allowExceedsCapabilities= */ true)) {
showToast(R.string.error_unsupported_video); showToast(R.string.error_unsupported_video);
} }
if (!tracksInfo.isTypeSupportedOrEmpty( if (tracks.containsType(C.TRACK_TYPE_AUDIO)
C.TRACK_TYPE_AUDIO, /* allowExceedsCapabilities= */ true)) { && !tracks.isTypeSupported(C.TRACK_TYPE_AUDIO, /* allowExceedsCapabilities= */ true)) {
showToast(R.string.error_unsupported_audio); showToast(R.string.error_unsupported_audio);
} }
lastSeenTracksInfo = tracksInfo; lastSeenTracks = tracks;
} }
} }
@ -513,29 +539,31 @@ public class PlayerActivity extends AppCompatActivity
private static List<MediaItem> createMediaItems(Intent intent, DownloadTracker downloadTracker) { private static List<MediaItem> createMediaItems(Intent intent, DownloadTracker downloadTracker) {
List<MediaItem> mediaItems = new ArrayList<>(); List<MediaItem> mediaItems = new ArrayList<>();
for (MediaItem item : IntentUtil.createMediaItemsFromIntent(intent)) { for (MediaItem item : IntentUtil.createMediaItemsFromIntent(intent)) {
@Nullable mediaItems.add(
DownloadRequest downloadRequest = maybeSetDownloadProperties(
downloadTracker.getDownloadRequest(item.localConfiguration.uri); item, downloadTracker.getDownloadRequest(item.localConfiguration.uri)));
if (downloadRequest != null) {
MediaItem.Builder builder = item.buildUpon();
builder
.setMediaId(downloadRequest.id)
.setUri(downloadRequest.uri)
.setCustomCacheKey(downloadRequest.customCacheKey)
.setMimeType(downloadRequest.mimeType)
.setStreamKeys(downloadRequest.streamKeys);
@Nullable
MediaItem.DrmConfiguration drmConfiguration = item.localConfiguration.drmConfiguration;
if (drmConfiguration != null) {
builder.setDrmConfiguration(
drmConfiguration.buildUpon().setKeySetId(downloadRequest.keySetId).build());
}
mediaItems.add(builder.build());
} else {
mediaItems.add(item);
}
} }
return mediaItems; return mediaItems;
} }
private static MediaItem maybeSetDownloadProperties(
MediaItem item, @Nullable DownloadRequest downloadRequest) {
if (downloadRequest == null) {
return item;
}
MediaItem.Builder builder = item.buildUpon();
builder
.setMediaId(downloadRequest.id)
.setUri(downloadRequest.uri)
.setCustomCacheKey(downloadRequest.customCacheKey)
.setMimeType(downloadRequest.mimeType)
.setStreamKeys(downloadRequest.streamKeys);
@Nullable
MediaItem.DrmConfiguration drmConfiguration = item.localConfiguration.drmConfiguration;
if (drmConfiguration != null) {
builder.setDrmConfiguration(
drmConfiguration.buildUpon().setKeySetId(downloadRequest.keySetId).build());
}
return builder.build();
}
} }

View file

@ -15,9 +15,9 @@
*/ */
package com.google.android.exoplayer2.demo; package com.google.android.exoplayer2.demo;
import static com.google.android.exoplayer2.util.Assertions.checkArgument; import static com.google.common.base.Preconditions.checkArgument;
import static com.google.android.exoplayer2.util.Assertions.checkNotNull; import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.android.exoplayer2.util.Assertions.checkState; import static com.google.common.base.Preconditions.checkState;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
@ -27,6 +27,7 @@ import android.content.res.AssetManager;
import android.net.Uri; import android.net.Uri;
import android.os.AsyncTask; import android.os.AsyncTask;
import android.os.Bundle; import android.os.Bundle;
import android.text.TextUtils;
import android.util.JsonReader; import android.util.JsonReader;
import android.view.Menu; import android.view.Menu;
import android.view.MenuInflater; import android.view.MenuInflater;
@ -53,6 +54,7 @@ import com.google.android.exoplayer2.upstream.DataSourceUtil;
import com.google.android.exoplayer2.upstream.DataSpec; import com.google.android.exoplayer2.upstream.DataSpec;
import com.google.android.exoplayer2.util.Log; import com.google.android.exoplayer2.util.Log;
import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.util.Util;
import com.google.common.base.Objects;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap;
import java.io.IOException; import java.io.IOException;
@ -116,8 +118,11 @@ public class SampleChooserActivity extends AppCompatActivity
useExtensionRenderers = DemoUtil.useExtensionRenderers(); useExtensionRenderers = DemoUtil.useExtensionRenderers();
downloadTracker = DemoUtil.getDownloadTracker(/* context= */ this); downloadTracker = DemoUtil.getDownloadTracker(/* context= */ this);
loadSample(); loadSample();
startDownloadService();
}
// Start the download service if it should be running but it's not currently. /** Start the download service if it should be running but it's not currently. */
private void startDownloadService() {
// Starting the service in the foreground causes notification flicker if there is no scheduled // Starting the service in the foreground causes notification flicker if there is no scheduled
// action. Starting it in the background throws an exception if the app is in the background too // action. Starting it in the background throws an exception if the app is in the background too
// (e.g. if device screen is locked). // (e.g. if device screen is locked).
@ -436,7 +441,10 @@ public class SampleChooserActivity extends AppCompatActivity
} else { } else {
@Nullable @Nullable
String adaptiveMimeType = String adaptiveMimeType =
Util.getAdaptiveMimeTypeForContentType(Util.inferContentType(uri, extension)); Util.getAdaptiveMimeTypeForContentType(
TextUtils.isEmpty(extension)
? Util.inferContentType(uri)
: Util.inferContentTypeForExtension(extension));
mediaItem mediaItem
.setUri(uri) .setUri(uri)
.setMediaMetadata(new MediaMetadata.Builder().setTitle(title).build()) .setMediaMetadata(new MediaMetadata.Builder().setTitle(title).build())
@ -447,7 +455,7 @@ public class SampleChooserActivity extends AppCompatActivity
new MediaItem.DrmConfiguration.Builder(drmUuid) new MediaItem.DrmConfiguration.Builder(drmUuid)
.setLicenseUri(drmLicenseUri) .setLicenseUri(drmLicenseUri)
.setLicenseRequestHeaders(drmLicenseRequestHeaders) .setLicenseRequestHeaders(drmLicenseRequestHeaders)
.forceSessionsForAudioAndVideoTracks(drmSessionForClearContent) .setForceSessionsForAudioAndVideoTracks(drmSessionForClearContent)
.setMultiSession(drmMultiSession) .setMultiSession(drmMultiSession)
.setForceDefaultLicenseUri(drmForceDefaultLicenseUri) .setForceDefaultLicenseUri(drmForceDefaultLicenseUri)
.build()); .build());
@ -481,7 +489,7 @@ public class SampleChooserActivity extends AppCompatActivity
private PlaylistGroup getGroup(String groupName, List<PlaylistGroup> groups) { private PlaylistGroup getGroup(String groupName, List<PlaylistGroup> groups) {
for (int i = 0; i < groups.size(); i++) { for (int i = 0; i < groups.size(); i++) {
if (Util.areEqual(groupName, groups.get(i).title)) { if (Objects.equal(groupName, groups.get(i).title)) {
return groups.get(i); return groups.get(i);
} }
} }

View file

@ -32,20 +32,40 @@ import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentPagerAdapter; import androidx.fragment.app.FragmentPagerAdapter;
import androidx.viewpager.widget.ViewPager; import androidx.viewpager.widget.ViewPager;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.source.TrackGroupArray; import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector; import com.google.android.exoplayer2.Tracks;
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector.SelectionOverride; import com.google.android.exoplayer2.source.TrackGroup;
import com.google.android.exoplayer2.trackselection.MappingTrackSelector.MappedTrackInfo; import com.google.android.exoplayer2.trackselection.TrackSelectionOverride;
import com.google.android.exoplayer2.trackselection.TrackSelectionParameters;
import com.google.android.exoplayer2.ui.TrackSelectionView; import com.google.android.exoplayer2.ui.TrackSelectionView;
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.material.tabs.TabLayout; import com.google.android.material.tabs.TabLayout;
import com.google.common.collect.ImmutableList;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map;
/** Dialog to select tracks. */ /** Dialog to select tracks. */
public final class TrackSelectionDialog extends DialogFragment { public final class TrackSelectionDialog extends DialogFragment {
/** Called when tracks are selected. */
public interface TrackSelectionListener {
/**
* Called when tracks are selected.
*
* @param trackSelectionParameters A {@link TrackSelectionParameters} representing the selected
* tracks. Any manual selections are defined by {@link
* TrackSelectionParameters#disabledTrackTypes} and {@link
* TrackSelectionParameters#overrides}.
*/
void onTracksSelected(TrackSelectionParameters trackSelectionParameters);
}
public static final ImmutableList<Integer> SUPPORTED_TRACK_TYPES =
ImmutableList.of(C.TRACK_TYPE_VIDEO, C.TRACK_TYPE_AUDIO, C.TRACK_TYPE_TEXT);
private final SparseArray<TrackSelectionViewFragment> tabFragments; private final SparseArray<TrackSelectionViewFragment> tabFragments;
private final ArrayList<Integer> tabTrackTypes; private final ArrayList<Integer> tabTrackTypes;
@ -55,20 +75,19 @@ public final class TrackSelectionDialog extends DialogFragment {
/** /**
* Returns whether a track selection dialog will have content to display if initialized with the * Returns whether a track selection dialog will have content to display if initialized with the
* specified {@link DefaultTrackSelector} in its current state. * specified {@link Player}.
*/ */
public static boolean willHaveContent(DefaultTrackSelector trackSelector) { public static boolean willHaveContent(Player player) {
MappedTrackInfo mappedTrackInfo = trackSelector.getCurrentMappedTrackInfo(); return willHaveContent(player.getCurrentTracks());
return mappedTrackInfo != null && willHaveContent(mappedTrackInfo);
} }
/** /**
* Returns whether a track selection dialog will have content to display if initialized with the * Returns whether a track selection dialog will have content to display if initialized with the
* specified {@link MappedTrackInfo}. * specified {@link Tracks}.
*/ */
public static boolean willHaveContent(MappedTrackInfo mappedTrackInfo) { public static boolean willHaveContent(Tracks tracks) {
for (int i = 0; i < mappedTrackInfo.getRendererCount(); i++) { for (Tracks.Group trackGroup : tracks.getGroups()) {
if (showTabForRenderer(mappedTrackInfo, i)) { if (SUPPORTED_TRACK_TYPES.contains(trackGroup.getType())) {
return true; return true;
} }
} }
@ -76,78 +95,67 @@ public final class TrackSelectionDialog extends DialogFragment {
} }
/** /**
* Creates a dialog for a given {@link DefaultTrackSelector}, whose parameters will be * Creates a dialog for a given {@link Player}, whose parameters will be automatically updated
* automatically updated when tracks are selected. * when tracks are selected.
* *
* @param trackSelector The {@link DefaultTrackSelector}. * @param player The {@link Player}.
* @param onDismissListener A {@link DialogInterface.OnDismissListener} to call when the dialog is * @param onDismissListener A {@link DialogInterface.OnDismissListener} to call when the dialog is
* dismissed. * dismissed.
*/ */
public static TrackSelectionDialog createForTrackSelector( public static TrackSelectionDialog createForPlayer(
DefaultTrackSelector trackSelector, DialogInterface.OnDismissListener onDismissListener) { Player player, DialogInterface.OnDismissListener onDismissListener) {
MappedTrackInfo mappedTrackInfo = return createForTracksAndParameters(
Assertions.checkNotNull(trackSelector.getCurrentMappedTrackInfo()); R.string.track_selection_title,
TrackSelectionDialog trackSelectionDialog = new TrackSelectionDialog(); player.getCurrentTracks(),
DefaultTrackSelector.Parameters parameters = trackSelector.getParameters(); player.getTrackSelectionParameters(),
trackSelectionDialog.init(
/* titleId= */ R.string.track_selection_title,
mappedTrackInfo,
/* initialParameters = */ parameters,
/* allowAdaptiveSelections= */ true, /* allowAdaptiveSelections= */ true,
/* allowMultipleOverrides= */ false, /* allowMultipleOverrides= */ false,
/* onClickListener= */ (dialog, which) -> { player::setTrackSelectionParameters,
DefaultTrackSelector.ParametersBuilder builder = parameters.buildUpon();
for (int i = 0; i < mappedTrackInfo.getRendererCount(); i++) {
builder
.clearSelectionOverrides(/* rendererIndex= */ i)
.setRendererDisabled(
/* rendererIndex= */ i,
trackSelectionDialog.getIsDisabled(/* rendererIndex= */ i));
List<SelectionOverride> overrides =
trackSelectionDialog.getOverrides(/* rendererIndex= */ i);
if (!overrides.isEmpty()) {
builder.setSelectionOverride(
/* rendererIndex= */ i,
mappedTrackInfo.getTrackGroups(/* rendererIndex= */ i),
overrides.get(0));
}
}
trackSelector.setParameters(builder);
},
onDismissListener); onDismissListener);
return trackSelectionDialog;
} }
/** /**
* Creates a dialog for given {@link MappedTrackInfo} and {@link DefaultTrackSelector.Parameters}. * Creates a dialog for given {@link Tracks} and {@link TrackSelectionParameters}.
* *
* @param titleId The resource id of the dialog title. * @param titleId The resource id of the dialog title.
* @param mappedTrackInfo The {@link MappedTrackInfo} to display. * @param tracks The {@link Tracks} describing the tracks to display.
* @param initialParameters The {@link DefaultTrackSelector.Parameters} describing the initial * @param trackSelectionParameters The initial {@link TrackSelectionParameters}.
* track selection.
* @param allowAdaptiveSelections Whether adaptive selections (consisting of more than one track) * @param allowAdaptiveSelections Whether adaptive selections (consisting of more than one track)
* can be made. * can be made.
* @param allowMultipleOverrides Whether tracks from multiple track groups can be selected. * @param allowMultipleOverrides Whether tracks from multiple track groups can be selected.
* @param onClickListener {@link DialogInterface.OnClickListener} called when tracks are selected. * @param trackSelectionListener Called when tracks are selected.
* @param onDismissListener {@link DialogInterface.OnDismissListener} called when the dialog is * @param onDismissListener {@link DialogInterface.OnDismissListener} called when the dialog is
* dismissed. * dismissed.
*/ */
public static TrackSelectionDialog createForMappedTrackInfoAndParameters( public static TrackSelectionDialog createForTracksAndParameters(
int titleId, int titleId,
MappedTrackInfo mappedTrackInfo, Tracks tracks,
DefaultTrackSelector.Parameters initialParameters, TrackSelectionParameters trackSelectionParameters,
boolean allowAdaptiveSelections, boolean allowAdaptiveSelections,
boolean allowMultipleOverrides, boolean allowMultipleOverrides,
DialogInterface.OnClickListener onClickListener, TrackSelectionListener trackSelectionListener,
DialogInterface.OnDismissListener onDismissListener) { DialogInterface.OnDismissListener onDismissListener) {
TrackSelectionDialog trackSelectionDialog = new TrackSelectionDialog(); TrackSelectionDialog trackSelectionDialog = new TrackSelectionDialog();
trackSelectionDialog.init( trackSelectionDialog.init(
tracks,
trackSelectionParameters,
titleId, titleId,
mappedTrackInfo,
initialParameters,
allowAdaptiveSelections, allowAdaptiveSelections,
allowMultipleOverrides, allowMultipleOverrides,
onClickListener, /* onClickListener= */ (dialog, which) -> {
TrackSelectionParameters.Builder builder = trackSelectionParameters.buildUpon();
for (int i = 0; i < SUPPORTED_TRACK_TYPES.size(); i++) {
int trackType = SUPPORTED_TRACK_TYPES.get(i);
builder.setTrackTypeDisabled(trackType, trackSelectionDialog.getIsDisabled(trackType));
builder.clearOverridesOfType(trackType);
Map<TrackGroup, TrackSelectionOverride> overrides =
trackSelectionDialog.getOverrides(trackType);
for (TrackSelectionOverride override : overrides.values()) {
builder.addOverride(override);
}
}
trackSelectionListener.onTracksSelected(builder.build());
},
onDismissListener); onDismissListener);
return trackSelectionDialog; return trackSelectionDialog;
} }
@ -160,9 +168,9 @@ public final class TrackSelectionDialog extends DialogFragment {
} }
private void init( private void init(
Tracks tracks,
TrackSelectionParameters trackSelectionParameters,
int titleId, int titleId,
MappedTrackInfo mappedTrackInfo,
DefaultTrackSelector.Parameters initialParameters,
boolean allowAdaptiveSelections, boolean allowAdaptiveSelections,
boolean allowMultipleOverrides, boolean allowMultipleOverrides,
DialogInterface.OnClickListener onClickListener, DialogInterface.OnClickListener onClickListener,
@ -170,45 +178,49 @@ public final class TrackSelectionDialog extends DialogFragment {
this.titleId = titleId; this.titleId = titleId;
this.onClickListener = onClickListener; this.onClickListener = onClickListener;
this.onDismissListener = onDismissListener; this.onDismissListener = onDismissListener;
for (int i = 0; i < mappedTrackInfo.getRendererCount(); i++) {
if (showTabForRenderer(mappedTrackInfo, i)) { for (int i = 0; i < SUPPORTED_TRACK_TYPES.size(); i++) {
int trackType = mappedTrackInfo.getRendererType(/* rendererIndex= */ i); @C.TrackType int trackType = SUPPORTED_TRACK_TYPES.get(i);
TrackGroupArray trackGroupArray = mappedTrackInfo.getTrackGroups(i); ArrayList<Tracks.Group> trackGroups = new ArrayList<>();
for (Tracks.Group trackGroup : tracks.getGroups()) {
if (trackGroup.getType() == trackType) {
trackGroups.add(trackGroup);
}
}
if (!trackGroups.isEmpty()) {
TrackSelectionViewFragment tabFragment = new TrackSelectionViewFragment(); TrackSelectionViewFragment tabFragment = new TrackSelectionViewFragment();
tabFragment.init( tabFragment.init(
mappedTrackInfo, trackGroups,
/* rendererIndex= */ i, trackSelectionParameters.disabledTrackTypes.contains(trackType),
initialParameters.getRendererDisabled(/* rendererIndex= */ i), trackSelectionParameters.overrides,
initialParameters.getSelectionOverride(/* rendererIndex= */ i, trackGroupArray),
allowAdaptiveSelections, allowAdaptiveSelections,
allowMultipleOverrides); allowMultipleOverrides);
tabFragments.put(i, tabFragment); tabFragments.put(trackType, tabFragment);
tabTrackTypes.add(trackType); tabTrackTypes.add(trackType);
} }
} }
} }
/** /**
* Returns whether a renderer is disabled. * Returns whether the disabled option is selected for the specified track type.
* *
* @param rendererIndex Renderer index. * @param trackType The track type.
* @return Whether the renderer is disabled. * @return Whether the disabled option is selected for the track type.
*/ */
public boolean getIsDisabled(int rendererIndex) { public boolean getIsDisabled(int trackType) {
TrackSelectionViewFragment rendererView = tabFragments.get(rendererIndex); TrackSelectionViewFragment trackView = tabFragments.get(trackType);
return rendererView != null && rendererView.isDisabled; return trackView != null && trackView.isDisabled;
} }
/** /**
* Returns the list of selected track selection overrides for the specified renderer. There will * Returns the selected track overrides for the specified track type.
* be at most one override for each track group.
* *
* @param rendererIndex Renderer index. * @param trackType The track type.
* @return The list of track selection overrides for this renderer. * @return The track overrides for the track type.
*/ */
public List<SelectionOverride> getOverrides(int rendererIndex) { public Map<TrackGroup, TrackSelectionOverride> getOverrides(int trackType) {
TrackSelectionViewFragment rendererView = tabFragments.get(rendererIndex); TrackSelectionViewFragment trackView = tabFragments.get(trackType);
return rendererView == null ? Collections.emptyList() : rendererView.overrides; return trackView == null ? Collections.emptyMap() : trackView.overrides;
} }
@Override @Override
@ -248,27 +260,7 @@ public final class TrackSelectionDialog extends DialogFragment {
return dialogView; return dialogView;
} }
private static boolean showTabForRenderer(MappedTrackInfo mappedTrackInfo, int rendererIndex) { private static String getTrackTypeString(Resources resources, @C.TrackType int trackType) {
TrackGroupArray trackGroupArray = mappedTrackInfo.getTrackGroups(rendererIndex);
if (trackGroupArray.length == 0) {
return false;
}
int trackType = mappedTrackInfo.getRendererType(rendererIndex);
return isSupportedTrackType(trackType);
}
private static boolean isSupportedTrackType(int trackType) {
switch (trackType) {
case C.TRACK_TYPE_VIDEO:
case C.TRACK_TYPE_AUDIO:
case C.TRACK_TYPE_TEXT:
return true;
default:
return false;
}
}
private static String getTrackTypeString(Resources resources, int trackType) {
switch (trackType) { switch (trackType) {
case C.TRACK_TYPE_VIDEO: case C.TRACK_TYPE_VIDEO:
return resources.getString(R.string.exo_track_selection_title_video); return resources.getString(R.string.exo_track_selection_title_video);
@ -289,12 +281,12 @@ public final class TrackSelectionDialog extends DialogFragment {
@Override @Override
public Fragment getItem(int position) { public Fragment getItem(int position) {
return tabFragments.valueAt(position); return tabFragments.get(tabTrackTypes.get(position));
} }
@Override @Override
public int getCount() { public int getCount() {
return tabFragments.size(); return tabTrackTypes.size();
} }
@Override @Override
@ -307,13 +299,12 @@ public final class TrackSelectionDialog extends DialogFragment {
public static final class TrackSelectionViewFragment extends Fragment public static final class TrackSelectionViewFragment extends Fragment
implements TrackSelectionView.TrackSelectionListener { implements TrackSelectionView.TrackSelectionListener {
private MappedTrackInfo mappedTrackInfo; private List<Tracks.Group> trackGroups;
private int rendererIndex;
private boolean allowAdaptiveSelections; private boolean allowAdaptiveSelections;
private boolean allowMultipleOverrides; private boolean allowMultipleOverrides;
/* package */ boolean isDisabled; /* package */ boolean isDisabled;
/* package */ List<SelectionOverride> overrides; /* package */ Map<TrackGroup, TrackSelectionOverride> overrides;
public TrackSelectionViewFragment() { public TrackSelectionViewFragment() {
// Retain instance across activity re-creation to prevent losing access to init data. // Retain instance across activity re-creation to prevent losing access to init data.
@ -321,21 +312,20 @@ public final class TrackSelectionDialog extends DialogFragment {
} }
public void init( public void init(
MappedTrackInfo mappedTrackInfo, List<Tracks.Group> trackGroups,
int rendererIndex, boolean isDisabled,
boolean initialIsDisabled, Map<TrackGroup, TrackSelectionOverride> overrides,
@Nullable SelectionOverride initialOverride,
boolean allowAdaptiveSelections, boolean allowAdaptiveSelections,
boolean allowMultipleOverrides) { boolean allowMultipleOverrides) {
this.mappedTrackInfo = mappedTrackInfo; this.trackGroups = trackGroups;
this.rendererIndex = rendererIndex; this.isDisabled = isDisabled;
this.isDisabled = initialIsDisabled;
this.overrides =
initialOverride == null
? Collections.emptyList()
: Collections.singletonList(initialOverride);
this.allowAdaptiveSelections = allowAdaptiveSelections; this.allowAdaptiveSelections = allowAdaptiveSelections;
this.allowMultipleOverrides = allowMultipleOverrides; this.allowMultipleOverrides = allowMultipleOverrides;
// TrackSelectionView does this filtering internally, but we need to do it here as well to
// handle the case where the TrackSelectionView is never created.
this.overrides =
new HashMap<>(
TrackSelectionView.filterOverrides(overrides, trackGroups, allowMultipleOverrides));
} }
@Override @Override
@ -351,8 +341,7 @@ public final class TrackSelectionDialog extends DialogFragment {
trackSelectionView.setAllowMultipleOverrides(allowMultipleOverrides); trackSelectionView.setAllowMultipleOverrides(allowMultipleOverrides);
trackSelectionView.setAllowAdaptiveSelections(allowAdaptiveSelections); trackSelectionView.setAllowAdaptiveSelections(allowAdaptiveSelections);
trackSelectionView.init( trackSelectionView.init(
mappedTrackInfo, trackGroups,
rendererIndex,
isDisabled, isDisabled,
overrides, overrides,
/* trackFormatComparator= */ null, /* trackFormatComparator= */ null,
@ -361,7 +350,8 @@ public final class TrackSelectionDialog extends DialogFragment {
} }
@Override @Override
public void onTrackSelectionChanged(boolean isDisabled, List<SelectionOverride> overrides) { public void onTrackSelectionChanged(
boolean isDisabled, Map<TrackGroup, TrackSelectionOverride> overrides) {
this.isDisabled = isDisabled; this.isDisabled = isDisabled;
this.overrides = overrides; this.overrides = overrides;
} }

View file

@ -19,6 +19,7 @@ import android.app.Activity;
import android.content.Intent; import android.content.Intent;
import android.net.Uri; import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.text.TextUtils;
import android.view.Surface; import android.view.Surface;
import android.view.SurfaceControl; import android.view.SurfaceControl;
import android.view.SurfaceHolder; import android.view.SurfaceHolder;
@ -42,7 +43,6 @@ import com.google.android.exoplayer2.ui.PlayerControlView;
import com.google.android.exoplayer2.upstream.DataSource; import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.DefaultDataSource; import com.google.android.exoplayer2.upstream.DefaultDataSource;
import com.google.android.exoplayer2.upstream.DefaultHttpDataSource; import com.google.android.exoplayer2.upstream.DefaultHttpDataSource;
import com.google.android.exoplayer2.upstream.HttpDataSource;
import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.util.Util;
import java.util.UUID; import java.util.UUID;
@ -189,7 +189,7 @@ public final class MainActivity extends Activity {
String drmScheme = Assertions.checkNotNull(intent.getStringExtra(DRM_SCHEME_EXTRA)); String drmScheme = Assertions.checkNotNull(intent.getStringExtra(DRM_SCHEME_EXTRA));
String drmLicenseUrl = Assertions.checkNotNull(intent.getStringExtra(DRM_LICENSE_URL_EXTRA)); String drmLicenseUrl = Assertions.checkNotNull(intent.getStringExtra(DRM_LICENSE_URL_EXTRA));
UUID drmSchemeUuid = Assertions.checkNotNull(Util.getDrmUuid(drmScheme)); UUID drmSchemeUuid = Assertions.checkNotNull(Util.getDrmUuid(drmScheme));
HttpDataSource.Factory licenseDataSourceFactory = new DefaultHttpDataSource.Factory(); DataSource.Factory licenseDataSourceFactory = new DefaultHttpDataSource.Factory();
HttpMediaDrmCallback drmCallback = HttpMediaDrmCallback drmCallback =
new HttpMediaDrmCallback(drmLicenseUrl, licenseDataSourceFactory); new HttpMediaDrmCallback(drmLicenseUrl, licenseDataSourceFactory);
drmSessionManager = drmSessionManager =
@ -202,13 +202,18 @@ public final class MainActivity extends Activity {
DataSource.Factory dataSourceFactory = new DefaultDataSource.Factory(this); DataSource.Factory dataSourceFactory = new DefaultDataSource.Factory(this);
MediaSource mediaSource; MediaSource mediaSource;
@C.ContentType int type = Util.inferContentType(uri, intent.getStringExtra(EXTENSION_EXTRA)); @Nullable String fileExtension = intent.getStringExtra(EXTENSION_EXTRA);
if (type == C.TYPE_DASH) { @C.ContentType
int type =
TextUtils.isEmpty(fileExtension)
? Util.inferContentType(uri)
: Util.inferContentTypeForExtension(fileExtension);
if (type == C.CONTENT_TYPE_DASH) {
mediaSource = mediaSource =
new DashMediaSource.Factory(dataSourceFactory) new DashMediaSource.Factory(dataSourceFactory)
.setDrmSessionManagerProvider(unusedMediaItem -> drmSessionManager) .setDrmSessionManagerProvider(unusedMediaItem -> drmSessionManager)
.createMediaSource(MediaItem.fromUri(uri)); .createMediaSource(MediaItem.fromUri(uri));
} else if (type == C.TYPE_OTHER) { } else if (type == C.CONTENT_TYPE_OTHER) {
mediaSource = mediaSource =
new ProgressiveMediaSource.Factory(dataSourceFactory) new ProgressiveMediaSource.Factory(dataSourceFactory)
.setDrmSessionManagerProvider(unusedMediaItem -> drmSessionManager) .setDrmSessionManagerProvider(unusedMediaItem -> drmSessionManager)

View file

@ -0,0 +1,22 @@
# Build targets for a demo MediaPipe graph.
# See README.md for instructions on using MediaPipe in the demo.
load("//mediapipe/java/com/google/mediapipe:mediapipe_aar.bzl", "mediapipe_aar")
load(
"//mediapipe/framework/tool:mediapipe_graph.bzl",
"mediapipe_binary_graph",
)
mediapipe_aar(
name = "edge_detector_mediapipe_aar",
calculators = [
"//mediapipe/calculators/image:luminance_calculator",
"//mediapipe/calculators/image:sobel_edges_calculator",
],
)
mediapipe_binary_graph(
name = "edge_detector_binary_graph",
graph = "edge_detector_mediapipe_graph.pbtxt",
output_name = "edge_detector_mediapipe_graph.binarypb",
)

View file

@ -6,4 +6,61 @@ example by removing audio or video.
See the [demos README](../README.md) for instructions on how to build and run See the [demos README](../README.md) for instructions on how to build and run
this demo. this demo.
## MediaPipe frame processing demo
Building the demo app with [MediaPipe][] integration enabled requires some extra
manual steps.
1. Follow the
[instructions](https://google.github.io/mediapipe/getting_started/install.html)
to install MediaPipe.
1. Copy the Transformer demo's build configuration and MediaPipe graph text
protocol buffer under the MediaPipe source tree. This makes it easy to
[build an AAR][] with bazel by reusing MediaPipe's workspace.
```sh
cd "<path to MediaPipe checkout>"
MEDIAPIPE_ROOT="$(pwd)"
MEDIAPIPE_TRANSFORMER_ROOT="${MEDIAPIPE_ROOT}/mediapipe/java/com/google/mediapipe/transformer"
cd "<path to the transformer demo (containing this readme)>"
TRANSFORMER_DEMO_ROOT="$(pwd)"
mkdir -p "${MEDIAPIPE_TRANSFORMER_ROOT}"
mkdir -p "${TRANSFORMER_DEMO_ROOT}/libs"
cp ${TRANSFORMER_DEMO_ROOT}/BUILD.bazel ${MEDIAPIPE_TRANSFORMER_ROOT}/BUILD
cp ${TRANSFORMER_DEMO_ROOT}/src/withMediaPipe/assets/edge_detector_mediapipe_graph.pbtxt \
${MEDIAPIPE_TRANSFORMER_ROOT}
```
1. Build the AAR and the binary proto for the demo's MediaPipe graph, then copy
them to Transformer.
```sh
cd ${MEDIAPIPE_ROOT}
bazel build -c opt --strip=ALWAYS \
--host_crosstool_top=@bazel_tools//tools/cpp:toolchain \
--fat_apk_cpu=arm64-v8a,armeabi-v7a \
--legacy_whole_archive=0 \
--features=-legacy_whole_archive \
--copt=-fvisibility=hidden \
--copt=-ffunction-sections \
--copt=-fdata-sections \
--copt=-fstack-protector \
--copt=-Oz \
--copt=-fomit-frame-pointer \
--copt=-DABSL_MIN_LOG_LEVEL=2 \
--linkopt=-Wl,--gc-sections,--strip-all \
mediapipe/java/com/google/mediapipe/transformer:edge_detector_mediapipe_aar.aar
cp bazel-bin/mediapipe/java/com/google/mediapipe/transformer/edge_detector_mediapipe_aar.aar \
${TRANSFORMER_DEMO_ROOT}/libs
bazel build mediapipe/java/com/google/mediapipe/transformer:edge_detector_binary_graph
cp bazel-bin/mediapipe/java/com/google/mediapipe/transformer/edge_detector_mediapipe_graph.binarypb \
${TRANSFORMER_DEMO_ROOT}/src/withMediaPipe/assets
```
1. In Android Studio, gradle sync and select the `withMediaPipe` build variant
(this will only appear if the AAR is present), then build and run the demo
app and select a MediaPipe-based effect.
[Transformer]: https://exoplayer.dev/transforming-media.html [Transformer]: https://exoplayer.dev/transforming-media.html
[MediaPipe]: https://google.github.io/mediapipe/
[build an AAR]: https://google.github.io/mediapipe/getting_started/android_archive_library.html

View file

@ -45,6 +45,27 @@ android {
// This demo app isn't indexed and doesn't have translations. // This demo app isn't indexed and doesn't have translations.
disable 'GoogleAppIndexingWarning','MissingTranslation' disable 'GoogleAppIndexingWarning','MissingTranslation'
} }
flavorDimensions "mediaPipe"
productFlavors {
noMediaPipe {
dimension "mediaPipe"
}
withMediaPipe {
dimension "mediaPipe"
}
}
// Ignore the withMediaPipe variant if the MediaPipe AAR is not present.
if (!project.file("libs/edge_detector_mediapipe_aar.aar").exists()) {
variantFilter { variant ->
def names = variant.flavors*.name
if (names.contains("withMediaPipe")) {
setIgnore(true)
}
}
}
} }
dependencies { dependencies {
@ -56,6 +77,14 @@ dependencies {
implementation 'androidx.multidex:multidex:' + androidxMultidexVersion implementation 'androidx.multidex:multidex:' + androidxMultidexVersion
implementation 'com.google.android.material:material:' + androidxMaterialVersion implementation 'com.google.android.material:material:' + androidxMaterialVersion
implementation project(modulePrefix + 'library-core') implementation project(modulePrefix + 'library-core')
implementation project(modulePrefix + 'library-dash')
implementation project(modulePrefix + 'library-transformer') implementation project(modulePrefix + 'library-transformer')
implementation project(modulePrefix + 'library-ui') implementation project(modulePrefix + 'library-ui')
// For MediaPipe and its dependencies:
withMediaPipeImplementation fileTree(dir: 'libs', include: ['*.aar'])
withMediaPipeImplementation 'com.google.flogger:flogger:latest.release'
withMediaPipeImplementation 'com.google.flogger:flogger-system-backend:latest.release'
withMediaPipeImplementation 'com.google.code.findbugs:jsr305:latest.release'
withMediaPipeImplementation 'com.google.protobuf:protobuf-javalite:3.19.1'
} }

View file

@ -0,0 +1,37 @@
#version 100
// Copyright 2022 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.
// ES 2 fragment shader that overlays the bitmap from uTexSampler1 over a video
// frame from uTexSampler0.
precision mediump float;
// Texture containing an input video frame.
uniform sampler2D uTexSampler0;
// Texture containing the overlap bitmap.
uniform sampler2D uTexSampler1;
// Horizontal scaling factor for the overlap bitmap.
uniform float uScaleX;
// Vertical scaling factory for the overlap bitmap.
uniform float uScaleY;
varying vec2 vTexSamplingCoord;
void main() {
vec4 videoColor = texture2D(uTexSampler0, vTexSamplingCoord);
vec4 overlayColor = texture2D(uTexSampler1,
vec2(vTexSamplingCoord.x * uScaleX,
vTexSamplingCoord.y * uScaleY));
// Blend the video decoder output and the overlay bitmap.
gl_FragColor = videoColor * (1.0 - overlayColor.a)
+ overlayColor * overlayColor.a;
}

View file

@ -0,0 +1,31 @@
#version 100
// Copyright 2022 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.
// ES 2 fragment shader that samples from a (non-external) texture with uTexSampler,
// copying from this texture to the current output while applying a vignette effect
// by linearly darkening the pixels between uInnerRadius and uOuterRadius.
precision mediump float;
uniform sampler2D uTexSampler;
uniform vec2 uCenter;
uniform float uInnerRadius;
uniform float uOuterRadius;
varying vec2 vTexSamplingCoord;
void main() {
vec3 src = texture2D(uTexSampler, vTexSamplingCoord).xyz;
float dist = distance(vTexSamplingCoord, uCenter);
float scale = clamp(1.0 - (dist - uInnerRadius) / (uOuterRadius - uInnerRadius), 0.0, 1.0);
gl_FragColor = vec4(src.r * scale, src.g * scale, src.b * scale, 1.0);
}

View file

@ -1,4 +1,4 @@
#version 300 es #version 100
// Copyright 2022 The Android Open Source Project // Copyright 2022 The Android Open Source Project
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
@ -12,12 +12,12 @@
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// 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.
in vec4 aFramePosition;
in vec4 aTexCoords; // ES 2 vertex shader that leaves the coordinates unchanged.
uniform mat4 uTexTransform;
uniform mat4 uTransformationMatrix; attribute vec4 aFramePosition;
out vec2 vTexCoords; varying vec2 vTexSamplingCoord;
void main() { void main() {
gl_Position = uTransformationMatrix * aFramePosition; gl_Position = aFramePosition;
vTexCoords = (uTexTransform * aTexCoords).xy; vTexSamplingCoord = vec2(aFramePosition.x * 0.5 + 0.5, aFramePosition.y * 0.5 + 0.5);
} }

View file

@ -0,0 +1,167 @@
/*
* Copyright 2022 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.transformerdemo;
import static com.google.android.exoplayer2.util.Assertions.checkStateNotNull;
import android.content.Context;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.drawable.BitmapDrawable;
import android.opengl.GLES20;
import android.opengl.GLUtils;
import android.util.Size;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.transformer.FrameProcessingException;
import com.google.android.exoplayer2.transformer.SingleFrameGlTextureProcessor;
import com.google.android.exoplayer2.util.GlProgram;
import com.google.android.exoplayer2.util.GlUtil;
import java.io.IOException;
import java.util.Locale;
/**
* A {@link SingleFrameGlTextureProcessor} that overlays a bitmap with a logo and timer on each
* frame.
*
* <p>The bitmap is drawn using an Android {@link Canvas}.
*/
// TODO(b/227625365): Delete this class and use a texture processor from the Transformer library,
// once overlaying a bitmap and text is supported in Transformer.
/* package */ final class BitmapOverlayProcessor implements SingleFrameGlTextureProcessor {
static {
GlUtil.glAssertionsEnabled = true;
}
private static final String VERTEX_SHADER_PATH = "vertex_shader_copy_es2.glsl";
private static final String FRAGMENT_SHADER_PATH = "fragment_shader_bitmap_overlay_es2.glsl";
private static final int BITMAP_WIDTH_HEIGHT = 512;
private final Paint paint;
private final Bitmap overlayBitmap;
private final Bitmap logoBitmap;
private final Canvas overlayCanvas;
private final GlProgram glProgram;
private float bitmapScaleX;
private float bitmapScaleY;
private int bitmapTexId;
/**
* Creates a new instance.
*
* @throws IOException If a problem occurs while reading shader files.
*/
public BitmapOverlayProcessor(Context context) throws IOException {
paint = new Paint();
paint.setTextSize(64);
paint.setAntiAlias(true);
paint.setARGB(0xFF, 0xFF, 0xFF, 0xFF);
paint.setColor(Color.GRAY);
overlayBitmap =
Bitmap.createBitmap(BITMAP_WIDTH_HEIGHT, BITMAP_WIDTH_HEIGHT, Bitmap.Config.ARGB_8888);
overlayCanvas = new Canvas(overlayBitmap);
try {
logoBitmap =
((BitmapDrawable)
context.getPackageManager().getApplicationIcon(context.getPackageName()))
.getBitmap();
} catch (PackageManager.NameNotFoundException e) {
throw new IllegalStateException(e);
}
bitmapTexId = GlUtil.createTexture(BITMAP_WIDTH_HEIGHT, BITMAP_WIDTH_HEIGHT);
GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, /* level= */ 0, overlayBitmap, /* border= */ 0);
glProgram = new GlProgram(context, VERTEX_SHADER_PATH, FRAGMENT_SHADER_PATH);
// Draw the frame on the entire normalized device coordinate space, from -1 to 1, for x and y.
glProgram.setBufferAttribute(
"aFramePosition",
GlUtil.getNormalizedCoordinateBounds(),
GlUtil.HOMOGENEOUS_COORDINATE_VECTOR_SIZE);
glProgram.setSamplerTexIdUniform("uTexSampler1", bitmapTexId, /* texUnitIndex= */ 1);
}
@Override
public Size configure(int inputWidth, int inputHeight) {
if (inputWidth > inputHeight) {
bitmapScaleX = inputWidth / (float) inputHeight;
bitmapScaleY = 1f;
} else {
bitmapScaleX = 1f;
bitmapScaleY = inputHeight / (float) inputWidth;
}
glProgram.setFloatUniform("uScaleX", bitmapScaleX);
glProgram.setFloatUniform("uScaleY", bitmapScaleY);
return new Size(inputWidth, inputHeight);
}
@Override
public void drawFrame(int inputTexId, long presentationTimeUs) throws FrameProcessingException {
try {
checkStateNotNull(glProgram).use();
// Draw to the canvas and store it in a texture.
String text =
String.format(Locale.US, "%.02f", presentationTimeUs / (float) C.MICROS_PER_SECOND);
overlayBitmap.eraseColor(Color.TRANSPARENT);
overlayCanvas.drawBitmap(checkStateNotNull(logoBitmap), /* left= */ 3, /* top= */ 378, paint);
overlayCanvas.drawText(text, /* x= */ 160, /* y= */ 466, paint);
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, bitmapTexId);
GLUtils.texSubImage2D(
GLES20.GL_TEXTURE_2D,
/* level= */ 0,
/* xoffset= */ 0,
/* yoffset= */ 0,
flipBitmapVertically(overlayBitmap));
GlUtil.checkGlError();
glProgram.setSamplerTexIdUniform("uTexSampler0", inputTexId, /* texUnitIndex= */ 0);
glProgram.bindAttributesAndUniforms();
// The four-vertex triangle strip forms a quad.
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, /* first= */ 0, /* count= */ 4);
GlUtil.checkGlError();
} catch (GlUtil.GlException e) {
throw new FrameProcessingException(e);
}
}
@Override
public void release() {
if (glProgram != null) {
glProgram.delete();
}
}
private static Bitmap flipBitmapVertically(Bitmap bitmap) {
Matrix flip = new Matrix();
flip.postScale(1f, -1f);
return Bitmap.createBitmap(
bitmap,
/* x= */ 0,
/* y= */ 0,
bitmap.getWidth(),
bitmap.getHeight(),
flip,
/* filter= */ true);
}
}

View file

@ -32,7 +32,11 @@ import android.widget.TextView;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.app.AppCompatActivity;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.MimeTypes;
import com.google.android.exoplayer2.util.Util;
import com.google.android.material.slider.RangeSlider;
import com.google.android.material.slider.Slider;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull; import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
@ -49,39 +53,84 @@ public final class ConfigurationActivity extends AppCompatActivity {
public static final String AUDIO_MIME_TYPE = "audio_mime_type"; public static final String AUDIO_MIME_TYPE = "audio_mime_type";
public static final String VIDEO_MIME_TYPE = "video_mime_type"; public static final String VIDEO_MIME_TYPE = "video_mime_type";
public static final String RESOLUTION_HEIGHT = "resolution_height"; public static final String RESOLUTION_HEIGHT = "resolution_height";
public static final String TRANSLATE_X = "translate_x";
public static final String TRANSLATE_Y = "translate_y";
public static final String SCALE_X = "scale_x"; public static final String SCALE_X = "scale_x";
public static final String SCALE_Y = "scale_y"; public static final String SCALE_Y = "scale_y";
public static final String ROTATE_DEGREES = "rotate_degrees"; public static final String ROTATE_DEGREES = "rotate_degrees";
public static final String TRIM_START_MS = "trim_start_ms";
public static final String TRIM_END_MS = "trim_end_ms";
public static final String ENABLE_FALLBACK = "enable_fallback";
public static final String ENABLE_REQUEST_SDR_TONE_MAPPING = "enable_request_sdr_tone_mapping";
public static final String ENABLE_HDR_EDITING = "enable_hdr_editing"; public static final String ENABLE_HDR_EDITING = "enable_hdr_editing";
public static final String DEMO_EFFECTS_SELECTIONS = "demo_effects_selections";
public static final String PERIODIC_VIGNETTE_CENTER_X = "periodic_vignette_center_x";
public static final String PERIODIC_VIGNETTE_CENTER_Y = "periodic_vignette_center_y";
public static final String PERIODIC_VIGNETTE_INNER_RADIUS = "periodic_vignette_inner_radius";
public static final String PERIODIC_VIGNETTE_OUTER_RADIUS = "periodic_vignette_outer_radius";
private static final String[] INPUT_URIS = { private static final String[] INPUT_URIS = {
"https://html5demos.com/assets/dizzy.mp4", "https://storage.googleapis.com/exoplayer-test-media-1/mp4/android-screens-10s.mp4",
"https://storage.googleapis.com/exoplayer-test-media-0/android-block-1080-hevc.mp4", "https://storage.googleapis.com/exoplayer-test-media-0/android-block-1080-hevc.mp4",
"https://storage.googleapis.com/exoplayer-test-media-0/BigBuckBunny_320x180.mp4", "https://html5demos.com/assets/dizzy.mp4",
"https://html5demos.com/assets/dizzy.webm", "https://html5demos.com/assets/dizzy.webm",
"https://storage.googleapis.com/exoplayer-test-media-1/mp4/portrait_4k60.mp4",
"https://storage.googleapis.com/exoplayer-test-media-1/mp4/8k24fps_4s.mp4",
"https://storage.googleapis.com/exoplayer-test-media-1/mp4/1920w_1080h_4s.mp4",
"https://storage.googleapis.com/exoplayer-test-media-0/BigBuckBunny_320x180.mp4",
"https://storage.googleapis.com/exoplayer-test-media-1/mp4/portrait_avc_aac.mp4",
"https://storage.googleapis.com/exoplayer-test-media-1/mp4/portrait_rotated_avc_aac.mp4",
"https://storage.googleapis.com/exoplayer-test-media-1/mp4/slow-motion/slowMotion_stopwatch_240fps_long.mp4",
"https://storage.googleapis.com/exoplayer-test-media-1/gen/screens/dash-vod-single-segment/manifest-baseline.mpd",
"https://storage.googleapis.com/exoplayer-test-media-1/mp4/samsung-hdr-hdr10.mp4",
}; };
private static final String[] URI_DESCRIPTIONS = { // same order as INPUT_URIS private static final String[] URI_DESCRIPTIONS = { // same order as INPUT_URIS
"MP4 with H264 video and AAC audio", "720p H264 video and AAC audio",
"MP4 with H265 video and AAC audio", "1080p H265 video and AAC audio",
"Long MP4 with H264 video and AAC audio", "360p H264 video and AAC audio",
"WebM with VP8 video and Vorbis audio", "360p VP8 video and Vorbis audio",
"4K H264 video and AAC audio (portrait, no B-frames)",
"8k H265 video and AAC audio",
"Short 1080p H265 video and AAC audio",
"Long 180p H264 video and AAC audio",
"H264 video and AAC audio (portrait, H > W, 0\u00B0)",
"H264 video and AAC audio (portrait, H < W, 90\u00B0)",
"SEF slow motion with 240 fps",
"480p DASH (non-square pixels)",
"HDR (HDR10) H265 video (encoding may fail)",
}; };
private static final String[] DEMO_EFFECTS = {
"Dizzy crop",
"Edge detector (Media Pipe)",
"Periodic vignette",
"3D spin",
"Overlay logo & timer",
"Zoom in start",
};
private static final int PERIODIC_VIGNETTE_INDEX = 2;
private static final String SAME_AS_INPUT_OPTION = "same as input"; private static final String SAME_AS_INPUT_OPTION = "same as input";
private static final float HALF_DIAGONAL = 1f / (float) Math.sqrt(2);
private @MonotonicNonNull Button chooseFileButton; private @MonotonicNonNull Button selectFileButton;
private @MonotonicNonNull TextView chosenFileTextView; private @MonotonicNonNull TextView selectedFileTextView;
private @MonotonicNonNull CheckBox removeAudioCheckbox; private @MonotonicNonNull CheckBox removeAudioCheckbox;
private @MonotonicNonNull CheckBox removeVideoCheckbox; private @MonotonicNonNull CheckBox removeVideoCheckbox;
private @MonotonicNonNull CheckBox flattenForSlowMotionCheckbox; private @MonotonicNonNull CheckBox flattenForSlowMotionCheckbox;
private @MonotonicNonNull Spinner audioMimeSpinner; private @MonotonicNonNull Spinner audioMimeSpinner;
private @MonotonicNonNull Spinner videoMimeSpinner; private @MonotonicNonNull Spinner videoMimeSpinner;
private @MonotonicNonNull Spinner resolutionHeightSpinner; private @MonotonicNonNull Spinner resolutionHeightSpinner;
private @MonotonicNonNull Spinner translateSpinner;
private @MonotonicNonNull Spinner scaleSpinner; private @MonotonicNonNull Spinner scaleSpinner;
private @MonotonicNonNull Spinner rotateSpinner; private @MonotonicNonNull Spinner rotateSpinner;
private @MonotonicNonNull CheckBox trimCheckBox;
private @MonotonicNonNull CheckBox enableFallbackCheckBox;
private @MonotonicNonNull CheckBox enableRequestSdrToneMappingCheckBox;
private @MonotonicNonNull CheckBox enableHdrEditingCheckBox; private @MonotonicNonNull CheckBox enableHdrEditingCheckBox;
private @MonotonicNonNull Button selectDemoEffectsButton;
private boolean @MonotonicNonNull [] demoEffectsSelections;
private int inputUriPosition; private int inputUriPosition;
private long trimStartMs;
private long trimEndMs;
private float periodicVignetteCenterX;
private float periodicVignetteCenterY;
private float periodicVignetteInnerRadius;
private float periodicVignetteOuterRadius;
@Override @Override
protected void onCreate(@Nullable Bundle savedInstanceState) { protected void onCreate(@Nullable Bundle savedInstanceState) {
@ -90,11 +139,11 @@ public final class ConfigurationActivity extends AppCompatActivity {
findViewById(R.id.transform_button).setOnClickListener(this::startTransformation); findViewById(R.id.transform_button).setOnClickListener(this::startTransformation);
chooseFileButton = findViewById(R.id.choose_file_button); selectFileButton = findViewById(R.id.select_file_button);
chooseFileButton.setOnClickListener(this::chooseFile); selectFileButton.setOnClickListener(this::selectFile);
chosenFileTextView = findViewById(R.id.chosen_file_text_view); selectedFileTextView = findViewById(R.id.selected_file_text_view);
chosenFileTextView.setText(URI_DESCRIPTIONS[inputUriPosition]); selectedFileTextView.setText(URI_DESCRIPTIONS[inputUriPosition]);
removeAudioCheckbox = findViewById(R.id.remove_audio_checkbox); removeAudioCheckbox = findViewById(R.id.remove_audio_checkbox);
removeAudioCheckbox.setOnClickListener(this::onRemoveAudio); removeAudioCheckbox.setOnClickListener(this::onRemoveAudio);
@ -118,11 +167,10 @@ public final class ConfigurationActivity extends AppCompatActivity {
videoMimeSpinner = findViewById(R.id.video_mime_spinner); videoMimeSpinner = findViewById(R.id.video_mime_spinner);
videoMimeSpinner.setAdapter(videoMimeAdapter); videoMimeSpinner.setAdapter(videoMimeAdapter);
videoMimeAdapter.addAll( videoMimeAdapter.addAll(
SAME_AS_INPUT_OPTION, SAME_AS_INPUT_OPTION, MimeTypes.VIDEO_H263, MimeTypes.VIDEO_H264, MimeTypes.VIDEO_MP4V);
MimeTypes.VIDEO_H263, if (Util.SDK_INT >= 24) {
MimeTypes.VIDEO_H264, videoMimeAdapter.add(MimeTypes.VIDEO_H265);
MimeTypes.VIDEO_H265, }
MimeTypes.VIDEO_MP4V);
ArrayAdapter<String> resolutionHeightAdapter = ArrayAdapter<String> resolutionHeightAdapter =
new ArrayAdapter<>(/* context= */ this, R.layout.spinner_item); new ArrayAdapter<>(/* context= */ this, R.layout.spinner_item);
@ -132,14 +180,6 @@ public final class ConfigurationActivity extends AppCompatActivity {
resolutionHeightAdapter.addAll( resolutionHeightAdapter.addAll(
SAME_AS_INPUT_OPTION, "144", "240", "360", "480", "720", "1080", "1440", "2160"); SAME_AS_INPUT_OPTION, "144", "240", "360", "480", "720", "1080", "1440", "2160");
ArrayAdapter<String> translateAdapter =
new ArrayAdapter<>(/* context= */ this, R.layout.spinner_item);
translateAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
translateSpinner = findViewById(R.id.translate_spinner);
translateSpinner.setAdapter(translateAdapter);
translateAdapter.addAll(
SAME_AS_INPUT_OPTION, "-.1, -.1", "0, 0", ".5, 0", "0, .5", "1, 1", "1.9, 0", "0, 1.9");
ArrayAdapter<String> scaleAdapter = ArrayAdapter<String> scaleAdapter =
new ArrayAdapter<>(/* context= */ this, R.layout.spinner_item); new ArrayAdapter<>(/* context= */ this, R.layout.spinner_item);
scaleAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); scaleAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
@ -152,9 +192,22 @@ public final class ConfigurationActivity extends AppCompatActivity {
rotateAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); rotateAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
rotateSpinner = findViewById(R.id.rotate_spinner); rotateSpinner = findViewById(R.id.rotate_spinner);
rotateSpinner.setAdapter(rotateAdapter); rotateSpinner.setAdapter(rotateAdapter);
rotateAdapter.addAll(SAME_AS_INPUT_OPTION, "0", "10", "45", "90", "180"); rotateAdapter.addAll(SAME_AS_INPUT_OPTION, "0", "10", "45", "60", "90", "180");
trimCheckBox = findViewById(R.id.trim_checkbox);
trimCheckBox.setOnCheckedChangeListener(this::selectTrimBounds);
trimStartMs = C.TIME_UNSET;
trimEndMs = C.TIME_UNSET;
enableFallbackCheckBox = findViewById(R.id.enable_fallback_checkbox);
enableRequestSdrToneMappingCheckBox = findViewById(R.id.request_sdr_tone_mapping_checkbox);
enableRequestSdrToneMappingCheckBox.setEnabled(isRequestSdrToneMappingSupported());
findViewById(R.id.request_sdr_tone_mapping).setEnabled(isRequestSdrToneMappingSupported());
enableHdrEditingCheckBox = findViewById(R.id.hdr_editing_checkbox); enableHdrEditingCheckBox = findViewById(R.id.hdr_editing_checkbox);
demoEffectsSelections = new boolean[DEMO_EFFECTS.length];
selectDemoEffectsButton = findViewById(R.id.select_demo_effects_button);
selectDemoEffectsButton.setOnClickListener(this::selectDemoEffects);
} }
@Override @Override
@ -162,8 +215,8 @@ public final class ConfigurationActivity extends AppCompatActivity {
super.onResume(); super.onResume();
@Nullable Uri intentUri = getIntent().getData(); @Nullable Uri intentUri = getIntent().getData();
if (intentUri != null) { if (intentUri != null) {
checkNotNull(chooseFileButton).setEnabled(false); checkNotNull(selectFileButton).setEnabled(false);
checkNotNull(chosenFileTextView).setText(intentUri.toString()); checkNotNull(selectedFileTextView).setText(intentUri.toString());
} }
} }
@ -180,13 +233,16 @@ public final class ConfigurationActivity extends AppCompatActivity {
"audioMimeSpinner", "audioMimeSpinner",
"videoMimeSpinner", "videoMimeSpinner",
"resolutionHeightSpinner", "resolutionHeightSpinner",
"translateSpinner",
"scaleSpinner", "scaleSpinner",
"rotateSpinner", "rotateSpinner",
"enableHdrEditingCheckBox" "trimCheckBox",
"enableFallbackCheckBox",
"enableRequestSdrToneMappingCheckBox",
"enableHdrEditingCheckBox",
"demoEffectsSelections"
}) })
private void startTransformation(View view) { private void startTransformation(View view) {
Intent transformerIntent = new Intent(this, TransformerActivity.class); Intent transformerIntent = new Intent(/* packageContext= */ this, TransformerActivity.class);
Bundle bundle = new Bundle(); Bundle bundle = new Bundle();
bundle.putBoolean(SHOULD_REMOVE_AUDIO, removeAudioCheckbox.isChecked()); bundle.putBoolean(SHOULD_REMOVE_AUDIO, removeAudioCheckbox.isChecked());
bundle.putBoolean(SHOULD_REMOVE_VIDEO, removeVideoCheckbox.isChecked()); bundle.putBoolean(SHOULD_REMOVE_VIDEO, removeVideoCheckbox.isChecked());
@ -203,13 +259,6 @@ public final class ConfigurationActivity extends AppCompatActivity {
if (!SAME_AS_INPUT_OPTION.equals(selectedResolutionHeight)) { if (!SAME_AS_INPUT_OPTION.equals(selectedResolutionHeight)) {
bundle.putInt(RESOLUTION_HEIGHT, Integer.parseInt(selectedResolutionHeight)); bundle.putInt(RESOLUTION_HEIGHT, Integer.parseInt(selectedResolutionHeight));
} }
String selectedTranslate = String.valueOf(translateSpinner.getSelectedItem());
if (!SAME_AS_INPUT_OPTION.equals(selectedTranslate)) {
List<String> translateXY = Arrays.asList(selectedTranslate.split(", "));
checkState(translateXY.size() == 2);
bundle.putFloat(TRANSLATE_X, Float.parseFloat(translateXY.get(0)));
bundle.putFloat(TRANSLATE_Y, Float.parseFloat(translateXY.get(1)));
}
String selectedScale = String.valueOf(scaleSpinner.getSelectedItem()); String selectedScale = String.valueOf(scaleSpinner.getSelectedItem());
if (!SAME_AS_INPUT_OPTION.equals(selectedScale)) { if (!SAME_AS_INPUT_OPTION.equals(selectedScale)) {
List<String> scaleXY = Arrays.asList(selectedScale.split(", ")); List<String> scaleXY = Arrays.asList(selectedScale.split(", "));
@ -221,7 +270,19 @@ public final class ConfigurationActivity extends AppCompatActivity {
if (!SAME_AS_INPUT_OPTION.equals(selectedRotate)) { if (!SAME_AS_INPUT_OPTION.equals(selectedRotate)) {
bundle.putFloat(ROTATE_DEGREES, Float.parseFloat(selectedRotate)); bundle.putFloat(ROTATE_DEGREES, Float.parseFloat(selectedRotate));
} }
if (trimCheckBox.isChecked()) {
bundle.putLong(TRIM_START_MS, trimStartMs);
bundle.putLong(TRIM_END_MS, trimEndMs);
}
bundle.putBoolean(ENABLE_FALLBACK, enableFallbackCheckBox.isChecked());
bundle.putBoolean(
ENABLE_REQUEST_SDR_TONE_MAPPING, enableRequestSdrToneMappingCheckBox.isChecked());
bundle.putBoolean(ENABLE_HDR_EDITING, enableHdrEditingCheckBox.isChecked()); bundle.putBoolean(ENABLE_HDR_EDITING, enableHdrEditingCheckBox.isChecked());
bundle.putBooleanArray(DEMO_EFFECTS_SELECTIONS, demoEffectsSelections);
bundle.putFloat(PERIODIC_VIGNETTE_CENTER_X, periodicVignetteCenterX);
bundle.putFloat(PERIODIC_VIGNETTE_CENTER_Y, periodicVignetteCenterY);
bundle.putFloat(PERIODIC_VIGNETTE_INNER_RADIUS, periodicVignetteInnerRadius);
bundle.putFloat(PERIODIC_VIGNETTE_OUTER_RADIUS, periodicVignetteOuterRadius);
transformerIntent.putExtras(bundle); transformerIntent.putExtras(bundle);
@Nullable Uri intentUri = getIntent().getData(); @Nullable Uri intentUri = getIntent().getData();
@ -231,19 +292,82 @@ public final class ConfigurationActivity extends AppCompatActivity {
startActivity(transformerIntent); startActivity(transformerIntent);
} }
private void chooseFile(View view) { private void selectFile(View view) {
new AlertDialog.Builder(/* context= */ this) new AlertDialog.Builder(/* context= */ this)
.setTitle(R.string.choose_file_title) .setTitle(R.string.select_file_title)
.setSingleChoiceItems(URI_DESCRIPTIONS, inputUriPosition, this::selectFileInDialog) .setSingleChoiceItems(URI_DESCRIPTIONS, inputUriPosition, this::selectFileInDialog)
.setPositiveButton(android.R.string.ok, /* listener= */ null) .setPositiveButton(android.R.string.ok, /* listener= */ null)
.create() .create()
.show(); .show();
} }
@RequiresNonNull("chosenFileTextView") private void selectDemoEffects(View view) {
new AlertDialog.Builder(/* context= */ this)
.setTitle(R.string.select_demo_effects)
.setMultiChoiceItems(
DEMO_EFFECTS, checkNotNull(demoEffectsSelections), this::selectDemoEffect)
.setPositiveButton(android.R.string.ok, /* listener= */ null)
.create()
.show();
}
private void selectTrimBounds(View view, boolean isChecked) {
if (!isChecked) {
return;
}
View dialogView = getLayoutInflater().inflate(R.layout.trim_options, /* root= */ null);
RangeSlider radiusRangeSlider =
checkNotNull(dialogView.findViewById(R.id.trim_bounds_range_slider));
radiusRangeSlider.setValues(0f, 60f); // seconds
new AlertDialog.Builder(/* context= */ this)
.setView(dialogView)
.setPositiveButton(
android.R.string.ok,
(DialogInterface dialogInterface, int i) -> {
List<Float> radiusRange = radiusRangeSlider.getValues();
trimStartMs = 1000 * radiusRange.get(0).longValue();
trimEndMs = 1000 * radiusRange.get(1).longValue();
})
.create()
.show();
}
@RequiresNonNull("selectedFileTextView")
private void selectFileInDialog(DialogInterface dialog, int which) { private void selectFileInDialog(DialogInterface dialog, int which) {
inputUriPosition = which; inputUriPosition = which;
chosenFileTextView.setText(URI_DESCRIPTIONS[inputUriPosition]); selectedFileTextView.setText(URI_DESCRIPTIONS[inputUriPosition]);
}
@RequiresNonNull("demoEffectsSelections")
private void selectDemoEffect(DialogInterface dialog, int which, boolean isChecked) {
demoEffectsSelections[which] = isChecked;
if (!isChecked || which != PERIODIC_VIGNETTE_INDEX) {
return;
}
View dialogView =
getLayoutInflater().inflate(R.layout.periodic_vignette_options, /* root= */ null);
Slider centerXSlider =
checkNotNull(dialogView.findViewById(R.id.periodic_vignette_center_x_slider));
Slider centerYSlider =
checkNotNull(dialogView.findViewById(R.id.periodic_vignette_center_y_slider));
RangeSlider radiusRangeSlider =
checkNotNull(dialogView.findViewById(R.id.periodic_vignette_radius_range_slider));
radiusRangeSlider.setValues(0f, HALF_DIAGONAL);
new AlertDialog.Builder(/* context= */ this)
.setTitle(R.string.periodic_vignette_options)
.setView(dialogView)
.setPositiveButton(
android.R.string.ok,
(DialogInterface dialogInterface, int i) -> {
periodicVignetteCenterX = centerXSlider.getValue();
periodicVignetteCenterY = centerYSlider.getValue();
List<Float> radiusRange = radiusRangeSlider.getValues();
periodicVignetteInnerRadius = radiusRange.get(0);
periodicVignetteOuterRadius = radiusRange.get(1);
})
.create()
.show();
} }
@RequiresNonNull({ @RequiresNonNull({
@ -251,10 +375,11 @@ public final class ConfigurationActivity extends AppCompatActivity {
"audioMimeSpinner", "audioMimeSpinner",
"videoMimeSpinner", "videoMimeSpinner",
"resolutionHeightSpinner", "resolutionHeightSpinner",
"translateSpinner",
"scaleSpinner", "scaleSpinner",
"rotateSpinner", "rotateSpinner",
"enableHdrEditingCheckBox" "enableRequestSdrToneMappingCheckBox",
"enableHdrEditingCheckBox",
"selectDemoEffectsButton"
}) })
private void onRemoveAudio(View view) { private void onRemoveAudio(View view) {
if (((CheckBox) view).isChecked()) { if (((CheckBox) view).isChecked()) {
@ -270,10 +395,11 @@ public final class ConfigurationActivity extends AppCompatActivity {
"audioMimeSpinner", "audioMimeSpinner",
"videoMimeSpinner", "videoMimeSpinner",
"resolutionHeightSpinner", "resolutionHeightSpinner",
"translateSpinner",
"scaleSpinner", "scaleSpinner",
"rotateSpinner", "rotateSpinner",
"enableHdrEditingCheckBox" "enableRequestSdrToneMappingCheckBox",
"enableHdrEditingCheckBox",
"selectDemoEffectsButton"
}) })
private void onRemoveVideo(View view) { private void onRemoveVideo(View view) {
if (((CheckBox) view).isChecked()) { if (((CheckBox) view).isChecked()) {
@ -288,26 +414,34 @@ public final class ConfigurationActivity extends AppCompatActivity {
"audioMimeSpinner", "audioMimeSpinner",
"videoMimeSpinner", "videoMimeSpinner",
"resolutionHeightSpinner", "resolutionHeightSpinner",
"translateSpinner",
"scaleSpinner", "scaleSpinner",
"rotateSpinner", "rotateSpinner",
"enableHdrEditingCheckBox" "enableRequestSdrToneMappingCheckBox",
"enableHdrEditingCheckBox",
"selectDemoEffectsButton"
}) })
private void enableTrackSpecificOptions(boolean isAudioEnabled, boolean isVideoEnabled) { private void enableTrackSpecificOptions(boolean isAudioEnabled, boolean isVideoEnabled) {
audioMimeSpinner.setEnabled(isAudioEnabled); audioMimeSpinner.setEnabled(isAudioEnabled);
videoMimeSpinner.setEnabled(isVideoEnabled); videoMimeSpinner.setEnabled(isVideoEnabled);
resolutionHeightSpinner.setEnabled(isVideoEnabled); resolutionHeightSpinner.setEnabled(isVideoEnabled);
translateSpinner.setEnabled(isVideoEnabled);
scaleSpinner.setEnabled(isVideoEnabled); scaleSpinner.setEnabled(isVideoEnabled);
rotateSpinner.setEnabled(isVideoEnabled); rotateSpinner.setEnabled(isVideoEnabled);
enableRequestSdrToneMappingCheckBox.setEnabled(
isRequestSdrToneMappingSupported() && isVideoEnabled);
enableHdrEditingCheckBox.setEnabled(isVideoEnabled); enableHdrEditingCheckBox.setEnabled(isVideoEnabled);
selectDemoEffectsButton.setEnabled(isVideoEnabled);
findViewById(R.id.audio_mime_text_view).setEnabled(isAudioEnabled); findViewById(R.id.audio_mime_text_view).setEnabled(isAudioEnabled);
findViewById(R.id.video_mime_text_view).setEnabled(isVideoEnabled); findViewById(R.id.video_mime_text_view).setEnabled(isVideoEnabled);
findViewById(R.id.resolution_height_text_view).setEnabled(isVideoEnabled); findViewById(R.id.resolution_height_text_view).setEnabled(isVideoEnabled);
findViewById(R.id.translate).setEnabled(isVideoEnabled);
findViewById(R.id.scale).setEnabled(isVideoEnabled); findViewById(R.id.scale).setEnabled(isVideoEnabled);
findViewById(R.id.rotate).setEnabled(isVideoEnabled); findViewById(R.id.rotate).setEnabled(isVideoEnabled);
findViewById(R.id.request_sdr_tone_mapping)
.setEnabled(isRequestSdrToneMappingSupported() && isVideoEnabled);
findViewById(R.id.hdr_editing).setEnabled(isVideoEnabled); findViewById(R.id.hdr_editing).setEnabled(isVideoEnabled);
} }
private static boolean isRequestSdrToneMappingSupported() {
return Util.SDK_INT >= 31;
}
} }

View file

@ -0,0 +1,93 @@
/*
* Copyright 2022 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.transformerdemo;
import android.graphics.Matrix;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.transformer.GlMatrixTransformation;
import com.google.android.exoplayer2.transformer.MatrixTransformation;
import com.google.android.exoplayer2.util.Util;
/**
* Factory for {@link GlMatrixTransformation GlMatrixTransformations} and {@link
* MatrixTransformation MatrixTransformations} that create video effects by applying transformation
* matrices to the individual video frames.
*/
/* package */ final class MatrixTransformationFactory {
/**
* Returns a {@link MatrixTransformation} that rescales the frames over the first {@value
* #ZOOM_DURATION_SECONDS} seconds, such that the rectangle filled with the input frame increases
* linearly in size from a single point to filling the full output frame.
*/
public static MatrixTransformation createZoomInTransition() {
return MatrixTransformationFactory::calculateZoomInTransitionMatrix;
}
/**
* Returns a {@link MatrixTransformation} that crops frames to a rectangle that moves on an
* ellipse.
*/
public static MatrixTransformation createDizzyCropEffect() {
return MatrixTransformationFactory::calculateDizzyCropMatrix;
}
/**
* Returns a {@link GlMatrixTransformation} that rotates a frame in 3D around the y-axis and
* applies perspective projection to 2D.
*/
public static GlMatrixTransformation createSpin3dEffect() {
return MatrixTransformationFactory::calculate3dSpinMatrix;
}
private static final float ZOOM_DURATION_SECONDS = 2f;
private static final float DIZZY_CROP_ROTATION_PERIOD_US = 1_500_000f;
private static Matrix calculateZoomInTransitionMatrix(long presentationTimeUs) {
Matrix transformationMatrix = new Matrix();
float scale = Math.min(1, presentationTimeUs / (C.MICROS_PER_SECOND * ZOOM_DURATION_SECONDS));
transformationMatrix.postScale(/* sx= */ scale, /* sy= */ scale);
return transformationMatrix;
}
private static android.graphics.Matrix calculateDizzyCropMatrix(long presentationTimeUs) {
double theta = presentationTimeUs * 2 * Math.PI / DIZZY_CROP_ROTATION_PERIOD_US;
float centerX = 0.5f * (float) Math.cos(theta);
float centerY = 0.5f * (float) Math.sin(theta);
android.graphics.Matrix transformationMatrix = new android.graphics.Matrix();
transformationMatrix.postTranslate(/* dx= */ centerX, /* dy= */ centerY);
transformationMatrix.postScale(/* sx= */ 2f, /* sy= */ 2f);
return transformationMatrix;
}
private static float[] calculate3dSpinMatrix(long presentationTimeUs) {
float[] transformationMatrix = new float[16];
android.opengl.Matrix.frustumM(
transformationMatrix,
/* offset= */ 0,
/* left= */ -1f,
/* right= */ 1f,
/* bottom= */ -1f,
/* top= */ 1f,
/* near= */ 3f,
/* far= */ 5f);
android.opengl.Matrix.translateM(
transformationMatrix, /* mOffset= */ 0, /* x= */ 0f, /* y= */ 0f, /* z= */ -4f);
float theta = Util.usToMs(presentationTimeUs) / 10f;
android.opengl.Matrix.rotateM(
transformationMatrix, /* mOffset= */ 0, theta, /* x= */ 0f, /* y= */ 1f, /* z= */ 0f);
return transformationMatrix;
}
}

View file

@ -0,0 +1,115 @@
/*
* Copyright 2022 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.transformerdemo;
import static com.google.android.exoplayer2.util.Assertions.checkArgument;
import android.content.Context;
import android.opengl.GLES20;
import android.util.Size;
import com.google.android.exoplayer2.transformer.FrameProcessingException;
import com.google.android.exoplayer2.transformer.SingleFrameGlTextureProcessor;
import com.google.android.exoplayer2.util.GlProgram;
import com.google.android.exoplayer2.util.GlUtil;
import java.io.IOException;
/**
* A {@link SingleFrameGlTextureProcessor} that periodically dims the frames such that pixels are
* darker the further they are away from the frame center.
*/
/* package */ final class PeriodicVignetteProcessor implements SingleFrameGlTextureProcessor {
static {
GlUtil.glAssertionsEnabled = true;
}
private static final String VERTEX_SHADER_PATH = "vertex_shader_copy_es2.glsl";
private static final String FRAGMENT_SHADER_PATH = "fragment_shader_vignette_es2.glsl";
private static final float DIMMING_PERIOD_US = 5_600_000f;
private final GlProgram glProgram;
private final float minInnerRadius;
private final float deltaInnerRadius;
/**
* Creates a new instance.
*
* <p>The inner radius of the vignette effect oscillates smoothly between {@code minInnerRadius}
* and {@code maxInnerRadius}.
*
* <p>The pixels between the inner radius and the {@code outerRadius} are darkened linearly based
* on their distance from {@code innerRadius}. All pixels outside {@code outerRadius} are black.
*
* <p>The parameters are given in normalized texture coordinates from 0 to 1.
*
* @param context The {@link Context}.
* @param centerX The x-coordinate of the center of the effect.
* @param centerY The y-coordinate of the center of the effect.
* @param minInnerRadius The lower bound of the radius that is unaffected by the effect.
* @param maxInnerRadius The upper bound of the radius that is unaffected by the effect.
* @param outerRadius The radius after which all pixels are black.
* @throws IOException If a problem occurs while reading shader files.
*/
public PeriodicVignetteProcessor(
Context context,
float centerX,
float centerY,
float minInnerRadius,
float maxInnerRadius,
float outerRadius)
throws IOException {
checkArgument(minInnerRadius <= maxInnerRadius);
checkArgument(maxInnerRadius <= outerRadius);
this.minInnerRadius = minInnerRadius;
this.deltaInnerRadius = maxInnerRadius - minInnerRadius;
glProgram = new GlProgram(context, VERTEX_SHADER_PATH, FRAGMENT_SHADER_PATH);
glProgram.setFloatsUniform("uCenter", new float[] {centerX, centerY});
glProgram.setFloatsUniform("uOuterRadius", new float[] {outerRadius});
// Draw the frame on the entire normalized device coordinate space, from -1 to 1, for x and y.
glProgram.setBufferAttribute(
"aFramePosition",
GlUtil.getNormalizedCoordinateBounds(),
GlUtil.HOMOGENEOUS_COORDINATE_VECTOR_SIZE);
}
@Override
public Size configure(int inputWidth, int inputHeight) {
return new Size(inputWidth, inputHeight);
}
@Override
public void drawFrame(int inputTexId, long presentationTimeUs) throws FrameProcessingException {
try {
glProgram.use();
glProgram.setSamplerTexIdUniform("uTexSampler", inputTexId, /* texUnitIndex= */ 0);
double theta = presentationTimeUs * 2 * Math.PI / DIMMING_PERIOD_US;
float innerRadius =
minInnerRadius + deltaInnerRadius * (0.5f - 0.5f * (float) Math.cos(theta));
glProgram.setFloatsUniform("uInnerRadius", new float[] {innerRadius});
glProgram.bindAttributesAndUniforms();
// The four-vertex triangle strip forms a quad.
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, /* first= */ 0, /* count= */ 4);
} catch (GlUtil.GlException e) {
throw new FrameProcessingException(e);
}
}
@Override
public void release() {
if (glProgram != null) {
glProgram.delete();
}
}
}

View file

@ -19,9 +19,9 @@ import static android.Manifest.permission.READ_EXTERNAL_STORAGE;
import static com.google.android.exoplayer2.util.Assertions.checkNotNull; import static com.google.android.exoplayer2.util.Assertions.checkNotNull;
import android.app.Activity; import android.app.Activity;
import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.graphics.Matrix;
import android.net.Uri; import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.os.Handler; import android.os.Handler;
@ -32,13 +32,19 @@ import android.view.ViewGroup;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.app.AppCompatActivity;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ExoPlayer; import com.google.android.exoplayer2.ExoPlayer;
import com.google.android.exoplayer2.MediaItem; import com.google.android.exoplayer2.MediaItem;
import com.google.android.exoplayer2.transformer.DefaultEncoderFactory;
import com.google.android.exoplayer2.transformer.EncoderSelector;
import com.google.android.exoplayer2.transformer.GlEffect;
import com.google.android.exoplayer2.transformer.ProgressHolder; import com.google.android.exoplayer2.transformer.ProgressHolder;
import com.google.android.exoplayer2.transformer.SingleFrameGlTextureProcessor;
import com.google.android.exoplayer2.transformer.TransformationException; import com.google.android.exoplayer2.transformer.TransformationException;
import com.google.android.exoplayer2.transformer.TransformationRequest; import com.google.android.exoplayer2.transformer.TransformationRequest;
import com.google.android.exoplayer2.transformer.TransformationResult;
import com.google.android.exoplayer2.transformer.Transformer; import com.google.android.exoplayer2.transformer.Transformer;
import com.google.android.exoplayer2.ui.AspectRatioFrameLayout; import com.google.android.exoplayer2.ui.AspectRatioFrameLayout;
import com.google.android.exoplayer2.ui.StyledPlayerView; import com.google.android.exoplayer2.ui.StyledPlayerView;
@ -48,8 +54,10 @@ import com.google.android.exoplayer2.util.Util;
import com.google.android.material.progressindicator.LinearProgressIndicator; import com.google.android.material.progressindicator.LinearProgressIndicator;
import com.google.common.base.Stopwatch; import com.google.common.base.Stopwatch;
import com.google.common.base.Ticker; import com.google.common.base.Ticker;
import com.google.common.collect.ImmutableList;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.lang.reflect.Constructor;
import java.util.concurrent.CountDownLatch; import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull; import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
@ -145,9 +153,10 @@ public final class TransformerActivity extends AppCompatActivity {
externalCacheFile = createExternalCacheFile("transformer-output.mp4"); externalCacheFile = createExternalCacheFile("transformer-output.mp4");
String filePath = externalCacheFile.getAbsolutePath(); String filePath = externalCacheFile.getAbsolutePath();
@Nullable Bundle bundle = intent.getExtras(); @Nullable Bundle bundle = intent.getExtras();
MediaItem mediaItem = createMediaItem(bundle, uri);
Transformer transformer = createTransformer(bundle, filePath); Transformer transformer = createTransformer(bundle, filePath);
transformationStopwatch.start(); transformationStopwatch.start();
transformer.startTransformation(MediaItem.fromUri(uri), filePath); transformer.startTransformation(mediaItem, filePath);
this.transformer = transformer; this.transformer = transformer;
} catch (IOException e) { } catch (IOException e) {
throw new IllegalStateException(e); throw new IllegalStateException(e);
@ -174,6 +183,24 @@ public final class TransformerActivity extends AppCompatActivity {
}); });
} }
private MediaItem createMediaItem(@Nullable Bundle bundle, Uri uri) {
MediaItem.Builder mediaItemBuilder = new MediaItem.Builder().setUri(uri);
if (bundle != null) {
long trimStartMs =
bundle.getLong(ConfigurationActivity.TRIM_START_MS, /* defaultValue= */ C.TIME_UNSET);
long trimEndMs =
bundle.getLong(ConfigurationActivity.TRIM_END_MS, /* defaultValue= */ C.TIME_UNSET);
if (trimStartMs != C.TIME_UNSET && trimEndMs != C.TIME_UNSET) {
mediaItemBuilder.setClippingConfiguration(
new MediaItem.ClippingConfiguration.Builder()
.setStartPositionMs(trimStartMs)
.setEndPositionMs(trimEndMs)
.build());
}
}
return mediaItemBuilder.build();
}
// Create a cache file, resetting it if it already exists. // Create a cache file, resetting it if it already exists.
private File createExternalCacheFile(String fileName) throws IOException { private File createExternalCacheFile(String fileName) throws IOException {
File file = new File(getExternalCacheDir(), fileName); File file = new File(getExternalCacheDir(), fileName);
@ -214,22 +241,91 @@ public final class TransformerActivity extends AppCompatActivity {
if (resolutionHeight != C.LENGTH_UNSET) { if (resolutionHeight != C.LENGTH_UNSET) {
requestBuilder.setResolution(resolutionHeight); requestBuilder.setResolution(resolutionHeight);
} }
Matrix transformationMatrix = getTransformationMatrix(bundle);
if (!transformationMatrix.isIdentity()) { float scaleX = bundle.getFloat(ConfigurationActivity.SCALE_X, /* defaultValue= */ 1);
requestBuilder.setTransformationMatrix(transformationMatrix); float scaleY = bundle.getFloat(ConfigurationActivity.SCALE_Y, /* defaultValue= */ 1);
} requestBuilder.setScale(scaleX, scaleY);
float rotateDegrees =
bundle.getFloat(ConfigurationActivity.ROTATE_DEGREES, /* defaultValue= */ 0);
requestBuilder.setRotationDegrees(rotateDegrees);
requestBuilder.setEnableRequestSdrToneMapping(
bundle.getBoolean(ConfigurationActivity.ENABLE_REQUEST_SDR_TONE_MAPPING));
requestBuilder.experimental_setEnableHdrEditing( requestBuilder.experimental_setEnableHdrEditing(
bundle.getBoolean(ConfigurationActivity.ENABLE_HDR_EDITING)); bundle.getBoolean(ConfigurationActivity.ENABLE_HDR_EDITING));
transformerBuilder transformerBuilder
.setTransformationRequest(requestBuilder.build()) .setTransformationRequest(requestBuilder.build())
.setRemoveAudio(bundle.getBoolean(ConfigurationActivity.SHOULD_REMOVE_AUDIO)) .setRemoveAudio(bundle.getBoolean(ConfigurationActivity.SHOULD_REMOVE_AUDIO))
.setRemoveVideo(bundle.getBoolean(ConfigurationActivity.SHOULD_REMOVE_VIDEO)); .setRemoveVideo(bundle.getBoolean(ConfigurationActivity.SHOULD_REMOVE_VIDEO))
.setEncoderFactory(
new DefaultEncoderFactory(
EncoderSelector.DEFAULT,
/* enableFallback= */ bundle.getBoolean(ConfigurationActivity.ENABLE_FALLBACK)));
ImmutableList.Builder<GlEffect> effects = new ImmutableList.Builder<>();
@Nullable
boolean[] selectedEffects =
bundle.getBooleanArray(ConfigurationActivity.DEMO_EFFECTS_SELECTIONS);
if (selectedEffects != null) {
if (selectedEffects[0]) {
effects.add(MatrixTransformationFactory.createDizzyCropEffect());
}
if (selectedEffects[1]) {
try {
Class<?> clazz =
Class.forName("com.google.android.exoplayer2.transformerdemo.MediaPipeProcessor");
Constructor<?> constructor =
clazz.getConstructor(Context.class, String.class, String.class, String.class);
effects.add(
(Context context) -> {
try {
return (SingleFrameGlTextureProcessor)
constructor.newInstance(
context,
/* graphName= */ "edge_detector_mediapipe_graph.binarypb",
/* inputStreamName= */ "input_video",
/* outputStreamName= */ "output_video");
} catch (Exception e) {
runOnUiThread(() -> showToast(R.string.no_media_pipe_error));
throw new RuntimeException("Failed to load MediaPipe processor", e);
}
});
} catch (Exception e) {
showToast(R.string.no_media_pipe_error);
}
}
if (selectedEffects[2]) {
effects.add(
(Context context) ->
new PeriodicVignetteProcessor(
context,
bundle.getFloat(ConfigurationActivity.PERIODIC_VIGNETTE_CENTER_X),
bundle.getFloat(ConfigurationActivity.PERIODIC_VIGNETTE_CENTER_Y),
/* minInnerRadius= */ bundle.getFloat(
ConfigurationActivity.PERIODIC_VIGNETTE_INNER_RADIUS),
/* maxInnerRadius= */ bundle.getFloat(
ConfigurationActivity.PERIODIC_VIGNETTE_OUTER_RADIUS),
bundle.getFloat(ConfigurationActivity.PERIODIC_VIGNETTE_OUTER_RADIUS)));
}
if (selectedEffects[3]) {
effects.add(MatrixTransformationFactory.createSpin3dEffect());
}
if (selectedEffects[4]) {
effects.add(BitmapOverlayProcessor::new);
}
if (selectedEffects[5]) {
effects.add(MatrixTransformationFactory.createZoomInTransition());
}
transformerBuilder.setVideoFrameEffects(effects.build());
}
} }
return transformerBuilder return transformerBuilder
.addListener( .addListener(
new Transformer.Listener() { new Transformer.Listener() {
@Override @Override
public void onTransformationCompleted(MediaItem mediaItem) { public void onTransformationCompleted(
MediaItem mediaItem, TransformationResult transformationResult) {
TransformerActivity.this.onTransformationCompleted(filePath); TransformerActivity.this.onTransformationCompleted(filePath);
} }
@ -243,26 +339,6 @@ public final class TransformerActivity extends AppCompatActivity {
.build(); .build();
} }
private static Matrix getTransformationMatrix(Bundle bundle) {
Matrix transformationMatrix = new Matrix();
float translateX = bundle.getFloat(ConfigurationActivity.TRANSLATE_X, /* defaultValue= */ 0);
float translateY = bundle.getFloat(ConfigurationActivity.TRANSLATE_Y, /* defaultValue= */ 0);
// TODO(b/213198690): Get resolution for aspect ratio and scale all translations' translateX
// by this aspect ratio.
transformationMatrix.postTranslate(translateX, translateY);
float scaleX = bundle.getFloat(ConfigurationActivity.SCALE_X, /* defaultValue= */ 1);
float scaleY = bundle.getFloat(ConfigurationActivity.SCALE_Y, /* defaultValue= */ 1);
transformationMatrix.postScale(scaleX, scaleY);
float rotateDegrees =
bundle.getFloat(ConfigurationActivity.ROTATE_DEGREES, /* defaultValue= */ 0);
transformationMatrix.postRotate(rotateDegrees);
return transformationMatrix;
}
@RequiresNonNull({ @RequiresNonNull({
"informationTextView", "informationTextView",
"progressViewGroup", "progressViewGroup",
@ -335,6 +411,10 @@ public final class TransformerActivity extends AppCompatActivity {
} }
} }
private void showToast(@StringRes int messageResource) {
Toast.makeText(getApplicationContext(), getString(messageResource), Toast.LENGTH_LONG).show();
}
private final class DemoDebugViewProvider implements Transformer.DebugViewProvider { private final class DemoDebugViewProvider implements Transformer.DebugViewProvider {
@Nullable @Nullable

View file

@ -34,18 +34,18 @@
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" /> app:layout_constraintTop_toTopOf="parent" />
<Button <Button
android:id="@+id/choose_file_button" android:id="@+id/select_file_button"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="32dp" android:layout_marginTop="32dp"
android:layout_marginStart="32dp" android:layout_marginStart="32dp"
android:layout_marginEnd="32dp" android:layout_marginEnd="32dp"
android:text="@string/choose_file_title" android:text="@string/select_file_title"
app:layout_constraintTop_toBottomOf="@+id/configuration_text_view" app:layout_constraintTop_toBottomOf="@+id/configuration_text_view"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" /> app:layout_constraintStart_toStartOf="parent" />
<TextView <TextView
android:id="@+id/chosen_file_text_view" android:id="@+id/selected_file_text_view"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="12dp" android:layout_marginTop="12dp"
@ -57,14 +57,14 @@
android:gravity="center" android:gravity="center"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/choose_file_button" /> app:layout_constraintTop_toBottomOf="@+id/select_file_button" />
<androidx.core.widget.NestedScrollView <androidx.core.widget.NestedScrollView
android:layout_width="fill_parent" android:layout_width="fill_parent"
android:layout_height="0dp" android:layout_height="0dp"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/chosen_file_text_view" app:layout_constraintTop_toBottomOf="@+id/selected_file_text_view"
app:layout_constraintBottom_toTopOf="@+id/transform_button"> app:layout_constraintBottom_toTopOf="@+id/select_demo_effects_button">
<TableLayout <TableLayout
android:layout_width="fill_parent" android:layout_width="fill_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
@ -137,17 +137,6 @@
android:layout_gravity="right|center_vertical" android:layout_gravity="right|center_vertical"
android:gravity="right" /> android:gravity="right" />
</TableRow> </TableRow>
<TableRow
android:layout_weight="1"
android:gravity="center_vertical" >
<TextView
android:id="@+id/translate"
android:text="@string/translate"/>
<Spinner
android:id="@+id/translate_spinner"
android:layout_gravity="right|center_vertical"
android:gravity="right" />
</TableRow>
<TableRow <TableRow
android:layout_weight="1" android:layout_weight="1"
android:gravity="center_vertical" > android:gravity="center_vertical" >
@ -170,6 +159,36 @@
android:layout_gravity="right|center_vertical" android:layout_gravity="right|center_vertical"
android:gravity="right" /> android:gravity="right" />
</TableRow> </TableRow>
<TableRow
android:layout_weight="1"
android:gravity="center_vertical" >
<TextView
android:id="@+id/trim"
android:text="@string/trim" />
<CheckBox
android:id="@+id/trim_checkbox"
android:layout_gravity="right" />
</TableRow>
<TableRow
android:layout_weight="1"
android:gravity="center_vertical" >
<TextView
android:text="@string/enable_fallback" />
<CheckBox
android:id="@+id/enable_fallback_checkbox"
android:layout_gravity="right"
android:checked="true"/>
</TableRow>
<TableRow
android:layout_weight="1"
android:gravity="center_vertical" >
<TextView
android:id="@+id/request_sdr_tone_mapping"
android:text="@string/request_sdr_tone_mapping" />
<CheckBox
android:id="@+id/request_sdr_tone_mapping_checkbox"
android:layout_gravity="right" />
</TableRow>
<TableRow <TableRow
android:layout_weight="1" android:layout_weight="1"
android:gravity="center_vertical" > android:gravity="center_vertical" >
@ -182,6 +201,17 @@
</TableRow> </TableRow>
</TableLayout> </TableLayout>
</androidx.core.widget.NestedScrollView> </androidx.core.widget.NestedScrollView>
<Button
android:id="@+id/select_demo_effects_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="32dp"
android:layout_marginStart="32dp"
android:layout_marginEnd="32dp"
android:text="@string/select_demo_effects"
app:layout_constraintBottom_toTopOf="@+id/transform_button"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<Button <Button
android:id="@+id/transform_button" android:id="@+id/transform_button"
android:layout_width="wrap_content" android:layout_width="wrap_content"

View file

@ -0,0 +1,72 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright 2022 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.
-->
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
tools:context=".ConfigurationActivity">
<TableLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:stretchColumns="1"
android:layout_marginTop="32dp"
android:measureWithLargestChild="true"
android:paddingLeft="24dp"
android:paddingRight="12dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent">
<TableRow
android:layout_weight="1"
android:gravity="center_vertical" >
<TextView
android:text="@string/center_x" />
<com.google.android.material.slider.Slider
android:id="@+id/periodic_vignette_center_x_slider"
android:valueFrom="0.0"
android:value="0.5"
android:valueTo="1.0"
android:layout_gravity="right"/>
</TableRow>
<TableRow
android:layout_weight="1"
android:gravity="center_vertical" >
<TextView
android:text="@string/center_y" />
<com.google.android.material.slider.Slider
android:id="@+id/periodic_vignette_center_y_slider"
android:valueFrom="0.0"
android:value="0.5"
android:valueTo="1.0"
android:layout_gravity="right"/>
</TableRow>
<TableRow
android:layout_weight="1"
android:gravity="center_vertical" >
<TextView
android:text="@string/radius_range" />
<com.google.android.material.slider.RangeSlider
android:id="@+id/periodic_vignette_radius_range_slider"
android:valueFrom="0.0"
android:valueTo="1.414"
android:layout_gravity="right"/>
</TableRow>
</TableLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -0,0 +1,48 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright 2022 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.
-->
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
tools:context=".ConfigurationActivity">
<TableLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:stretchColumns="1"
android:layout_marginTop="32dp"
android:measureWithLargestChild="true"
android:paddingLeft="24dp"
android:paddingRight="12dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent">
<TableRow
android:layout_weight="1"
android:gravity="center_vertical" >
<TextView
android:text="@string/trim_range" />
<com.google.android.material.slider.RangeSlider
android:id="@+id/trim_bounds_range_slider"
android:valueFrom="0.0"
android:valueTo="60.0"
android:layout_gravity="right"/>
</TableRow>
</TableLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -17,22 +17,31 @@
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" translatable="false">Transformer Demo</string> <string name="app_name" translatable="false">Transformer Demo</string>
<string name="configuration" translatable="false">Configuration</string> <string name="configuration" translatable="false">Configuration</string>
<string name="choose_file_title" translatable="false">Choose file</string> <string name="select_file_title" translatable="false">Choose file</string>
<string name="remove_audio" translatable="false">Remove audio</string> <string name="remove_audio" translatable="false">Remove audio</string>
<string name="remove_video" translatable="false">Remove video</string> <string name="remove_video" translatable="false">Remove video</string>
<string name="flatten_for_slow_motion" translatable="false">Flatten for slow motion</string> <string name="flatten_for_slow_motion" translatable="false">Flatten for slow motion</string>
<string name="audio_mime" translatable="false">Output audio MIME type</string> <string name="audio_mime" translatable="false">Output audio MIME type</string>
<string name="video_mime" translatable="false">Output video MIME type</string> <string name="video_mime" translatable="false">Output video MIME type</string>
<string name="resolution_height" translatable="false">Output video resolution</string> <string name="resolution_height" translatable="false">Output video resolution</string>
<string name="translate" translatable="false">Translate video</string>
<string name="scale" translatable="false">Scale video</string> <string name="scale" translatable="false">Scale video</string>
<string name="rotate" translatable="false">Rotate video (degrees)</string> <string name="rotate" translatable="false">Rotate video (degrees)</string>
<string name="transform" translatable="false">Transform</string> <string name="enable_fallback" translatable="false">Enable fallback</string>
<string name="trim" translatable="false">Trim</string>
<string name="request_sdr_tone_mapping" translatable="false">Request SDR tone-mapping (API 31+)</string>
<string name="hdr_editing" translatable="false">[Experimental] HDR editing</string> <string name="hdr_editing" translatable="false">[Experimental] HDR editing</string>
<string name="select_demo_effects" translatable="false">Add demo effects</string>
<string name="periodic_vignette_options" translatable="false">Periodic vignette options</string>
<string name="no_media_pipe_error" translatable="false">Failed to load MediaPipe processor. Check the README for instructions.</string>
<string name="transform" translatable="false">Transform</string>
<string name="debug_preview" translatable="false">Debug preview:</string> <string name="debug_preview" translatable="false">Debug preview:</string>
<string name="debug_preview_not_available" translatable="false">No debug preview available.</string> <string name="debug_preview_not_available" translatable="false">No debug preview available.</string>
<string name="transformation_started" translatable="false">Transformation started</string> <string name="transformation_started" translatable="false">Transformation started</string>
<string name="transformation_timer" translatable="false">Transformation started %d seconds ago.</string> <string name="transformation_timer" translatable="false">Transformation started %d seconds ago.</string>
<string name="transformation_completed" translatable="false">Transformation completed in %d seconds.</string> <string name="transformation_completed" translatable="false">Transformation completed in %d seconds.</string>
<string name="transformation_error" translatable="false">Transformation error</string> <string name="transformation_error" translatable="false">Transformation error</string>
<string name="center_x">Center X</string>
<string name="center_y">Center Y</string>
<string name="radius_range">Radius range</string>
<string name="trim_range">Bounds in seconds</string>
</resources> </resources>

View file

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright 2022 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.
-->
<manifest package="com.google.android.exoplayer2.transformerdemo">
<uses-sdk />
</manifest>

View file

@ -0,0 +1,13 @@
# Demo MediaPipe graph that shows edges using a SobelEdgesCalculator.
input_stream: "input_video"
output_stream: "output_video"
node: {
calculator: "LuminanceCalculator"
input_stream: "input_video"
output_stream: "luma_video"
}
node: {
calculator: "SobelEdgesCalculator"
input_stream: "luma_video"
output_stream: "output_video"
}

View file

@ -0,0 +1,165 @@
/*
* Copyright 2022 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.transformerdemo;
import static com.google.android.exoplayer2.util.Assertions.checkState;
import static com.google.android.exoplayer2.util.Assertions.checkStateNotNull;
import android.content.Context;
import android.opengl.EGL14;
import android.opengl.GLES20;
import android.util.Size;
import com.google.android.exoplayer2.transformer.FrameProcessingException;
import com.google.android.exoplayer2.transformer.SingleFrameGlTextureProcessor;
import com.google.android.exoplayer2.util.ConditionVariable;
import com.google.android.exoplayer2.util.GlProgram;
import com.google.android.exoplayer2.util.GlUtil;
import com.google.android.exoplayer2.util.LibraryLoader;
import com.google.mediapipe.components.FrameProcessor;
import com.google.mediapipe.framework.AndroidAssetUtil;
import com.google.mediapipe.framework.AppTextureFrame;
import com.google.mediapipe.framework.TextureFrame;
import com.google.mediapipe.glutil.EglManager;
import java.io.IOException;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
/**
* Runs a MediaPipe graph on input frames. The implementation is currently limited to graphs that
* can immediately produce one output frame per input frame.
*/
/* package */ final class MediaPipeProcessor implements SingleFrameGlTextureProcessor {
private static final LibraryLoader LOADER =
new LibraryLoader("mediapipe_jni") {
@Override
protected void loadLibrary(String name) {
System.loadLibrary(name);
}
};
static {
// Not all build configurations require OpenCV to be loaded separately, so attempt to load the
// library but ignore the error if it's not present.
try {
System.loadLibrary("opencv_java3");
} catch (UnsatisfiedLinkError e) {
// Do nothing.
}
}
private static final String COPY_VERTEX_SHADER_NAME = "vertex_shader_copy_es2.glsl";
private static final String COPY_FRAGMENT_SHADER_NAME = "shaders/fragment_shader_copy_es2.glsl";
private final ConditionVariable frameProcessorConditionVariable;
private final FrameProcessor frameProcessor;
private final GlProgram glProgram;
private int inputWidth;
private int inputHeight;
private @MonotonicNonNull TextureFrame outputFrame;
private @MonotonicNonNull RuntimeException frameProcessorPendingError;
/**
* Creates a new texture processor that wraps a MediaPipe graph.
*
* @param context The {@link Context}.
* @param graphName Name of a MediaPipe graph asset to load.
* @param inputStreamName Name of the input video stream in the graph.
* @param outputStreamName Name of the input video stream in the graph.
* @throws IOException If a problem occurs while reading shader files or initializing MediaPipe
* resources.
*/
public MediaPipeProcessor(
Context context, String graphName, String inputStreamName, String outputStreamName)
throws IOException {
checkState(LOADER.isAvailable());
frameProcessorConditionVariable = new ConditionVariable();
AndroidAssetUtil.initializeNativeAssetManager(context);
EglManager eglManager = new EglManager(EGL14.eglGetCurrentContext());
frameProcessor =
new FrameProcessor(
context, eglManager.getNativeContext(), graphName, inputStreamName, outputStreamName);
// Unblock drawFrame when there is an output frame or an error.
frameProcessor.setConsumer(
frame -> {
outputFrame = frame;
frameProcessorConditionVariable.open();
});
frameProcessor.setAsynchronousErrorListener(
error -> {
frameProcessorPendingError = error;
frameProcessorConditionVariable.open();
});
glProgram = new GlProgram(context, COPY_VERTEX_SHADER_NAME, COPY_FRAGMENT_SHADER_NAME);
}
@Override
public Size configure(int inputWidth, int inputHeight) {
this.inputWidth = inputWidth;
this.inputHeight = inputHeight;
return new Size(inputWidth, inputHeight);
}
@Override
public void drawFrame(int inputTexId, long presentationTimeUs) throws FrameProcessingException {
frameProcessorConditionVariable.close();
// Pass the input frame to MediaPipe.
AppTextureFrame appTextureFrame = new AppTextureFrame(inputTexId, inputWidth, inputHeight);
appTextureFrame.setTimestamp(presentationTimeUs);
checkStateNotNull(frameProcessor).onNewFrame(appTextureFrame);
// Wait for output to be passed to the consumer.
try {
frameProcessorConditionVariable.block();
} catch (InterruptedException e) {
// Propagate the interrupted flag so the next blocking operation will throw.
// TODO(b/230469581): The next processor that runs will not have valid input due to returning
// early here. This could be fixed by checking for interruption in the outer loop that runs
// through the texture processors.
Thread.currentThread().interrupt();
return;
}
if (frameProcessorPendingError != null) {
throw new FrameProcessingException(frameProcessorPendingError);
}
// Copy from MediaPipe's output texture to the current output.
try {
checkStateNotNull(glProgram).use();
glProgram.setSamplerTexIdUniform(
"uTexSampler", checkStateNotNull(outputFrame).getTextureName(), /* texUnitIndex= */ 0);
glProgram.setBufferAttribute(
"aFramePosition",
GlUtil.getNormalizedCoordinateBounds(),
GlUtil.HOMOGENEOUS_COORDINATE_VECTOR_SIZE);
glProgram.bindAttributesAndUniforms();
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, /* first= */ 0, /* count= */ 4);
GlUtil.checkGlError();
} catch (GlUtil.GlException e) {
throw new FrameProcessingException(e);
} finally {
checkStateNotNull(outputFrame).release();
}
}
@Override
public void release() {
checkStateNotNull(frameProcessor).close();
}
}

View file

@ -51,8 +51,8 @@ build and inject a `DefaultMediaSourceFactory` configured with an
~~~ ~~~
MediaSource.Factory mediaSourceFactory = MediaSource.Factory mediaSourceFactory =
new DefaultMediaSourceFactory(context) new DefaultMediaSourceFactory(context)
.setAdsLoaderProvider(adsLoaderProvider) .setLocalAdInsertionComponents(
.setAdViewProvider(playerView); adsLoaderProvider, /* adViewProvider= */ playerView);
ExoPlayer player = new ExoPlayer.Builder(context) ExoPlayer player = new ExoPlayer.Builder(context)
.setMediaSourceFactory(mediaSourceFactory) .setMediaSourceFactory(mediaSourceFactory)
.build(); .build();
@ -220,7 +220,7 @@ server-side ad insertion `MediaSource` for URIs using the `ssai://` scheme:
Player player = Player player =
new ExoPlayer.Builder(context) new ExoPlayer.Builder(context)
.setMediaSourceFactory( .setMediaSourceFactory(
new DefaultMediaSourceFactory(dataSourceFactory) new DefaultMediaSourceFactory(context)
.setServerSideAdInsertionMediaSourceFactory(ssaiFactory)) .setServerSideAdInsertionMediaSourceFactory(ssaiFactory))
.build(); .build();
``` ```
@ -241,7 +241,7 @@ In order to use this class, you need to set up the
``` ```
// MediaSource.Factory to load the actual media stream. // MediaSource.Factory to load the actual media stream.
DefaultMediaSourceFactory defaultMediaSourceFactory = DefaultMediaSourceFactory defaultMediaSourceFactory =
new DefaultMediaSourceFactory(dataSourceFactory); new DefaultMediaSourceFactory(context);
// AdsLoader that can be reused for multiple playbacks. // AdsLoader that can be reused for multiple playbacks.
ImaServerSideAdInsertionMediaSource.AdsLoader adsLoader = ImaServerSideAdInsertionMediaSource.AdsLoader adsLoader =
new ImaServerSideAdInsertionMediaSource.AdsLoader.Builder(context, adViewProvider) new ImaServerSideAdInsertionMediaSource.AdsLoader.Builder(context, adViewProvider)
@ -267,7 +267,10 @@ with `ImaServerSideAdInsertionUriBuilder`:
``` ```
Uri ssaiUri = Uri ssaiUri =
new ImaServerSideAdInsertionUriBuilder().setAssetKey(assetKey).build(); new ImaServerSideAdInsertionUriBuilder()
.setAssetKey(assetKey)
.setFormat(C.TYPE_HLS)
.build();
player.setMediaItem(MediaItem.fromUri(ssaiUri)); player.setMediaItem(MediaItem.fromUri(ssaiUri));
``` ```

View file

@ -43,32 +43,7 @@ described below.
### Configuring the network stack ### ### Configuring the network stack ###
ExoPlayer supports Android's default network stack, as well as Cronet and We have a page about [customizing the network stack used by ExoPlayer].
OkHttp. In each case it's possible to customize the network stack for your use
case. The following example shows how to customize the player to use Android's
default network stack with cross-protocol redirects enabled:
~~~
// Build a HttpDataSource.Factory with cross-protocol redirects enabled.
HttpDataSource.Factory httpDataSourceFactory =
new DefaultHttpDataSource.Factory().setAllowCrossProtocolRedirects(true);
// Wrap the HttpDataSource.Factory in a DefaultDataSource.Factory, which adds in
// support for requesting data from other sources (e.g., files, resources, etc).
DefaultDataSource.Factory dataSourceFactory =
new DefaultDataSource.Factory(context, httpDataSourceFactory);
// Inject the DefaultDataSourceFactory when creating the player.
ExoPlayer player =
new ExoPlayer.Builder(context)
.setMediaSourceFactory(new DefaultMediaSourceFactory(dataSourceFactory))
.build();
~~~
{: .language-java}
The same approach can be used to configure and inject `HttpDataSource.Factory`
implementations provided by the [Cronet extension] and the [OkHttp extension],
depending on your preferred choice of network stack.
### Caching data loaded from the network ### ### Caching data loaded from the network ###
@ -84,7 +59,8 @@ DataSource.Factory cacheDataSourceFactory =
ExoPlayer player = new ExoPlayer.Builder(context) ExoPlayer player = new ExoPlayer.Builder(context)
.setMediaSourceFactory( .setMediaSourceFactory(
new DefaultMediaSourceFactory(cacheDataSourceFactory)) new DefaultMediaSourceFactory(context)
.setDataSourceFactory(cacheDataSourceFactory))
.build(); .build();
~~~ ~~~
{: .language-java} {: .language-java}
@ -108,7 +84,9 @@ DataSource.Factory dataSourceFactory = () -> {
}; };
ExoPlayer player = new ExoPlayer.Builder(context) ExoPlayer player = new ExoPlayer.Builder(context)
.setMediaSourceFactory(new DefaultMediaSourceFactory(dataSourceFactory)) .setMediaSourceFactory(
new DefaultMediaSourceFactory(context)
.setDataSourceFactory(dataSourceFactory))
.build(); .build();
~~~ ~~~
{: .language-java} {: .language-java}
@ -206,6 +184,56 @@ DefaultExtractorsFactory extractorsFactory =
The `ExtractorsFactory` can then be injected via `DefaultMediaSourceFactory` as The `ExtractorsFactory` can then be injected via `DefaultMediaSourceFactory` as
described for customizing extractor flags above. described for customizing extractor flags above.
### Enabling asynchronous buffer queueing ###
Asynchronous buffer queueing is an enhancement in ExoPlayer's rendering
pipeline, which operates `MediaCodec` instances in [asynchronous mode][] and
uses additional threads to schedule decoding and rendering of data. Enabling it
can reduce dropped frames and audio underruns.
Asynchronous buffer queueing is enabled by default on devices running Android 12
and above, and can be enabled manually from Android 6. Consider enabling the
feature for specific devices on which you observe dropped frames or audio
underruns, particularly when playing DRM protected or high frame rate content.
In the simplest case, you need to inject a `DefaultRenderersFactory` to the
player as follows:
~~~
DefaultRenderersFactory renderersFactory =
new DefaultRenderersFactory(context)
.forceEnableMediaCodecAsynchronousQueueing();
ExoPlayer exoPlayer = new ExoPlayer.Builder(context, renderersFactory).build();
~~~
{: .language-java}
If you're instantiating renderers directly, pass a
`AsynchronousMediaCodecAdapter.Factory` to the `MediaCodecVideoRenderer` and
`MediaCodecAudioRenderer` constructors.
### Intercepting method calls with `ForwardingPlayer` ###
You can customize some of the behavior of a `Player` instance by wrapping it in
a subclass of `ForwardingPlayer` and overriding methods in order to do any of
the following:
* Access parameters before passing them to the delegate `Player`.
* Access the return value from the delegate `Player` before returning it.
* Re-implement the method completely.
When overriding `ForwardingPlayer` methods it's important to ensure the
implementation remains self-consistent and compliant with the `Player`
interface, especially when dealing with methods that are intended to have
identical or related behavior. For example, if you want to override every 'play'
operation, you need to override both `ForwardingPlayer.play` and
`ForwardingPlayer.setPlayWhenReady`, because a caller will expect the behavior
of these methdods to be identical when `playWhenReady = true`. Similarly, if you
want to change the seek-forward increment you need to override both
`ForwardingPlayer.seekForward` to perform a seek with your customized increment,
and `ForwardingPlayer.getSeekForwardIncrement` in order to report the correct
customized increment back to the caller.
## MediaSource customization ## ## MediaSource customization ##
The examples above inject customized components for use during playback of all The examples above inject customized components for use during playback of all
@ -270,8 +298,8 @@ When building custom components, we recommend the following:
ensures that they are executed in order with any other operations being ensures that they are executed in order with any other operations being
performed on the player. performed on the player.
[Cronet extension]: https://github.com/google/ExoPlayer/tree/release-v2/extensions/cronet [customizing the network stack used by ExoPlayer]: {{ site.baseurl }}/network-stacks.html
[OkHttp extension]: https://github.com/google/ExoPlayer/tree/release-v2/extensions/okhttp
[LoadErrorHandlingPolicy]: {{ site.exo_sdk }}/upstream/LoadErrorHandlingPolicy.html [LoadErrorHandlingPolicy]: {{ site.exo_sdk }}/upstream/LoadErrorHandlingPolicy.html
[media source based playlist API]: {{ site.baseurl }}/media-sources.html#media-source-based-playlist-api [media source based playlist API]: {{ site.baseurl }}/media-sources.html#media-source-based-playlist-api
[asynchronous mode]: https://developer.android.com/reference/android/media/MediaCodec#asynchronous-processing-using-buffers

View file

@ -9,13 +9,10 @@ issues. `EventLogger` implements `AnalyticsListener`, so registering an instance
with an `ExoPlayer` is easy: with an `ExoPlayer` is easy:
``` ```
player.addAnalyticsListener(new EventLogger(trackSelector)); player.addAnalyticsListener(new EventLogger());
``` ```
{: .language-java} {: .language-java}
Passing the `trackSelector` enables additional logging, but is optional and so
`null` can be passed instead.
The easiest way to observe the log is using Android Studio's [logcat tab][]. You The easiest way to observe the log is using Android Studio's [logcat tab][]. You
can select your app as debuggable process by the package name ( can select your app as debuggable process by the package name (
`com.google.android.exoplayer2.demo` if using the demo app) and tell the logcat `com.google.android.exoplayer2.demo` if using the demo app) and tell the logcat
@ -80,20 +77,16 @@ logging for an adaptive stream:
``` ```
EventLogger: tracks [eventTime=0.30, mediaPos=0.00, window=0, period=0, EventLogger: tracks [eventTime=0.30, mediaPos=0.00, window=0, period=0,
EventLogger: MediaCodecVideoRenderer [ EventLogger: group [
EventLogger: Group:0, adaptive_supported=YES [ EventLogger: [X] Track:0, id=133, mimeType=video/avc, bitrate=261112, codecs=avc1.4d4015, res=426x240, fps=30.0, supported=YES
EventLogger: [X] Track:0, id=133, mimeType=video/avc, bitrate=261112, codecs=avc1.4d4015, res=426x240, fps=30.0, supported=YES EventLogger: [X] Track:1, id=134, mimeType=video/avc, bitrate=671331, codecs=avc1.4d401e, res=640x360, fps=30.0, supported=YES
EventLogger: [X] Track:1, id=134, mimeType=video/avc, bitrate=671331, codecs=avc1.4d401e, res=640x360, fps=30.0, supported=YES EventLogger: [X] Track:2, id=135, mimeType=video/avc, bitrate=1204535, codecs=avc1.4d401f, res=854x480, fps=30.0, supported=YES
EventLogger: [X] Track:2, id=135, mimeType=video/avc, bitrate=1204535, codecs=avc1.4d401f, res=854x480, fps=30.0, supported=YES EventLogger: [X] Track:3, id=160, mimeType=video/avc, bitrate=112329, codecs=avc1.4d400c, res=256x144, fps=30.0, supported=YES
EventLogger: [X] Track:3, id=160, mimeType=video/avc, bitrate=112329, codecs=avc1.4d400c, res=256x144, fps=30.0, supported=YES EventLogger: [ ] Track:4, id=136, mimeType=video/avc, bitrate=2400538, codecs=avc1.4d401f, res=1280x720, fps=30.0, supported=NO_EXCEEDS_CAPABILITIES
EventLogger: [ ] Track:4, id=136, mimeType=video/avc, bitrate=2400538, codecs=avc1.4d401f, res=1280x720, fps=30.0, supported=NO_EXCEEDS_CAPABILITIES
EventLogger: ]
EventLogger: ] EventLogger: ]
EventLogger: MediaCodecAudioRenderer [ EventLogger: group [
EventLogger: Group:0, adaptive_supported=YES_NOT_SEAMLESS [ EventLogger: [ ] Track:0, id=139, mimeType=audio/mp4a-latm, bitrate=48582, codecs=mp4a.40.5, channels=2, sample_rate=22050, supported=YES
EventLogger: [ ] Track:0, id=139, mimeType=audio/mp4a-latm, bitrate=48582, codecs=mp4a.40.5, channels=2, sample_rate=22050, supported=YES EventLogger: [X] Track:1, id=140, mimeType=audio/mp4a-latm, bitrate=127868, codecs=mp4a.40.2, channels=2, sample_rate=44100, supported=YES
EventLogger: [X] Track:1, id=140, mimeType=audio/mp4a-latm, bitrate=127868, codecs=mp4a.40.2, channels=2, sample_rate=44100, supported=YES
EventLogger: ]
EventLogger: ] EventLogger: ]
EventLogger: ] EventLogger: ]
``` ```

View file

@ -319,7 +319,8 @@ DataSource.Factory cacheDataSourceFactory =
ExoPlayer player = new ExoPlayer.Builder(context) ExoPlayer player = new ExoPlayer.Builder(context)
.setMediaSourceFactory( .setMediaSourceFactory(
new DefaultMediaSourceFactory(cacheDataSourceFactory)) new DefaultMediaSourceFactory(context)
.setDataSourceFactory(cacheDataSourceFactory))
.build(); .build();
~~~ ~~~
{: .language-java} {: .language-java}

View file

@ -94,11 +94,18 @@ DrmSessionManager customDrmSessionManager =
new CustomDrmSessionManager(/* ... */); new CustomDrmSessionManager(/* ... */);
// Pass a drm session manager provider to the media source factory. // Pass a drm session manager provider to the media source factory.
MediaSource.Factory mediaSourceFactory = MediaSource.Factory mediaSourceFactory =
new DefaultMediaSourceFactory(dataSourceFactory) new DefaultMediaSourceFactory(context)
.setDrmSessionManagerProvider(mediaItem -> customDrmSessionManager); .setDrmSessionManagerProvider(mediaItem -> customDrmSessionManager);
~~~ ~~~
{: .language-java} {: .language-java}
### Improving playback performance ###
If you're experiencing video stuttering on a device running Android 6 to 11 when
playing DRM protected content, you can try [enabling asynchronous buffer
queueing].
[main demo app]: {{ site.release_v2 }}/demos/main [main demo app]: {{ site.release_v2 }}/demos/main
[`MediaDrm`]: {{ site.android_sdk }}/android/media/MediaDrm.html [`MediaDrm`]: {{ site.android_sdk }}/android/media/MediaDrm.html
[used when building the player]: {{ site.baseurl }}/media-sources.html#customizing-media-source-creation [used when building the player]: {{ site.baseurl }}/media-sources.html#customizing-media-source-creation
[enabling asynchronous buffer queueing]: {{ site.baseurl }}/customization.html#enabling-asynchronous-buffer-queueing

View file

@ -88,8 +88,6 @@ public void onPlayerError(PlaybackException error) {
if (cause instanceof HttpDataSourceException) { if (cause instanceof HttpDataSourceException) {
// An HTTP error occurred. // An HTTP error occurred.
HttpDataSourceException httpError = (HttpDataSourceException) cause; HttpDataSourceException httpError = (HttpDataSourceException) cause;
// This is the request for which the error occurred.
DataSpec requestDataSpec = httpError.dataSpec;
// It's possible to find out more about the error both by casting and by // It's possible to find out more about the error both by casting and by
// querying the cause. // querying the cause.
if (httpError instanceof HttpDataSource.InvalidResponseCodeException) { if (httpError instanceof HttpDataSource.InvalidResponseCodeException) {
@ -193,12 +191,11 @@ logging purposes. It can be added to an `ExoPlayer` to enable useful
additional logging with a single line. additional logging with a single line.
``` ```
player.addAnalyticsListener(new EventLogger(trackSelector)); player.addAnalyticsListener(new EventLogger());
``` ```
{: .language-java } {: .language-java }
Passing the `trackSelector` enables additional logging, but is optional and so See the [debug logging page][] for more details.
`null` can be passed instead. See the [debug logging page][] for more details.
## Firing events at specified playback positions ## ## Firing events at specified playback positions ##

View file

@ -16,13 +16,6 @@ and can only be played at one position. The documentation on this page is only
relevant to adaptive live streams. relevant to adaptive live streams.
{:.info} {:.info}
ExoPlayer adjusts the live offset by slightly changing the playback speed.
The player will try to match user and media preferences, but will also try to
react to changing network conditions. For example, if rebuffers occur during
playback, the player will move further away from the live edge. If there is
enough available buffer over a longer period of time, the player will move
closer to the live edge again.
## Detecting and monitoring live playbacks ## ## Detecting and monitoring live playbacks ##
Every time a live window is updated, registered `Player.Listener` instances Every time a live window is updated, registered `Player.Listener` instances
@ -87,12 +80,16 @@ components to support additional modes when playing live streams.
## Configuring live playback parameters ## ## Configuring live playback parameters ##
By default, ExoPlayer uses live playback parameters defined by the media. If you ExoPlayer uses some parameters to control the offset of the playback position
want to configure the live playback parameters yourself, you can set them on a from the live edge, and the range of playback speeds that can be used to
per `MediaItem` basis by calling `MediaItem.Builder.setLiveConfiguration`. If adjust this offset.
you'd like to set these values globally for all items, you can set them on the
`DefaultMediaSourceFactory` provided to the player. In both cases, the provided ExoPlayer gets values for these parameters from three places, in descending
values will override parameters defined by the media. order of priority (the first value found is used):
* Per `MediaItem` values passed to `MediaItem.Builder.setLiveConfiguration`.
* Global default values set on `DefaultMediaSourceFactory`.
* Values read directly from the media.
~~~ ~~~
// Global settings. // Global settings.
@ -130,40 +127,31 @@ Available configuration values are:
* `maxPlaybackSpeed`: The maximum playback speed the player can use to catch up * `maxPlaybackSpeed`: The maximum playback speed the player can use to catch up
when trying to reach the target live offset. when trying to reach the target live offset.
## Playback speed adjustment ##
When playing a low-latency live stream ExoPlayer adjusts the live offset by
slightly changing the playback speed. The player will try to match the target
live offset provided by the media or the app, but will also try to react to
changing network conditions. For example, if rebuffers occur during playback,
the player will slow down playback slightly to move further away from the live
edge. If the network then becomes stable enough to support playing closer to the
live edge again, the player will speed up playback to move back toward the
target live offset.
If automatic playback speed adjustment is not desired, it can be disabled by If automatic playback speed adjustment is not desired, it can be disabled by
setting `minPlaybackSpeed` and `maxPlaybackSpeed` to `1.0f`. setting `minPlaybackSpeed` and `maxPlaybackSpeed` properties to `1.0f`.
Similarly, it can be enabled for non-low-latency live streams by setting these
explicitly to values other than `1.0f`. See
[the configuration section above](#configuring-live-playback-parameters) for
more details on how these properties can be set.
## BehindLiveWindowException and ERROR_CODE_BEHIND_LIVE_WINDOW ## ### Customizing the playback speed adjustment algorithm ###
The playback position may fall behind the live window, for example if the player If speed adjustment is enabled, a `LivePlaybackSpeedControl` defines what
is paused or buffering for a long enough period of time. If this happens then adjustments are made. It's possible to implement a custom
playback will fail and an exception with error code `LivePlaybackSpeedControl`, or to customize the default implementation, which is
`ERROR_CODE_BEHIND_LIVE_WINDOW` will be reported via `DefaultLivePlaybackSpeedControl`. In both cases an instance can be set when
`Player.Listener.onPlayerError`. Application code may wish to handle such building the player:
errors by resuming playback at the default position. The [PlayerActivity][] of
the demo app exemplifies this approach.
~~~
@Override
public void onPlayerError(PlaybackException error) {
if (eror.errorCode == PlaybackException.ERROR_CODE_BEHIND_LIVE_WINDOW) {
// Re-initialize player at the current live window default position.
player.seekToDefaultPosition();
player.prepare();
} else {
// Handle other errors.
}
}
~~~
{: .language-java}
## Customizing the playback speed adjustment algorithm ##
To stay close to the target live offset, a `LivePlaybackSpeedControl` is used to
make adjustments to the playback speed during live playbacks. It's possible to
implement a custom `LivePlaybackSpeedControl`, or to customize the default
implementation, which is `DefaultLivePlaybackSpeedControl`. In both cases an
instance can be set when building the player:
~~~ ~~~
ExoPlayer player = ExoPlayer player =
@ -195,6 +183,30 @@ Relevant customization parameters of `DefaultLivePlaybackSpeedControl` are:
a lower value means the estimation will adjust faster at a higher risk of a lower value means the estimation will adjust faster at a higher risk of
running into rebuffers. running into rebuffers.
## BehindLiveWindowException and ERROR_CODE_BEHIND_LIVE_WINDOW ##
The playback position may fall behind the live window, for example if the player
is paused or buffering for a long enough period of time. If this happens then
playback will fail and an exception with error code
`ERROR_CODE_BEHIND_LIVE_WINDOW` will be reported via
`Player.Listener.onPlayerError`. Application code may wish to handle such
errors by resuming playback at the default position. The [PlayerActivity][] of
the demo app exemplifies this approach.
~~~
@Override
public void onPlayerError(PlaybackException error) {
if (eror.errorCode == PlaybackException.ERROR_CODE_BEHIND_LIVE_WINDOW) {
// Re-initialize player at the current live window default position.
player.seekToDefaultPosition();
player.prepare();
} else {
// Handle other errors.
}
}
~~~
{: .language-java}
[Supported Formats page]: {{ site.baseurl }}/supported-formats.html [Supported Formats page]: {{ site.baseurl }}/supported-formats.html
[default UI components]: {{ site.baseurl }}/ui-components.html [default UI components]: {{ site.baseurl }}/ui-components.html
[pending feature request (#2213)]: https://github.com/google/ExoPlayer/issues/2213 [pending feature request (#2213)]: https://github.com/google/ExoPlayer/issues/2213

View file

@ -34,9 +34,10 @@ these requirements and injected during player construction:
~~~ ~~~
MediaSource.Factory mediaSourceFactory = MediaSource.Factory mediaSourceFactory =
new DefaultMediaSourceFactory(cacheDataSourceFactory) new DefaultMediaSourceFactory(context)
.setAdsLoaderProvider(adsLoaderProvider) .setDataSourceFactory(cacheDataSourceFactory)
.setAdViewProvider(playerView); .setLocalAdInsertionComponents(
adsLoaderProvider, /* adViewProvider= */ playerView);
ExoPlayer player = new ExoPlayer.Builder(context) ExoPlayer player = new ExoPlayer.Builder(context)
.setMediaSourceFactory(mediaSourceFactory) .setMediaSourceFactory(mediaSourceFactory)
.build(); .build();

View file

@ -22,14 +22,14 @@ that corresponds to the network stack you wish to use. If your application also
needs to play non-http(s) content such as local files, use needs to play non-http(s) content such as local files, use
~~~ ~~~
new DefaultDataSourceFactory( new DefaultDataSource.Factory(
... ...
/* baseDataSourceFactory= */ new PreferredHttpDataSource.Factory(...)); /* baseDataSourceFactory= */ new PreferredHttpDataSource.Factory(...));
~~~ ~~~
{: .language-java} {: .language-java}
where `PreferredHttpDataSource.Factory` is the factory corresponding to your where `PreferredHttpDataSource.Factory` is the factory corresponding to your
preferred network stack. The `DefaultDataSourceFactory` layer adds in support preferred network stack. The `DefaultDataSource.Factory` layer adds in support
for non-http(s) sources such as local files. for non-http(s) sources such as local files.
The example below shows how to build an `ExoPlayer` that will use the Cronet The example below shows how to build an `ExoPlayer` that will use the Cronet
@ -48,10 +48,12 @@ DefaultDataSource.Factory dataSourceFactory =
context, context,
/* baseDataSourceFactory= */ cronetDataSourceFactory); /* baseDataSourceFactory= */ cronetDataSourceFactory);
// Inject the DefaultDataSourceFactory when creating the player. // Inject the DefaultDataSource.Factory when creating the player.
ExoPlayer player = ExoPlayer player =
new ExoPlayer.Builder(context) new ExoPlayer.Builder(context)
.setMediaSourceFactory(new DefaultMediaSourceFactory(dataSourceFactory)) .setMediaSourceFactory(
new DefaultMediaSourceFactory(context)
.setDataSourceFactory(dataSourceFactory))
.build(); .build();
~~~ ~~~
{: .language-java} {: .language-java}

View file

@ -2,50 +2,63 @@
title: Track selection title: Track selection
--- ---
Track selection determines which of the available media tracks are played by the When a media item contains multiple tracks, track selection is the process that
player. This process is configured by [`TrackSelectionParameters`][], which determines which of them are chosen for playback. The track selection process is
support many different options to specify constraints and overrides. configured by [`TrackSelectionParameters`][], which allows many different
constraints and overrides influencing track selection to be specified.
## Information about existing tracks ## Querying the available tracks
The player needs to prepare the media to know which tracks are available for You can listen to `Player.Listener.onTracksChanged` to be notified about changes
selection. You can listen to `Player.Listener.onTracksInfoChanged` to get to tracks, including:
notified about changes, which may happen
* When preparation completes * The available tracks becoming known when preparation of the media item being
* When the available or selected tracks change played completes. Note that the player needs to prepare a media item to know
* When the playlist item changes what tracks it contains.
* The available tracks changing due to playback transitioning from one media
item to another.
* Changes to the selected tracks.
~~~ ~~~
player.addListener(new Player.Listener() { player.addListener(new Player.Listener() {
@Override @Override
public void onTracksInfoChanged(TracksInfo tracksInfo) { public void onTracksChanged(Tracks tracks) {
// Update UI using current TracksInfo. // Update UI using current tracks.
} }
}); });
~~~ ~~~
{: .language-java} {: .language-java}
You can also retrieve the current `TracksInfo` by calling You can also query the current tracks by calling `player.getCurrentTracks()`.
`player.getCurrentTracksInfo()`. The returned `Tracks` contains a list of `Track.Group`s, where tracks within a
single `Group` present the same content but in different formats.
`TracksInfo` contains a list of `TrackGroupInfo`s with information about the As an example of how tracks can be grouped, consider an adaptive playback where
track type, format details, player support and selection status of each a main video feed is provided in five bitrates, and an alternative video feed
available track. Tracks are grouped together into one `TrackGroup` if they (e.g., a different camera angle in a sports match) is provided in two bitrates.
represent the same content that can be used interchangeably by the player (for In this case there will be two video track groups, one corresponding to the main
example, all audio tracks of a single language, but with different bitrates). video feed containing five tracks, and a second for the alternative video feed
containing two tracks.
Audio tracks whose languages differ are not grouped, because content in
different languages is not considered to be the same. Conversely, audio tracks
in the same language that only differ in properties such as bitrate, sampling
rate, channel count and so on can be grouped. This also applies to text tracks.
Each `Group` can be queried to determine which tracks are supported for
playback, which are currently selected, and what `Format` each track uses:
~~~ ~~~
for (TrackGroupInfo groupInfo : tracksInfo.getTrackGroupInfos()) { for (Tracks.Group trackGroup : tracks.getGroups()) {
// Group level information. // Group level information.
@C.TrackType int trackType = groupInfo.getTrackType(); @C.TrackType int trackType = trackGroup.getTrackType();
boolean trackInGroupIsSelected = groupInfo.isSelected(); boolean trackInGroupIsSelected = trackGroup.isSelected();
boolean trackInGroupIsSupported = groupInfo.isSupported(); boolean trackInGroupIsSupported = trackGroup.isSupported();
TrackGroup group = groupInfo.getTrackGroup(); for (int i = 0; i < trackGroup.length; i++) {
for (int i = 0; i < group.length; i++) {
// Individual track information. // Individual track information.
boolean isSupported = groupInfo.isTrackSupported(i); boolean isSupported = trackGroup.isTrackSupported(i);
boolean isSelected = groupInfo.isTrackSelected(i); boolean isSelected = trackGroup.isTrackSelected(i);
Format trackFormat = group.getFormat(i); Format trackFormat = trackGroup.getTrackFormat(i);
} }
} }
~~~ ~~~
@ -56,22 +69,19 @@ for (TrackGroupInfo groupInfo : tracksInfo.getTrackGroupInfos()) {
multiple audio track groups) are supported, it only means that they are multiple audio track groups) are supported, it only means that they are
supported individually and the player is not necessarily able to play them at supported individually and the player is not necessarily able to play them at
the same time. the same time.
* A track is 'selected' if the track selector chose this track for playback * A track is 'selected' if it has been chosen for playback given the current
using the current `TrackSelectionParameters`. If multiple tracks within one `TrackSelectionParameters`. If multiple tracks within one track group are
track group are selected, the player uses these tracks for adaptive playback selected, the player uses these tracks for adaptive playback (for example,
(for example, multiple video tracks with different bitrates). Note that only multiple video tracks with different bitrates). Note that only one of these
one of these tracks will be played at any one time. If you want to be notified tracks will be played at any one time.
of in-playback changes to the adaptive video track you can listen to
`Player.Listener.onVideoSizeChanged`.
## Modifying track selection parameters ## Modifying track selection parameters
The selection process can be configured by setting `TrackSelectionParameters` on The track selection process can be configured using
the `Player` with `Player.setTrackSelectionParameters`. These updates can be `Player.setTrackSelectionParameters`. This can be done both before and during
done before and during playback. In most cases, it's advisable to obtain the playback. The example below demonstrates how to obtain the current
current parameters and only modify the required aspects with the `TrackSelectionParameters` from the player, modify them, and update the `Player`
`TrackSelectionParameters.Builder`. The builder class also allows chaining to with the modified result:
specify multiple options with one command:
~~~ ~~~
player.setTrackSelectionParameters( player.setTrackSelectionParameters(
@ -86,84 +96,84 @@ player.setTrackSelectionParameters(
### Constraint based track selection ### Constraint based track selection
Most options in `TrackSelectionParameters` allow you to specify constraints, Most options in `TrackSelectionParameters` allow you to specify constraints,
which are independent of the tracks that are actually available. Typical which are independent of the tracks that are actually available. Available
constraints are: constraints include:
* Maximum or minimum video width, height, frame rate, or bitrate. * Maximum and minimum video width, height, frame rate, and bitrate.
* Maximum audio channel count or bitrate. * Maximum audio channel count and bitrate.
* Preferred MIME types for video or audio. * Preferred MIME types for video and audio.
* Preferred audio languages or role flags. * Preferred audio languages and role flags.
* Preferred text languages or role flags. * Preferred text languages and role flags.
Note that ExoPlayer already applies sensible defaults for most of these values, ExoPlayer uses sensible defaults for these constraints, for example restricting
for example restricting video resolution to the display size or preferring the video resolution to the display size and preferring the audio language that
audio language that matches the user's system Locale setting. matches the user's system Locale setting.
There are several benefits to using constraint based track selection instead of There are several benefits to using constraint based track selection rather than
specifying specific tracks directly: selecting specific tracks from those that are available:
* You can specify constraints before knowing what tracks the media provides. * You can specify constraints before knowing what tracks a media item provides.
This allows to immediately select the appropriate tracks for faster startup This means that constraints can be specified before the player has prepared a
time and also simplifies track selection code as you don't have to listen for media item, whereas selecting specific tracks requires application code to
changes in the available tracks. wait until the available tracks become known.
* Constraints can be applied consistently across all items in a playlist. For * Constraints are applied for all media items in a playlist, even when those
example, selecting an audio language based on user preference will items have different available tracks. For example, a preferred audio language
automatically apply to the next playlist item too, whereas overriding a constraint will be automatically applied for all media items, even if the
specific track will only apply to the current playlist item for which the `Format` of the track in that language varies from one media item to the next.
track exists. This is not the case when selecting specific tracks, as described below.
### Selecting specific tracks ### Selecting specific tracks
It's possible to specify specific tracks in `TrackSelectionParameters` that It's possible to select specific tracks using `TrackSelectionParameters`. First,
should be selected for the current set of tracks. Note that a change in the the player's currently available tracks should be queried using
available tracks, for example when changing items in a playlist, will also `Player.getCurrentTracks`. Second, having identified which tracks to select,
invalidate such a track override. they can be set on `TrackSelectionParameters` using a `TrackSelectionOverride`.
For example, to select the first track from a specific `audioTrackGroup`:
The simplest way to specify track overrides is to specify the `TrackGroup` that
should be selected for its track type. For example, you can specify an audio
track group to select this audio group and prevent any other audio track groups
from being selected:
~~~
TrackSelectionOverrides overrides =
new TrackSelectionOverrides.Builder()
.setOverrideForType(new TrackSelectionOverride(audioTrackGroup))
.build();
player.setTrackSelectionParameters(
player.getTrackSelectionParameters()
.buildUpon().setTrackSelectionOverrides(overrides).build());
~~~
{: .language-java}
### Disabling track types or groups
Track types, like video, audio or text, can be disabled completely by using
`TrackSelectionParameters.Builder.setDisabledTrackTypes`. This will apply
unconditionally and will also affect other playlist items.
~~~ ~~~
player.setTrackSelectionParameters( player.setTrackSelectionParameters(
player.getTrackSelectionParameters() player.getTrackSelectionParameters()
.buildUpon() .buildUpon()
.setDisabledTrackTypes(ImmutableSet.of(C.TRACK_TYPE_VIDEO)) .setOverrideForType(
new TrackSelectionOverride(
audioTrackGroup.getMediaTrackGroup(),
/* trackIndex= */ 0))
.build()); .build());
~~~ ~~~
{: .language-java} {: .language-java}
Alternatively, it's possible to prevent the selection of track groups for the A `TrackSelectionOverride` will only apply to media items that contain a
current playlist item only by specifying empty overrides for these groups: `TrackGroup` exactly matching the one specified in the override. Hence an
override may not apply to a subsequent media item if that item contains
different tracks.
### Disabling track types or groups
Track types like video, audio or text, can be disabled completely using
`TrackSelectionParameters.Builder.setTrackTypeDisabled`. A disabled track type
will be disabled for all media items:
~~~ ~~~
TrackSelectionOverrides overrides =
new TrackSelectionOverrides.Builder()
.addOverride(
new TrackSelectionOverride(
disabledTrackGroup,
/* select no tracks for this group */ ImmutableList.of()))
.build();
player.setTrackSelectionParameters( player.setTrackSelectionParameters(
player.getTrackSelectionParameters() player.getTrackSelectionParameters()
.buildUpon().setTrackSelectionOverrides(overrides).build()); .buildUpon()
.setTrackTypeDisabled(C.TRACK_TYPE_VIDEO, /* disabled= */ true)
.build());
~~~
{: .language-java}
Alternatively, it's possible to prevent the selection of tracks from a specific
`TrackGroup` by specifying an empty override for that group:
~~~
player.setTrackSelectionParameters(
player.getTrackSelectionParameters()
.buildUpon()
.addOverride(
new TrackSelectionOverride(
disabledTrackGroup.getMediaTrackGroup(),
/* trackIndices= */ ImmutableList.of()))
.build());
~~~ ~~~
{: .language-java} {: .language-java}

View file

@ -65,7 +65,7 @@ app is notified of events via the listener passed to the `Transformer` builder.
Transformer.Listener transformerListener = Transformer.Listener transformerListener =
new Transformer.Listener() { new Transformer.Listener() {
@Override @Override
public void onTransformationCompleted(MediaItem inputMediaItem) { public void onTransformationCompleted(MediaItem inputMediaItem, TransformationResult transformationResult) {
playOutput(); playOutput();
} }

View file

@ -22,6 +22,7 @@ redirect_from:
* [Why does content fail to play, but no error is surfaced?] * [Why does content fail to play, but no error is surfaced?]
* [How can I get a decoding extension to load and be used for playback?][] * [How can I get a decoding extension to load and be used for playback?][]
* [Can I play YouTube videos directly with ExoPlayer?][] * [Can I play YouTube videos directly with ExoPlayer?][]
* [Video playback is stuttering][]
--- ---
@ -293,6 +294,16 @@ No, ExoPlayer cannot play videos from YouTube, i.e., urls of the form
Android Player API](https://developers.google.com/youtube/android/player/) which Android Player API](https://developers.google.com/youtube/android/player/) which
is the official way to play YouTube videos on Android. is the official way to play YouTube videos on Android.
#### Video playback is stuttering ###
The device may not be able to decode the content fast enough if, for example,
the content bitrate or resolution exceeds the device capabilities. You may need
to use lower quality content to obtain good performance on such devices.
If you're experiencing video stuttering on a device running Android 6 to 11,
particularly when playing DRM protected or high frame rate content, you can try
[enabling asynchronous buffer queueing].
[Fixing "Cleartext HTTP traffic not permitted" errors]: #fixing-cleartext-http-traffic-not-permitted-errors [Fixing "Cleartext HTTP traffic not permitted" errors]: #fixing-cleartext-http-traffic-not-permitted-errors
[Fixing "SSLHandshakeException", "CertPathValidatorException" and "ERR_CERT_AUTHORITY_INVALID" errors]: #fixing-sslhandshakeexception-certpathvalidatorexception-and-err_cert_authority_invalid-errors [Fixing "SSLHandshakeException", "CertPathValidatorException" and "ERR_CERT_AUTHORITY_INVALID" errors]: #fixing-sslhandshakeexception-certpathvalidatorexception-and-err_cert_authority_invalid-errors
[What formats does ExoPlayer support?]: #what-formats-does-exoplayer-support [What formats does ExoPlayer support?]: #what-formats-does-exoplayer-support
@ -311,7 +322,7 @@ is the official way to play YouTube videos on Android.
[Why does content fail to play, but no error is surfaced?]: #why-does-content-fail-to-play-but-no-error-is-surfaced [Why does content fail to play, but no error is surfaced?]: #why-does-content-fail-to-play-but-no-error-is-surfaced
[How can I get a decoding extension to load and be used for playback?]: #how-can-i-get-a-decoding-extension-to-load-and-be-used-for-playback [How can I get a decoding extension to load and be used for playback?]: #how-can-i-get-a-decoding-extension-to-load-and-be-used-for-playback
[Can I play YouTube videos directly with ExoPlayer?]: #can-i-play-youtube-videos-directly-with-exoplayer [Can I play YouTube videos directly with ExoPlayer?]: #can-i-play-youtube-videos-directly-with-exoplayer
[Video playback is stuttering]: #video-playback-is-stuttering
[Supported formats]: {{ site.baseurl }}/supported-formats.html [Supported formats]: {{ site.baseurl }}/supported-formats.html
[set on a `DefaultExtractorsFactory`]: {{ site.base_url }}/customization.html#customizing-extractor-flags [set on a `DefaultExtractorsFactory`]: {{ site.base_url }}/customization.html#customizing-extractor-flags
@ -349,3 +360,4 @@ is the official way to play YouTube videos on Android.
[`DefaultRenderersFactory`]: {{ site.exo_sdk }}/DefaultRenderersFactory.html [`DefaultRenderersFactory`]: {{ site.exo_sdk }}/DefaultRenderersFactory.html
[`LibraryLoader`]: {{ site.exo_sdk }}/util/LibraryLoader.html [`LibraryLoader`]: {{ site.exo_sdk }}/util/LibraryLoader.html
[`EventLogger`]: {{ site.baseurl }}/debug-logging.html [`EventLogger`]: {{ site.baseurl }}/debug-logging.html
[enabling asynchronous buffer queueing]: {{ site.baseurl }}/customization.html#enabling-asynchronous-buffer-queueing

View file

@ -37,19 +37,15 @@ import com.google.android.exoplayer2.PlaybackException;
import com.google.android.exoplayer2.PlaybackParameters; import com.google.android.exoplayer2.PlaybackParameters;
import com.google.android.exoplayer2.Player; import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.TracksInfo; import com.google.android.exoplayer2.Tracks;
import com.google.android.exoplayer2.audio.AudioAttributes; import com.google.android.exoplayer2.audio.AudioAttributes;
import com.google.android.exoplayer2.source.TrackGroup; import com.google.android.exoplayer2.source.TrackGroup;
import com.google.android.exoplayer2.source.TrackGroupArray; import com.google.android.exoplayer2.text.CueGroup;
import com.google.android.exoplayer2.text.Cue;
import com.google.android.exoplayer2.trackselection.TrackSelection;
import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
import com.google.android.exoplayer2.trackselection.TrackSelectionParameters; import com.google.android.exoplayer2.trackselection.TrackSelectionParameters;
import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.Clock; import com.google.android.exoplayer2.util.Clock;
import com.google.android.exoplayer2.util.ListenerSet; import com.google.android.exoplayer2.util.ListenerSet;
import com.google.android.exoplayer2.util.Log; import com.google.android.exoplayer2.util.Log;
import com.google.android.exoplayer2.util.MimeTypes;
import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.util.Util;
import com.google.android.exoplayer2.video.VideoSize; import com.google.android.exoplayer2.video.VideoSize;
import com.google.android.gms.cast.CastStatusCodes; import com.google.android.gms.cast.CastStatusCodes;
@ -67,7 +63,6 @@ import com.google.android.gms.common.api.PendingResult;
import com.google.android.gms.common.api.ResultCallback; import com.google.android.gms.common.api.ResultCallback;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import java.util.List; import java.util.List;
import org.checkerframework.checker.nullness.compatqual.NullableType;
import org.checkerframework.checker.nullness.qual.RequiresNonNull; import org.checkerframework.checker.nullness.qual.RequiresNonNull;
/** /**
@ -105,7 +100,7 @@ public final class CastPlayer extends BasePlayer {
COMMAND_GET_MEDIA_ITEMS_METADATA, COMMAND_GET_MEDIA_ITEMS_METADATA,
COMMAND_SET_MEDIA_ITEMS_METADATA, COMMAND_SET_MEDIA_ITEMS_METADATA,
COMMAND_CHANGE_MEDIA_ITEMS, COMMAND_CHANGE_MEDIA_ITEMS,
COMMAND_GET_TRACK_INFOS) COMMAND_GET_TRACKS)
.build(); .build();
public static final float MIN_SPEED_SUPPORTED = 0.5f; public static final float MIN_SPEED_SUPPORTED = 0.5f;
@ -113,13 +108,7 @@ public final class CastPlayer extends BasePlayer {
private static final String TAG = "CastPlayer"; private static final String TAG = "CastPlayer";
private static final int RENDERER_COUNT = 3;
private static final int RENDERER_INDEX_VIDEO = 0;
private static final int RENDERER_INDEX_AUDIO = 1;
private static final int RENDERER_INDEX_TEXT = 2;
private static final long PROGRESS_REPORT_PERIOD_MS = 1000; private static final long PROGRESS_REPORT_PERIOD_MS = 1000;
private static final TrackSelectionArray EMPTY_TRACK_SELECTION_ARRAY =
new TrackSelectionArray(null, null, null);
private static final long[] EMPTY_TRACK_ID_ARRAY = new long[0]; private static final long[] EMPTY_TRACK_ID_ARRAY = new long[0];
private final CastContext castContext; private final CastContext castContext;
@ -144,9 +133,7 @@ public final class CastPlayer extends BasePlayer {
private final StateHolder<PlaybackParameters> playbackParameters; private final StateHolder<PlaybackParameters> playbackParameters;
@Nullable private RemoteMediaClient remoteMediaClient; @Nullable private RemoteMediaClient remoteMediaClient;
private CastTimeline currentTimeline; private CastTimeline currentTimeline;
private TrackGroupArray currentTrackGroups; private Tracks currentTracks;
private TrackSelectionArray currentTrackSelection;
private TracksInfo currentTracksInfo;
private Commands availableCommands; private Commands availableCommands;
private @Player.State int playbackState; private @Player.State int playbackState;
private int currentWindowIndex; private int currentWindowIndex;
@ -222,9 +209,7 @@ public final class CastPlayer extends BasePlayer {
playbackParameters = new StateHolder<>(PlaybackParameters.DEFAULT); playbackParameters = new StateHolder<>(PlaybackParameters.DEFAULT);
playbackState = STATE_IDLE; playbackState = STATE_IDLE;
currentTimeline = CastTimeline.EMPTY_CAST_TIMELINE; currentTimeline = CastTimeline.EMPTY_CAST_TIMELINE;
currentTrackGroups = TrackGroupArray.EMPTY; currentTracks = Tracks.EMPTY;
currentTrackSelection = EMPTY_TRACK_SELECTION_ARRAY;
currentTracksInfo = TracksInfo.EMPTY;
availableCommands = new Commands.Builder().addAll(PERMANENT_AVAILABLE_COMMANDS).build(); availableCommands = new Commands.Builder().addAll(PERMANENT_AVAILABLE_COMMANDS).build();
pendingSeekWindowIndex = C.INDEX_UNSET; pendingSeekWindowIndex = C.INDEX_UNSET;
pendingSeekPositionMs = C.TIME_UNSET; pendingSeekPositionMs = C.TIME_UNSET;
@ -471,6 +456,11 @@ public final class CastPlayer extends BasePlayer {
stop(/* reset= */ false); stop(/* reset= */ false);
} }
/**
* @deprecated Use {@link #stop()} and {@link #clearMediaItems()} (if {@code reset} is true) or
* just {@link #stop()} (if {@code reset} is false). Any player error will be cleared when
* {@link #prepare() re-preparing} the player.
*/
@Deprecated @Deprecated
@Override @Override
public void stop(boolean reset) { public void stop(boolean reset) {
@ -556,18 +546,8 @@ public final class CastPlayer extends BasePlayer {
} }
@Override @Override
public TrackGroupArray getCurrentTrackGroups() { public Tracks getCurrentTracks() {
return currentTrackGroups; return currentTracks;
}
@Override
public TrackSelectionArray getCurrentTrackSelections() {
return currentTrackSelection;
}
@Override
public TracksInfo getCurrentTracksInfo() {
return currentTracksInfo;
} }
@Override @Override
@ -728,10 +708,10 @@ public final class CastPlayer extends BasePlayer {
return VideoSize.UNKNOWN; return VideoSize.UNKNOWN;
} }
/** This method is not supported and returns an empty list. */ /** This method is not supported and returns an empty {@link CueGroup}. */
@Override @Override
public ImmutableList<Cue> getCurrentCues() { public CueGroup getCurrentCues() {
return ImmutableList.of(); return CueGroup.EMPTY;
} }
/** This method is not supported and always returns {@link DeviceInfo#UNKNOWN}. */ /** This method is not supported and always returns {@link DeviceInfo#UNKNOWN}. */
@ -840,10 +820,7 @@ public final class CastPlayer extends BasePlayer {
} }
if (updateTracksAndSelectionsAndNotifyIfChanged()) { if (updateTracksAndSelectionsAndNotifyIfChanged()) {
listeners.queueEvent( listeners.queueEvent(
Player.EVENT_TRACKS_CHANGED, Player.EVENT_TRACKS_CHANGED, listener -> listener.onTracksChanged(currentTracks));
listener -> listener.onTracksChanged(currentTrackGroups, currentTrackSelection));
listeners.queueEvent(
Player.EVENT_TRACKS_CHANGED, listener -> listener.onTracksInfoChanged(currentTracksInfo));
} }
updateAvailableCommandsAndNotifyIfChanged(); updateAvailableCommandsAndNotifyIfChanged();
listeners.flushEvents(); listeners.flushEvents();
@ -998,55 +975,33 @@ public final class CastPlayer extends BasePlayer {
return false; return false;
} }
MediaStatus mediaStatus = getMediaStatus(); @Nullable MediaStatus mediaStatus = getMediaStatus();
MediaInfo mediaInfo = mediaStatus != null ? mediaStatus.getMediaInfo() : null; @Nullable MediaInfo mediaInfo = mediaStatus != null ? mediaStatus.getMediaInfo() : null;
@Nullable
List<MediaTrack> castMediaTracks = mediaInfo != null ? mediaInfo.getMediaTracks() : null; List<MediaTrack> castMediaTracks = mediaInfo != null ? mediaInfo.getMediaTracks() : null;
if (castMediaTracks == null || castMediaTracks.isEmpty()) { if (castMediaTracks == null || castMediaTracks.isEmpty()) {
boolean hasChanged = !currentTrackGroups.isEmpty(); boolean hasChanged = !Tracks.EMPTY.equals(currentTracks);
currentTrackGroups = TrackGroupArray.EMPTY; currentTracks = Tracks.EMPTY;
currentTrackSelection = EMPTY_TRACK_SELECTION_ARRAY;
currentTracksInfo = TracksInfo.EMPTY;
return hasChanged; return hasChanged;
} }
long[] activeTrackIds = mediaStatus.getActiveTrackIds(); @Nullable long[] activeTrackIds = mediaStatus.getActiveTrackIds();
if (activeTrackIds == null) { if (activeTrackIds == null) {
activeTrackIds = EMPTY_TRACK_ID_ARRAY; activeTrackIds = EMPTY_TRACK_ID_ARRAY;
} }
TrackGroup[] trackGroups = new TrackGroup[castMediaTracks.size()]; Tracks.Group[] trackGroups = new Tracks.Group[castMediaTracks.size()];
@NullableType TrackSelection[] trackSelections = new TrackSelection[RENDERER_COUNT];
TracksInfo.TrackGroupInfo[] trackGroupInfos =
new TracksInfo.TrackGroupInfo[castMediaTracks.size()];
for (int i = 0; i < castMediaTracks.size(); i++) { for (int i = 0; i < castMediaTracks.size(); i++) {
MediaTrack mediaTrack = castMediaTracks.get(i); MediaTrack mediaTrack = castMediaTracks.get(i);
trackGroups[i] = TrackGroup trackGroup =
new TrackGroup(/* id= */ Integer.toString(i), CastUtils.mediaTrackToFormat(mediaTrack)); new TrackGroup(/* id= */ Integer.toString(i), CastUtils.mediaTrackToFormat(mediaTrack));
@C.FormatSupport int[] trackSupport = new int[] {C.FORMAT_HANDLED};
long id = mediaTrack.getId(); boolean[] trackSelected = new boolean[] {isTrackActive(mediaTrack.getId(), activeTrackIds)};
@C.TrackType int trackType = MimeTypes.getTrackType(mediaTrack.getContentType()); trackGroups[i] =
int rendererIndex = getRendererIndexForTrackType(trackType); new Tracks.Group(trackGroup, /* adaptiveSupported= */ false, trackSupport, trackSelected);
boolean supported = rendererIndex != C.INDEX_UNSET;
boolean selected =
isTrackActive(id, activeTrackIds) && supported && trackSelections[rendererIndex] == null;
if (selected) {
trackSelections[rendererIndex] = new CastTrackSelection(trackGroups[i]);
}
@C.FormatSupport
int[] trackSupport = new int[] {supported ? C.FORMAT_HANDLED : C.FORMAT_UNSUPPORTED_TYPE};
final boolean[] trackSelected = new boolean[] {selected};
trackGroupInfos[i] =
new TracksInfo.TrackGroupInfo(trackGroups[i], trackSupport, trackType, trackSelected);
} }
TrackGroupArray newTrackGroups = new TrackGroupArray(trackGroups); Tracks newTracks = new Tracks(ImmutableList.copyOf(trackGroups));
TrackSelectionArray newTrackSelections = new TrackSelectionArray(trackSelections); if (!newTracks.equals(currentTracks)) {
TracksInfo newTracksInfo = new TracksInfo(ImmutableList.copyOf(trackGroupInfos)); currentTracks = newTracks;
if (!newTrackGroups.equals(currentTrackGroups)
|| !newTrackSelections.equals(currentTrackSelection)
|| !newTracksInfo.equals(currentTracksInfo)) {
currentTrackSelection = newTrackSelections;
currentTrackGroups = newTrackGroups;
currentTracksInfo = newTracksInfo;
return true; return true;
} }
return false; return false;
@ -1304,14 +1259,6 @@ public final class CastPlayer extends BasePlayer {
return false; return false;
} }
private static int getRendererIndexForTrackType(@C.TrackType int trackType) {
return trackType == C.TRACK_TYPE_VIDEO
? RENDERER_INDEX_VIDEO
: trackType == C.TRACK_TYPE_AUDIO
? RENDERER_INDEX_AUDIO
: trackType == C.TRACK_TYPE_TEXT ? RENDERER_INDEX_TEXT : C.INDEX_UNSET;
}
private static int getCastRepeatMode(@RepeatMode int repeatMode) { private static int getCastRepeatMode(@RepeatMode int repeatMode) {
switch (repeatMode) { switch (repeatMode) {
case REPEAT_MODE_ONE: case REPEAT_MODE_ONE:

View file

@ -1,96 +0,0 @@
/*
* Copyright (C) 2021 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.ext.cast;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.source.TrackGroup;
import com.google.android.exoplayer2.trackselection.TrackSelection;
import com.google.android.exoplayer2.util.Assertions;
/**
* {@link TrackSelection} that only selects the first track of the provided {@link TrackGroup}.
*
* <p>This relies on {@link CastPlayer} track groups only having one track.
*/
/* package */ class CastTrackSelection implements TrackSelection {
private final TrackGroup trackGroup;
/** @param trackGroup The {@link TrackGroup} from which the first track will only be selected. */
public CastTrackSelection(TrackGroup trackGroup) {
this.trackGroup = trackGroup;
}
@Override
public int getType() {
return TYPE_UNSET;
}
@Override
public TrackGroup getTrackGroup() {
return trackGroup;
}
@Override
public int length() {
return 1;
}
@Override
public Format getFormat(int index) {
Assertions.checkArgument(index == 0);
return trackGroup.getFormat(0);
}
@Override
public int getIndexInTrackGroup(int index) {
return index == 0 ? 0 : C.INDEX_UNSET;
}
@Override
@SuppressWarnings("ReferenceEquality")
public int indexOf(Format format) {
return format == trackGroup.getFormat(0) ? 0 : C.INDEX_UNSET;
}
@Override
public int indexOf(int indexInTrackGroup) {
return indexInTrackGroup == 0 ? 0 : C.INDEX_UNSET;
}
// Object overrides.
@Override
public int hashCode() {
return System.identityHashCode(trackGroup);
}
// Track groups are compared by identity not value, as distinct groups may have the same value.
@Override
@SuppressWarnings({"ReferenceEquality", "EqualsGetClass"})
public boolean equals(@Nullable Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
CastTrackSelection other = (CastTrackSelection) obj;
return trackGroup == other.trackGroup;
}
}

View file

@ -1,77 +0,0 @@
/*
* 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.ext.cast;
import static com.google.common.truth.Truth.assertThat;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.source.TrackGroup;
import org.junit.Test;
import org.junit.runner.RunWith;
/** Test for {@link CastTrackSelection}. */
@RunWith(AndroidJUnit4.class)
public class CastTrackSelectionTest {
private static final TrackGroup TRACK_GROUP =
new TrackGroup(new Format.Builder().build(), new Format.Builder().build());
private static final CastTrackSelection SELECTION = new CastTrackSelection(TRACK_GROUP);
@Test
public void length_isOne() {
assertThat(SELECTION.length()).isEqualTo(1);
}
@Test
public void getTrackGroup_returnsSameGroup() {
assertThat(SELECTION.getTrackGroup()).isSameInstanceAs(TRACK_GROUP);
}
@Test
public void getFormatSelectedTrack_isFirstTrack() {
assertThat(SELECTION.getFormat(0)).isSameInstanceAs(TRACK_GROUP.getFormat(0));
}
@Test
public void getIndexInTrackGroup_ofSelectedTrack_returnsFirstTrack() {
assertThat(SELECTION.getIndexInTrackGroup(0)).isEqualTo(0);
}
@Test
public void getIndexInTrackGroup_onePastTheEnd_returnsIndexUnset() {
assertThat(SELECTION.getIndexInTrackGroup(1)).isEqualTo(C.INDEX_UNSET);
}
@Test
public void indexOf_selectedTrack_returnsFirstTrack() {
assertThat(SELECTION.indexOf(0)).isEqualTo(0);
}
@Test
public void indexOf_onePastTheEnd_returnsIndexUnset() {
assertThat(SELECTION.indexOf(1)).isEqualTo(C.INDEX_UNSET);
}
@Test(expected = Exception.class)
public void getFormat_outOfBound_throws() {
CastTrackSelection selection = new CastTrackSelection(TRACK_GROUP);
selection.getFormat(1);
}
}

View file

@ -38,8 +38,9 @@ If your application only needs to play http(s) content, using the Cronet
extension is as simple as updating `DataSource.Factory` instantiations in your extension is as simple as updating `DataSource.Factory` instantiations in your
application code to use `CronetDataSource.Factory`. If your application also application code to use `CronetDataSource.Factory`. If your application also
needs to play non-http(s) content such as local files, use: needs to play non-http(s) content such as local files, use:
``` ```
new DefaultDataSourceFactory( new DefaultDataSource.Factory(
... ...
/* baseDataSourceFactory= */ new CronetDataSource.Factory(...) ); /* baseDataSourceFactory= */ new CronetDataSource.Factory(...) );
``` ```

View file

@ -343,7 +343,9 @@ public class CronetDataSource extends BaseDataSource implements HttpDataSource {
*/ */
public final int cronetConnectionStatus; public final int cronetConnectionStatus;
/** @deprecated Use {@link #OpenException(IOException, DataSpec, int, int)}. */ /**
* @deprecated Use {@link #OpenException(IOException, DataSpec, int, int)}.
*/
@Deprecated @Deprecated
public OpenException(IOException cause, DataSpec dataSpec, int cronetConnectionStatus) { public OpenException(IOException cause, DataSpec dataSpec, int cronetConnectionStatus) {
super(cause, dataSpec, PlaybackException.ERROR_CODE_IO_UNSPECIFIED, TYPE_OPEN); super(cause, dataSpec, PlaybackException.ERROR_CODE_IO_UNSPECIFIED, TYPE_OPEN);
@ -359,7 +361,9 @@ public class CronetDataSource extends BaseDataSource implements HttpDataSource {
this.cronetConnectionStatus = cronetConnectionStatus; this.cronetConnectionStatus = cronetConnectionStatus;
} }
/** @deprecated Use {@link #OpenException(String, DataSpec, int, int)}. */ /**
* @deprecated Use {@link #OpenException(String, DataSpec, int, int)}.
*/
@Deprecated @Deprecated
public OpenException(String errorMessage, DataSpec dataSpec, int cronetConnectionStatus) { public OpenException(String errorMessage, DataSpec dataSpec, int cronetConnectionStatus) {
super(errorMessage, dataSpec, PlaybackException.ERROR_CODE_IO_UNSPECIFIED, TYPE_OPEN); super(errorMessage, dataSpec, PlaybackException.ERROR_CODE_IO_UNSPECIFIED, TYPE_OPEN);
@ -461,11 +465,7 @@ public class CronetDataSource extends BaseDataSource implements HttpDataSource {
} }
/** /**
* Sets a content type {@link Predicate}. If a content type is rejected by the predicate then a * @deprecated Use {@link CronetDataSource.Factory#setContentTypePredicate(Predicate)} instead.
* {@link HttpDataSource.InvalidContentTypeException} is thrown from {@link #open(DataSpec)}.
*
* @param contentTypePredicate The content type {@link Predicate}, or {@code null} to clear a
* predicate that was previously set.
*/ */
@Deprecated @Deprecated
public void setContentTypePredicate(@Nullable Predicate<String> contentTypePredicate) { public void setContentTypePredicate(@Nullable Predicate<String> contentTypePredicate) {

View file

@ -25,7 +25,9 @@ import com.google.android.exoplayer2.upstream.TransferListener;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import org.chromium.net.CronetEngine; import org.chromium.net.CronetEngine;
/** @deprecated Use {@link CronetDataSource.Factory} instead. */ /**
* @deprecated Use {@link CronetDataSource.Factory} instead.
*/
@Deprecated @Deprecated
public final class CronetDataSourceFactory extends BaseFactory { public final class CronetDataSourceFactory extends BaseFactory {

View file

@ -17,7 +17,8 @@ apply from: "$gradle.ext.exoplayerSettingsDir/common_library_config.gradle"
// failures if ffmpeg hasn't been built according to the README instructions. // failures if ffmpeg hasn't been built according to the README instructions.
if (project.file('src/main/jni/ffmpeg').exists()) { if (project.file('src/main/jni/ffmpeg').exists()) {
android.externalNativeBuild.cmake.path = 'src/main/jni/CMakeLists.txt' android.externalNativeBuild.cmake.path = 'src/main/jni/CMakeLists.txt'
android.externalNativeBuild.cmake.version = '3.7.1+' // Should match cmake_minimum_required.
android.externalNativeBuild.cmake.version = '3.21.0+'
} }
dependencies { dependencies {

View file

@ -48,7 +48,7 @@ public final class FfmpegLibrary {
/** /**
* Override the names of the FFmpeg native libraries. If an application wishes to call this * Override the names of the FFmpeg native libraries. If an application wishes to call this
* method, it must do so before calling any other method defined by this class, and before * method, it must do so before calling any other method defined by this class, and before
* instantiating a {@link FfmpegAudioRenderer} instance. * instantiating a {@link FfmpegAudioRenderer} or {@link FfmpegVideoRenderer} instance.
* *
* @param libraries The names of the FFmpeg native libraries. * @param libraries The names of the FFmpeg native libraries.
*/ */
@ -146,6 +146,10 @@ public final class FfmpegLibrary {
return "pcm_mulaw"; return "pcm_mulaw";
case MimeTypes.AUDIO_ALAW: case MimeTypes.AUDIO_ALAW:
return "pcm_alaw"; return "pcm_alaw";
case MimeTypes.VIDEO_H264:
return "h264";
case MimeTypes.VIDEO_H265:
return "hevc";
default: default:
return null; return null;
} }

View file

@ -0,0 +1,134 @@
/*
* Copyright (C) 2020 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.ext.ffmpeg;
import static com.google.android.exoplayer2.decoder.DecoderReuseEvaluation.DISCARD_REASON_MIME_TYPE_CHANGED;
import static com.google.android.exoplayer2.decoder.DecoderReuseEvaluation.REUSE_RESULT_NO;
import static com.google.android.exoplayer2.decoder.DecoderReuseEvaluation.REUSE_RESULT_YES_WITHOUT_RECONFIGURATION;
import android.os.Handler;
import android.view.Surface;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.RendererCapabilities;
import com.google.android.exoplayer2.decoder.CryptoConfig;
import com.google.android.exoplayer2.decoder.Decoder;
import com.google.android.exoplayer2.decoder.DecoderInputBuffer;
import com.google.android.exoplayer2.decoder.DecoderReuseEvaluation;
import com.google.android.exoplayer2.decoder.VideoDecoderOutputBuffer;
import com.google.android.exoplayer2.util.TraceUtil;
import com.google.android.exoplayer2.util.Util;
import com.google.android.exoplayer2.video.DecoderVideoRenderer;
import com.google.android.exoplayer2.video.VideoRendererEventListener;
// TODO: Remove the NOTE below.
/**
* <b>NOTE: This class if under development and is not yet functional.</b>
*
* <p>Decodes and renders video using FFmpeg.
*/
public final class FfmpegVideoRenderer extends DecoderVideoRenderer {
private static final String TAG = "FfmpegVideoRenderer";
/**
* Creates a new instance.
*
* @param allowedJoiningTimeMs The maximum duration in milliseconds for which this video renderer
* can attempt to seamlessly join an ongoing playback.
* @param eventHandler A handler to use when delivering events to {@code eventListener}. May be
* null if delivery of events is not required.
* @param eventListener A listener of events. May be null if delivery of events is not required.
* @param maxDroppedFramesToNotify The maximum number of frames that can be dropped between
* invocations of {@link VideoRendererEventListener#onDroppedFrames(int, long)}.
*/
public FfmpegVideoRenderer(
long allowedJoiningTimeMs,
@Nullable Handler eventHandler,
@Nullable VideoRendererEventListener eventListener,
int maxDroppedFramesToNotify) {
super(allowedJoiningTimeMs, eventHandler, eventListener, maxDroppedFramesToNotify);
// TODO: Implement.
}
@Override
public String getName() {
return TAG;
}
@Override
public final @RendererCapabilities.Capabilities int supportsFormat(Format format) {
// TODO: Remove this line and uncomment the implementation below.
return C.FORMAT_UNSUPPORTED_TYPE;
/*
String mimeType = Assertions.checkNotNull(format.sampleMimeType);
if (!FfmpegLibrary.isAvailable() || !MimeTypes.isVideo(mimeType)) {
return FORMAT_UNSUPPORTED_TYPE;
} else if (!FfmpegLibrary.supportsFormat(format.sampleMimeType)) {
return RendererCapabilities.create(FORMAT_UNSUPPORTED_SUBTYPE);
} else if (format.exoMediaCryptoType != null) {
return RendererCapabilities.create(FORMAT_UNSUPPORTED_DRM);
} else {
return RendererCapabilities.create(
FORMAT_HANDLED,
ADAPTIVE_SEAMLESS,
TUNNELING_NOT_SUPPORTED);
}
*/
}
@SuppressWarnings("nullness:return")
@Override
protected Decoder<DecoderInputBuffer, VideoDecoderOutputBuffer, FfmpegDecoderException>
createDecoder(Format format, @Nullable CryptoConfig cryptoConfig)
throws FfmpegDecoderException {
TraceUtil.beginSection("createFfmpegVideoDecoder");
// TODO: Implement, remove the SuppressWarnings annotation, and update the return type to use
// the concrete type of the decoder (probably FfmepgVideoDecoder).
TraceUtil.endSection();
return null;
}
@Override
protected void renderOutputBufferToSurface(VideoDecoderOutputBuffer outputBuffer, Surface surface)
throws FfmpegDecoderException {
// TODO: Implement.
}
@Override
protected void setDecoderOutputMode(@C.VideoOutputMode int outputMode) {
// TODO: Uncomment the implementation below.
/*
if (decoder != null) {
decoder.setOutputMode(outputMode);
}
*/
}
@Override
protected DecoderReuseEvaluation canReuseDecoder(
String decoderName, Format oldFormat, Format newFormat) {
boolean sameMimeType = Util.areEqual(oldFormat.sampleMimeType, newFormat.sampleMimeType);
// TODO: Ability to reuse the decoder may be MIME type dependent.
return new DecoderReuseEvaluation(
decoderName,
oldFormat,
newFormat,
sameMimeType ? REUSE_RESULT_YES_WITHOUT_RECONFIGURATION : REUSE_RESULT_NO,
sameMimeType ? 0 : DISCARD_REASON_MIME_TYPE_CHANGED);
}
}

View file

@ -14,7 +14,7 @@
# limitations under the License. # limitations under the License.
# #
cmake_minimum_required(VERSION 3.7.1 FATAL_ERROR) cmake_minimum_required(VERSION 3.21.0 FATAL_ERROR)
# Enable C++11 features. # Enable C++11 features.
set(CMAKE_CXX_STANDARD 11) set(CMAKE_CXX_STANDARD 11)

View file

@ -22,7 +22,8 @@ import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
/** /**
* Unit test for {@link DefaultRenderersFactoryTest} with {@link FfmpegAudioRenderer}. * Unit test for {@link DefaultRenderersFactoryTest} with {@link FfmpegAudioRenderer} and {@link
* FfmpegVideoRenderer}.
*/ */
@RunWith(AndroidJUnit4.class) @RunWith(AndroidJUnit4.class)
public final class DefaultRenderersFactoryTest { public final class DefaultRenderersFactoryTest {
@ -32,4 +33,10 @@ public final class DefaultRenderersFactoryTest {
DefaultRenderersFactoryAsserts.assertExtensionRendererCreated( DefaultRenderersFactoryAsserts.assertExtensionRendererCreated(
FfmpegAudioRenderer.class, C.TRACK_TYPE_AUDIO); FfmpegAudioRenderer.class, C.TRACK_TYPE_AUDIO);
} }
@Test
public void createRenderers_instantiatesFfmpegVideoRenderer() {
DefaultRenderersFactoryAsserts.assertExtensionRendererCreated(
FfmpegVideoRenderer.class, C.TRACK_TYPE_VIDEO);
}
} }

View file

@ -42,9 +42,7 @@ import com.google.android.exoplayer2.testutil.ExoHostedTest;
import com.google.android.exoplayer2.testutil.HostActivity; import com.google.android.exoplayer2.testutil.HostActivity;
import com.google.android.exoplayer2.testutil.TestUtil; import com.google.android.exoplayer2.testutil.TestUtil;
import com.google.android.exoplayer2.trackselection.MappingTrackSelector; import com.google.android.exoplayer2.trackselection.MappingTrackSelector;
import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.DataSpec; import com.google.android.exoplayer2.upstream.DataSpec;
import com.google.android.exoplayer2.upstream.DefaultDataSource;
import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.util.Util;
import java.util.ArrayList; import java.util.ArrayList;
@ -235,14 +233,13 @@ public final class ImaPlaybackTest {
protected MediaSource buildSource( protected MediaSource buildSource(
HostActivity host, DrmSessionManager drmSessionManager, FrameLayout overlayFrameLayout) { HostActivity host, DrmSessionManager drmSessionManager, FrameLayout overlayFrameLayout) {
Context context = host.getApplicationContext(); Context context = host.getApplicationContext();
DataSource.Factory dataSourceFactory = new DefaultDataSource.Factory(context);
MediaSource contentMediaSource = MediaSource contentMediaSource =
new DefaultMediaSourceFactory(context).createMediaSource(MediaItem.fromUri(contentUri)); new DefaultMediaSourceFactory(context).createMediaSource(MediaItem.fromUri(contentUri));
return new AdsMediaSource( return new AdsMediaSource(
contentMediaSource, contentMediaSource,
adTagDataSpec, adTagDataSpec,
/* adsId= */ adTagDataSpec.uri, /* adsId= */ adTagDataSpec.uri,
new DefaultMediaSourceFactory(dataSourceFactory), new DefaultMediaSourceFactory(context),
Assertions.checkNotNull(imaAdsLoader), Assertions.checkNotNull(imaAdsLoader),
() -> overlayFrameLayout); () -> overlayFrameLayout);
} }

View file

@ -84,12 +84,14 @@ import java.util.Map;
private static final String IMA_SDK_SETTINGS_PLAYER_VERSION = ExoPlayerLibraryInfo.VERSION; private static final String IMA_SDK_SETTINGS_PLAYER_VERSION = ExoPlayerLibraryInfo.VERSION;
/** /**
* Interval at which ad progress updates are provided to the IMA SDK, in milliseconds. 100 ms is * Interval at which ad progress updates are provided to the IMA SDK, in milliseconds. 200 ms is
* the interval recommended by the IMA documentation. * the interval recommended by the Media Rating Council (MRC) for minimum polling of viewable
* video impressions.
* http://www.mediaratingcouncil.org/063014%20Viewable%20Ad%20Impression%20Guideline_Final.pdf.
* *
* @see VideoAdPlayer.VideoAdPlayerCallback * @see VideoAdPlayer.VideoAdPlayerCallback
*/ */
private static final int AD_PROGRESS_UPDATE_INTERVAL_MS = 100; private static final int AD_PROGRESS_UPDATE_INTERVAL_MS = 200;
/** The value used in {@link VideoProgressUpdate}s to indicate an unset duration. */ /** The value used in {@link VideoProgressUpdate}s to indicate an unset duration. */
private static final long IMA_DURATION_UNSET = -1L; private static final long IMA_DURATION_UNSET = -1L;
@ -708,7 +710,7 @@ import java.util.Map;
} }
// Check for a selected track using an audio renderer. // Check for a selected track using an audio renderer.
return player.getCurrentTracksInfo().isTypeSelected(C.TRACK_TYPE_AUDIO) ? 100 : 0; return player.getCurrentTracks().isTypeSelected(C.TRACK_TYPE_AUDIO) ? 100 : 0;
} }
private void handleAdEvent(AdEvent adEvent) { private void handleAdEvent(AdEvent adEvent) {

View file

@ -503,11 +503,11 @@ public final class ImaAdsLoader implements AdsLoader {
List<String> supportedMimeTypes = new ArrayList<>(); List<String> supportedMimeTypes = new ArrayList<>();
for (@C.ContentType int contentType : contentTypes) { for (@C.ContentType int contentType : contentTypes) {
// IMA does not support Smooth Streaming ad media. // IMA does not support Smooth Streaming ad media.
if (contentType == C.TYPE_DASH) { if (contentType == C.CONTENT_TYPE_DASH) {
supportedMimeTypes.add(MimeTypes.APPLICATION_MPD); supportedMimeTypes.add(MimeTypes.APPLICATION_MPD);
} else if (contentType == C.TYPE_HLS) { } else if (contentType == C.CONTENT_TYPE_HLS) {
supportedMimeTypes.add(MimeTypes.APPLICATION_M3U8); supportedMimeTypes.add(MimeTypes.APPLICATION_M3U8);
} else if (contentType == C.TYPE_OTHER) { } else if (contentType == C.CONTENT_TYPE_OTHER) {
supportedMimeTypes.addAll( supportedMimeTypes.addAll(
Arrays.asList( Arrays.asList(
MimeTypes.VIDEO_MP4, MimeTypes.VIDEO_MP4,

View file

@ -17,6 +17,8 @@ package com.google.android.exoplayer2.ext.ima;
import static com.google.android.exoplayer2.ext.ima.ImaUtil.expandAdGroupPlaceholder; import static com.google.android.exoplayer2.ext.ima.ImaUtil.expandAdGroupPlaceholder;
import static com.google.android.exoplayer2.ext.ima.ImaUtil.getAdGroupAndIndexInMultiPeriodWindow; import static com.google.android.exoplayer2.ext.ima.ImaUtil.getAdGroupAndIndexInMultiPeriodWindow;
import static com.google.android.exoplayer2.ext.ima.ImaUtil.secToMsRounded;
import static com.google.android.exoplayer2.ext.ima.ImaUtil.secToUsRounded;
import static com.google.android.exoplayer2.ext.ima.ImaUtil.splitAdPlaybackStateForPeriods; import static com.google.android.exoplayer2.ext.ima.ImaUtil.splitAdPlaybackStateForPeriods;
import static com.google.android.exoplayer2.ext.ima.ImaUtil.updateAdDurationAndPropagate; import static com.google.android.exoplayer2.ext.ima.ImaUtil.updateAdDurationAndPropagate;
import static com.google.android.exoplayer2.ext.ima.ImaUtil.updateAdDurationInAdGroup; import static com.google.android.exoplayer2.ext.ima.ImaUtil.updateAdDurationInAdGroup;
@ -24,7 +26,6 @@ import static com.google.android.exoplayer2.source.ads.ServerSideAdInsertionUtil
import static com.google.android.exoplayer2.util.Assertions.checkNotNull; import static com.google.android.exoplayer2.util.Assertions.checkNotNull;
import static com.google.android.exoplayer2.util.Assertions.checkState; import static com.google.android.exoplayer2.util.Assertions.checkState;
import static com.google.android.exoplayer2.util.Util.msToUs; import static com.google.android.exoplayer2.util.Util.msToUs;
import static com.google.android.exoplayer2.util.Util.secToUs;
import static com.google.android.exoplayer2.util.Util.sum; import static com.google.android.exoplayer2.util.Util.sum;
import static com.google.android.exoplayer2.util.Util.usToMs; import static com.google.android.exoplayer2.util.Util.usToMs;
import static java.lang.Math.min; import static java.lang.Math.min;
@ -133,14 +134,14 @@ public final class ImaServerSideAdInsertionMediaSource extends CompositeMediaSou
@Override @Override
public MediaSource.Factory setLoadErrorHandlingPolicy( public MediaSource.Factory setLoadErrorHandlingPolicy(
@Nullable LoadErrorHandlingPolicy loadErrorHandlingPolicy) { LoadErrorHandlingPolicy loadErrorHandlingPolicy) {
contentMediaSourceFactory.setLoadErrorHandlingPolicy(loadErrorHandlingPolicy); contentMediaSourceFactory.setLoadErrorHandlingPolicy(loadErrorHandlingPolicy);
return this; return this;
} }
@Override @Override
public MediaSource.Factory setDrmSessionManagerProvider( public MediaSource.Factory setDrmSessionManagerProvider(
@Nullable DrmSessionManagerProvider drmSessionManagerProvider) { DrmSessionManagerProvider drmSessionManagerProvider) {
contentMediaSourceFactory.setDrmSessionManagerProvider(drmSessionManagerProvider); contentMediaSourceFactory.setDrmSessionManagerProvider(drmSessionManagerProvider);
return this; return this;
} }
@ -325,23 +326,31 @@ public final class ImaServerSideAdInsertionMediaSource extends CompositeMediaSou
@Override @Override
public Bundle toBundle() { public Bundle toBundle() {
Bundle bundle = new Bundle(); Bundle bundle = new Bundle();
bundle.putSerializable(keyForField(FIELD_AD_PLAYBACK_STATES), adPlaybackStates); Bundle adPlaybackStatesBundle = new Bundle();
for (Map.Entry<String, AdPlaybackState> entry : adPlaybackStates.entrySet()) {
adPlaybackStatesBundle.putBundle(entry.getKey(), entry.getValue().toBundle());
}
bundle.putBundle(keyForField(FIELD_AD_PLAYBACK_STATES), adPlaybackStatesBundle);
return bundle; return bundle;
} }
/** Object that can restore {@link AdsLoader.State} from a {@link Bundle}. */ /** Object that can restore {@link AdsLoader.State} from a {@link Bundle}. */
public static final Bundleable.Creator<State> CREATOR = State::fromBundle; public static final Bundleable.Creator<State> CREATOR = State::fromBundle;
@SuppressWarnings("unchecked")
private static State fromBundle(Bundle bundle) { private static State fromBundle(Bundle bundle) {
@Nullable @Nullable
Map<String, AdPlaybackState> adPlaybackStateMap = ImmutableMap.Builder<String, AdPlaybackState> adPlaybackStateMap =
(Map<String, AdPlaybackState>) new ImmutableMap.Builder<>();
bundle.getSerializable(keyForField(FIELD_AD_PLAYBACK_STATES)); Bundle adPlaybackStateBundle =
return new State( checkNotNull(bundle.getBundle(keyForField(FIELD_AD_PLAYBACK_STATES)));
adPlaybackStateMap != null for (String key : adPlaybackStateBundle.keySet()) {
? ImmutableMap.copyOf(adPlaybackStateMap) AdPlaybackState adPlaybackState =
: ImmutableMap.of()); AdPlaybackState.CREATOR.fromBundle(
checkNotNull(adPlaybackStateBundle.getBundle(key)));
adPlaybackStateMap.put(
key, AdPlaybackState.fromAdPlaybackState(/* adsId= */ key, adPlaybackState));
}
return new State(adPlaybackStateMap.buildOrThrow());
} }
private static String keyForField(@FieldNumber int field) { private static String keyForField(@FieldNumber int field) {
@ -596,7 +605,7 @@ public final class ImaServerSideAdInsertionMediaSource extends CompositeMediaSou
} }
@MainThread @MainThread
@EnsuresNonNull("contentTimeline") @EnsuresNonNull("this.contentTimeline")
private void setContentTimeline(Timeline contentTimeline) { private void setContentTimeline(Timeline contentTimeline) {
if (contentTimeline.equals(this.contentTimeline)) { if (contentTimeline.equals(this.contentTimeline)) {
return; return;
@ -651,19 +660,29 @@ public final class ImaServerSideAdInsertionMediaSource extends CompositeMediaSou
private static AdPlaybackState setVodAdGroupPlaceholders( private static AdPlaybackState setVodAdGroupPlaceholders(
List<CuePoint> cuePoints, AdPlaybackState adPlaybackState) { List<CuePoint> cuePoints, AdPlaybackState adPlaybackState) {
// TODO(b/192231683) Use getEndTimeMs()/getStartTimeMs() after jar target was removed
for (int i = 0; i < cuePoints.size(); i++) { for (int i = 0; i < cuePoints.size(); i++) {
CuePoint cuePoint = cuePoints.get(i); CuePoint cuePoint = cuePoints.get(i);
long fromPositionUs = msToUs(secToMsRounded(cuePoint.getStartTime()));
adPlaybackState = adPlaybackState =
addAdGroupToAdPlaybackState( addAdGroupToAdPlaybackState(
adPlaybackState, adPlaybackState,
/* fromPositionUs= */ secToUs(cuePoint.getStartTime()), /* fromPositionUs= */ fromPositionUs,
/* contentResumeOffsetUs= */ 0, /* contentResumeOffsetUs= */ 0,
// TODO(b/192231683) Use getEndTimeMs()/getStartTimeMs() after jar target was removed /* adDurationsUs...= */ getAdDuration(
/* adDurationsUs...= */ secToUs(cuePoint.getEndTime() - cuePoint.getStartTime())); /* startTimeSeconds= */ cuePoint.getStartTime(),
/* endTimeSeconds= */ cuePoint.getEndTime()));
} }
return adPlaybackState; return adPlaybackState;
} }
private static long getAdDuration(double startTimeSeconds, double endTimeSeconds) {
// startTimeSeconds and endTimeSeconds that are coming from the SDK, only have a precision of
// milliseconds so everything that is below a millisecond can be safely considered as coming
// from rounding issues.
return msToUs(secToMsRounded(endTimeSeconds - startTimeSeconds));
}
private static AdPlaybackState setVodAdInPlaceholder(Ad ad, AdPlaybackState adPlaybackState) { private static AdPlaybackState setVodAdInPlaceholder(Ad ad, AdPlaybackState adPlaybackState) {
AdPodInfo adPodInfo = ad.getAdPodInfo(); AdPodInfo adPodInfo = ad.getAdPodInfo();
// Handle post rolls that have a podIndex of -1. // Handle post rolls that have a podIndex of -1.
@ -675,9 +694,9 @@ public final class ImaServerSideAdInsertionMediaSource extends CompositeMediaSou
adPlaybackState = adPlaybackState =
expandAdGroupPlaceholder( expandAdGroupPlaceholder(
adGroupIndex, adGroupIndex,
/* adGroupDurationUs= */ secToUs(adPodInfo.getMaxDuration()), /* adGroupDurationUs= */ msToUs(secToMsRounded(adPodInfo.getMaxDuration())),
adIndexInAdGroup, adIndexInAdGroup,
/* adDurationUs= */ secToUs(ad.getDuration()), /* adDurationUs= */ msToUs(secToMsRounded(ad.getDuration())),
/* adsInAdGroupCount= */ adPodInfo.getTotalAds(), /* adsInAdGroupCount= */ adPodInfo.getTotalAds(),
adPlaybackState); adPlaybackState);
} else if (adIndexInAdGroup < adGroup.count - 1) { } else if (adIndexInAdGroup < adGroup.count - 1) {
@ -685,7 +704,7 @@ public final class ImaServerSideAdInsertionMediaSource extends CompositeMediaSou
updateAdDurationInAdGroup( updateAdDurationInAdGroup(
adGroupIndex, adGroupIndex,
adIndexInAdGroup, adIndexInAdGroup,
/* adDurationUs= */ secToUs(ad.getDuration()), /* adDurationUs= */ msToUs(secToMsRounded(ad.getDuration())),
adPlaybackState); adPlaybackState);
} }
return adPlaybackState; return adPlaybackState;
@ -694,7 +713,7 @@ public final class ImaServerSideAdInsertionMediaSource extends CompositeMediaSou
private AdPlaybackState addLiveAdBreak( private AdPlaybackState addLiveAdBreak(
Ad ad, long currentPeriodPositionUs, AdPlaybackState adPlaybackState) { Ad ad, long currentPeriodPositionUs, AdPlaybackState adPlaybackState) {
AdPodInfo adPodInfo = ad.getAdPodInfo(); AdPodInfo adPodInfo = ad.getAdPodInfo();
long adDurationUs = secToUs(ad.getDuration()); long adDurationUs = secToUsRounded(ad.getDuration());
int adIndexInAdGroup = adPodInfo.getAdPosition() - 1; int adIndexInAdGroup = adPodInfo.getAdPosition() - 1;
// TODO(b/208398934) Support seeking backwards. // TODO(b/208398934) Support seeking backwards.
if (adIndexInAdGroup == 0 || adPlaybackState.adGroupCount == 1) { if (adIndexInAdGroup == 0 || adPlaybackState.adGroupCount == 1) {
@ -708,7 +727,7 @@ public final class ImaServerSideAdInsertionMediaSource extends CompositeMediaSou
new long[adCount], new long[adCount],
adIndexInAdGroup, adIndexInAdGroup,
adDurationUs, adDurationUs,
secToUs(adPodInfo.getMaxDuration())); msToUs(secToMsRounded(adPodInfo.getMaxDuration())));
adPlaybackState = adPlaybackState =
addAdGroupToAdPlaybackState( addAdGroupToAdPlaybackState(
adPlaybackState, adPlaybackState,

View file

@ -72,7 +72,7 @@ public final class ImaServerSideAdInsertionUriBuilder {
public ImaServerSideAdInsertionUriBuilder() { public ImaServerSideAdInsertionUriBuilder() {
adTagParameters = ImmutableMap.of(); adTagParameters = ImmutableMap.of();
loadVideoTimeoutMs = DEFAULT_LOAD_VIDEO_TIMEOUT_MS; loadVideoTimeoutMs = DEFAULT_LOAD_VIDEO_TIMEOUT_MS;
format = C.TYPE_OTHER; format = C.CONTENT_TYPE_OTHER;
} }
/** /**
@ -137,11 +137,11 @@ public final class ImaServerSideAdInsertionUriBuilder {
/** /**
* Sets the format of the stream request. * Sets the format of the stream request.
* *
* @param format VOD or live stream type. * @param format {@link C#TYPE_DASH} or {@link C#TYPE_HLS}.
* @return This instance, for convenience. * @return This instance, for convenience.
*/ */
public ImaServerSideAdInsertionUriBuilder setFormat(@ContentType int format) { public ImaServerSideAdInsertionUriBuilder setFormat(@ContentType int format) {
checkArgument(format == C.TYPE_DASH || format == C.TYPE_HLS); checkArgument(format == C.CONTENT_TYPE_DASH || format == C.CONTENT_TYPE_HLS);
this.format = format; this.format = format;
return this; return this;
} }
@ -243,7 +243,7 @@ public final class ImaServerSideAdInsertionUriBuilder {
|| (!TextUtils.isEmpty(assetKey) || (!TextUtils.isEmpty(assetKey)
&& TextUtils.isEmpty(contentSourceId) && TextUtils.isEmpty(contentSourceId)
&& TextUtils.isEmpty(videoId))); && TextUtils.isEmpty(videoId)));
checkState(format != C.TYPE_OTHER); checkState(format != C.CONTENT_TYPE_OTHER);
@Nullable String adsId = this.adsId; @Nullable String adsId = this.adsId;
if (adsId == null) { if (adsId == null) {
adsId = assetKey != null ? assetKey : checkNotNull(videoId); adsId = assetKey != null ? assetKey : checkNotNull(videoId);
@ -330,9 +330,9 @@ public final class ImaServerSideAdInsertionUriBuilder {
.createVodStreamRequest(checkNotNull(contentSourceId), checkNotNull(videoId), apiKey); .createVodStreamRequest(checkNotNull(contentSourceId), checkNotNull(videoId), apiKey);
} }
int format = Integer.parseInt(uri.getQueryParameter(FORMAT)); int format = Integer.parseInt(uri.getQueryParameter(FORMAT));
if (format == C.TYPE_DASH) { if (format == C.CONTENT_TYPE_DASH) {
streamRequest.setFormat(StreamFormat.DASH); streamRequest.setFormat(StreamFormat.DASH);
} else if (format == C.TYPE_HLS) { } else if (format == C.CONTENT_TYPE_HLS) {
streamRequest.setFormat(StreamFormat.HLS); streamRequest.setFormat(StreamFormat.HLS);
} else { } else {
throw new IllegalArgumentException("Unsupported stream format:" + format); throw new IllegalArgumentException("Unsupported stream format:" + format);

View file

@ -54,7 +54,10 @@ import com.google.android.exoplayer2.upstream.DataSpec;
import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.util.Util;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap;
import com.google.common.math.DoubleMath;
import java.io.IOException; import java.io.IOException;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.HashMap; import java.util.HashMap;
@ -398,17 +401,13 @@ import java.util.Set;
long elapsedAdGroupAdDurationUs = 0; long elapsedAdGroupAdDurationUs = 0;
for (int j = periodIndex; j < contentTimeline.getPeriodCount(); j++) { for (int j = periodIndex; j < contentTimeline.getPeriodCount(); j++) {
contentTimeline.getPeriod(j, period, /* setIds= */ true); contentTimeline.getPeriod(j, period, /* setIds= */ true);
// TODO(b/192231683) Remove subtracted US from ad group time when we can upgrade the SDK. if (totalElapsedContentDurationUs < adGroup.timeUs) {
// Subtract one microsecond to work around rounding errors with adGroup.timeUs.
if (totalElapsedContentDurationUs < adGroup.timeUs - 1) {
// Period starts before the ad group, so it is a content period. // Period starts before the ad group, so it is a content period.
adPlaybackStates.put(checkNotNull(period.uid), contentOnlyAdPlaybackState); adPlaybackStates.put(checkNotNull(period.uid), contentOnlyAdPlaybackState);
totalElapsedContentDurationUs += period.durationUs; totalElapsedContentDurationUs += period.durationUs;
} else { } else {
long periodStartUs = totalElapsedContentDurationUs + elapsedAdGroupAdDurationUs; long periodStartUs = totalElapsedContentDurationUs + elapsedAdGroupAdDurationUs;
// TODO(b/192231683) Remove additional US when we can upgrade the SDK. if (periodStartUs + period.durationUs <= adGroup.timeUs + adGroupDurationUs) {
// Add one microsecond to work around rounding errors with adGroup.timeUs.
if (periodStartUs + period.durationUs <= adGroup.timeUs + adGroupDurationUs + 1) {
// The period ends before the end of the ad group, so it is an ad period (Note: A VOD ad // The period ends before the end of the ad group, so it is an ad period (Note: A VOD ad
// reported by the IMA SDK spans multiple periods before the LOADED event arrives). // reported by the IMA SDK spans multiple periods before the LOADED event arrives).
adPlaybackStates.put( adPlaybackStates.put(
@ -490,16 +489,12 @@ import java.util.Set;
long elapsedAdGroupAdDurationUs = 0; long elapsedAdGroupAdDurationUs = 0;
for (int j = periodIndex; j < contentTimeline.getPeriodCount(); j++) { for (int j = periodIndex; j < contentTimeline.getPeriodCount(); j++) {
contentTimeline.getPeriod(j, period, /* setIds= */ true); contentTimeline.getPeriod(j, period, /* setIds= */ true);
// TODO(b/192231683) Remove subtracted US from ad group time when we can upgrade the SDK. if (totalElapsedContentDurationUs < adGroup.timeUs) {
// Subtract one microsecond to work around rounding errors with adGroup.timeUs.
if (totalElapsedContentDurationUs < adGroup.timeUs - 1) {
// Period starts before the ad group, so it is a content period. // Period starts before the ad group, so it is a content period.
totalElapsedContentDurationUs += period.durationUs; totalElapsedContentDurationUs += period.durationUs;
} else { } else {
long periodStartUs = totalElapsedContentDurationUs + elapsedAdGroupAdDurationUs; long periodStartUs = totalElapsedContentDurationUs + elapsedAdGroupAdDurationUs;
// TODO(b/192231683) Remove additional US when we can upgrade the SDK. if (periodStartUs + period.durationUs <= adGroup.timeUs + adGroupDurationUs) {
// Add one microsecond to work around rounding errors with adGroup.timeUs.
if (periodStartUs + period.durationUs <= adGroup.timeUs + adGroupDurationUs + 1) {
// The period ends before the end of the ad group, so it is an ad period. // The period ends before the end of the ad group, so it is an ad period.
if (j == adPeriodIndex) { if (j == adPeriodIndex) {
return new Pair<>(/* adGroupIndex= */ i, adIndexInAdGroup); return new Pair<>(/* adGroupIndex= */ i, adIndexInAdGroup);
@ -518,5 +513,31 @@ import java.util.Set;
throw new IllegalStateException(); throw new IllegalStateException();
} }
/**
* Converts a time in seconds to the corresponding time in microseconds.
*
* <p>Fractional values are rounded to the nearest microsecond using {@link RoundingMode#HALF_UP}.
*
* @param timeSec The time in seconds.
* @return The corresponding time in microseconds.
*/
public static long secToUsRounded(double timeSec) {
return DoubleMath.roundToLong(
BigDecimal.valueOf(timeSec).scaleByPowerOfTen(6).doubleValue(), RoundingMode.HALF_UP);
}
/**
* Converts a time in seconds to the corresponding time in milliseconds.
*
* <p>Fractional values are rounded to the nearest millisecond using {@link RoundingMode#HALF_UP}.
*
* @param timeSec The time in seconds.
* @return The corresponding time in milliseconds.
*/
public static long secToMsRounded(double timeSec) {
return DoubleMath.roundToLong(
BigDecimal.valueOf(timeSec).scaleByPowerOfTen(3).doubleValue(), RoundingMode.HALF_UP);
}
private ImaUtil() {} private ImaUtil() {}
} }

View file

@ -24,7 +24,7 @@ import com.google.android.exoplayer2.MediaItem;
import com.google.android.exoplayer2.PlaybackException; import com.google.android.exoplayer2.PlaybackException;
import com.google.android.exoplayer2.Player; import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.TracksInfo; import com.google.android.exoplayer2.Tracks;
import com.google.android.exoplayer2.testutil.StubExoPlayer; import com.google.android.exoplayer2.testutil.StubExoPlayer;
import com.google.android.exoplayer2.trackselection.TrackSelectionArray; import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
import com.google.android.exoplayer2.trackselection.TrackSelectionParameters; import com.google.android.exoplayer2.trackselection.TrackSelectionParameters;
@ -79,7 +79,7 @@ import com.google.android.exoplayer2.util.Util;
PositionInfo oldPosition = PositionInfo oldPosition =
new PositionInfo( new PositionInfo(
windowUid, windowUid,
/* windowIndex= */ 0, /* mediaItemIndex= */ 0,
mediaItem, mediaItem,
periodUid, periodUid,
/* periodIndex= */ 0, /* periodIndex= */ 0,
@ -97,7 +97,7 @@ import com.google.android.exoplayer2.util.Util;
PositionInfo newPosition = PositionInfo newPosition =
new PositionInfo( new PositionInfo(
windowUid, windowUid,
/* windowIndex= */ 0, /* mediaItemIndex= */ 0,
mediaItem, mediaItem,
periodUid, periodUid,
/* periodIndex= */ 0, /* periodIndex= */ 0,
@ -128,7 +128,7 @@ import com.google.android.exoplayer2.util.Util;
PositionInfo oldPosition = PositionInfo oldPosition =
new PositionInfo( new PositionInfo(
windowUid, windowUid,
/* windowIndex= */ 0, /* mediaItemIndex= */ 0,
mediaItem, mediaItem,
periodUid, periodUid,
/* periodIndex= */ 0, /* periodIndex= */ 0,
@ -146,7 +146,7 @@ import com.google.android.exoplayer2.util.Util;
PositionInfo newPosition = PositionInfo newPosition =
new PositionInfo( new PositionInfo(
windowUid, windowUid,
/* windowIndex= */ 0, /* mediaItemIndex= */ 0,
mediaItem, mediaItem,
periodUid, periodUid,
/* periodIndex= */ 0, /* periodIndex= */ 0,
@ -266,8 +266,8 @@ import com.google.android.exoplayer2.util.Util;
} }
@Override @Override
public TracksInfo getCurrentTracksInfo() { public Tracks getCurrentTracks() {
return TracksInfo.EMPTY; return Tracks.EMPTY;
} }
@Override @Override

View file

@ -296,8 +296,8 @@ public final class ImaAdsLoaderTest {
/* periodIndex= */ 0, /* periodIndex= */ 0,
/* adGroupIndex= */ 0, /* adGroupIndex= */ 0,
/* adIndexInAdGroup= */ 0, /* adIndexInAdGroup= */ 0,
/* position= */ 0, /* positionMs= */ 0,
/* contentPosition= */ 0); /* contentPositionMs= */ 0);
fakePlayer.setState(Player.STATE_READY, /* playWhenReady= */ true); fakePlayer.setState(Player.STATE_READY, /* playWhenReady= */ true);
adEventListener.onAdEvent(getAdEvent(AdEventType.STARTED, mockPrerollSingleAd)); adEventListener.onAdEvent(getAdEvent(AdEventType.STARTED, mockPrerollSingleAd));
adEventListener.onAdEvent(getAdEvent(AdEventType.FIRST_QUARTILE, mockPrerollSingleAd)); adEventListener.onAdEvent(getAdEvent(AdEventType.FIRST_QUARTILE, mockPrerollSingleAd));
@ -962,7 +962,7 @@ public final class ImaAdsLoaderTest {
@Test @Test
public void setsDefaultMimeTypes() throws Exception { public void setsDefaultMimeTypes() throws Exception {
imaAdsLoader.setSupportedContentTypes(C.TYPE_DASH, C.TYPE_OTHER); imaAdsLoader.setSupportedContentTypes(C.CONTENT_TYPE_DASH, C.CONTENT_TYPE_OTHER);
imaAdsLoader.start( imaAdsLoader.start(
adsMediaSource, TEST_DATA_SPEC, TEST_ADS_ID, adViewProvider, adsLoaderListener); adsMediaSource, TEST_DATA_SPEC, TEST_ADS_ID, adViewProvider, adsLoaderListener);
@ -996,7 +996,7 @@ public final class ImaAdsLoaderTest {
adViewProvider); adViewProvider);
when(mockAdsManager.getAdCuePoints()).thenReturn(PREROLL_CUE_POINTS_SECONDS); when(mockAdsManager.getAdCuePoints()).thenReturn(PREROLL_CUE_POINTS_SECONDS);
imaAdsLoader.setSupportedContentTypes(C.TYPE_OTHER); imaAdsLoader.setSupportedContentTypes(C.CONTENT_TYPE_OTHER);
imaAdsLoader.start( imaAdsLoader.start(
adsMediaSource, TEST_DATA_SPEC, TEST_ADS_ID, adViewProvider, adsLoaderListener); adsMediaSource, TEST_DATA_SPEC, TEST_ADS_ID, adViewProvider, adsLoaderListener);
@ -1096,8 +1096,8 @@ public final class ImaAdsLoaderTest {
/* periodIndex= */ 0, /* periodIndex= */ 0,
/* adGroupIndex= */ 0, /* adGroupIndex= */ 0,
/* adIndexInAdGroup= */ 0, /* adIndexInAdGroup= */ 0,
/* position= */ 0, /* positionMs= */ 0,
/* contentPosition= */ 0); /* contentPositionMs= */ 0);
fakePlayer.setState(Player.STATE_READY, /* playWhenReady= */ true); fakePlayer.setState(Player.STATE_READY, /* playWhenReady= */ true);
adEventListener.onAdEvent(getAdEvent(AdEventType.STARTED, mockPrerollSingleAd)); adEventListener.onAdEvent(getAdEvent(AdEventType.STARTED, mockPrerollSingleAd));
adEventListener.onAdEvent(getAdEvent(AdEventType.FIRST_QUARTILE, mockPrerollSingleAd)); adEventListener.onAdEvent(getAdEvent(AdEventType.FIRST_QUARTILE, mockPrerollSingleAd));
@ -1155,8 +1155,8 @@ public final class ImaAdsLoaderTest {
/* periodIndex= */ 0, /* periodIndex= */ 0,
/* adGroupIndex= */ 0, /* adGroupIndex= */ 0,
/* adIndexInAdGroup= */ 0, /* adIndexInAdGroup= */ 0,
/* position= */ 0, /* positionMs= */ 0,
/* contentPosition= */ 0); /* contentPositionMs= */ 0);
fakePlayer.setState(Player.STATE_READY, /* playWhenReady= */ true); fakePlayer.setState(Player.STATE_READY, /* playWhenReady= */ true);
adEventListener.onAdEvent(getAdEvent(AdEventType.STARTED, mockPrerollSingleAd)); adEventListener.onAdEvent(getAdEvent(AdEventType.STARTED, mockPrerollSingleAd));
adEventListener.onAdEvent(getAdEvent(AdEventType.FIRST_QUARTILE, mockPrerollSingleAd)); adEventListener.onAdEvent(getAdEvent(AdEventType.FIRST_QUARTILE, mockPrerollSingleAd));
@ -1257,7 +1257,7 @@ public final class ImaAdsLoaderTest {
adViewProvider); adViewProvider);
when(mockAdsManager.getAdCuePoints()).thenReturn(PREROLL_CUE_POINTS_SECONDS); when(mockAdsManager.getAdCuePoints()).thenReturn(PREROLL_CUE_POINTS_SECONDS);
imaAdsLoader.setSupportedContentTypes(C.TYPE_OTHER); imaAdsLoader.setSupportedContentTypes(C.CONTENT_TYPE_OTHER);
imaAdsLoader.start( imaAdsLoader.start(
adsMediaSource, TEST_DATA_SPEC, TEST_ADS_ID, adViewProvider, adsLoaderListener); adsMediaSource, TEST_DATA_SPEC, TEST_ADS_ID, adViewProvider, adsLoaderListener);
@ -1283,7 +1283,7 @@ public final class ImaAdsLoaderTest {
adViewProvider); adViewProvider);
when(mockAdsManager.getAdCuePoints()).thenReturn(PREROLL_CUE_POINTS_SECONDS); when(mockAdsManager.getAdCuePoints()).thenReturn(PREROLL_CUE_POINTS_SECONDS);
imaAdsLoader.setSupportedContentTypes(C.TYPE_OTHER); imaAdsLoader.setSupportedContentTypes(C.CONTENT_TYPE_OTHER);
imaAdsLoader.start( imaAdsLoader.start(
adsMediaSource, TEST_DATA_SPEC, TEST_ADS_ID, adViewProvider, adsLoaderListener); adsMediaSource, TEST_DATA_SPEC, TEST_ADS_ID, adViewProvider, adsLoaderListener);

View file

@ -60,7 +60,7 @@ public final class ImaServerSideAdInsertionUriBuilderTest {
builder.setContentUrl(CONTENT_URL); builder.setContentUrl(CONTENT_URL);
builder.setAuthToken(AUTH_TOKEN); builder.setAuthToken(AUTH_TOKEN);
builder.setStreamActivityMonitorId(STREAM_ACTIVITY_MONITOR_ID); builder.setStreamActivityMonitorId(STREAM_ACTIVITY_MONITOR_ID);
builder.setFormat(C.TYPE_HLS); builder.setFormat(C.CONTENT_TYPE_HLS);
builder.setAdTagParameters(adTagParameters); builder.setAdTagParameters(adTagParameters);
builder.setLoadVideoTimeoutMs(ADS_LOADER_TIMEOUT_MS); builder.setLoadVideoTimeoutMs(ADS_LOADER_TIMEOUT_MS);
Uri uri = builder.build(); Uri uri = builder.build();
@ -96,7 +96,7 @@ public final class ImaServerSideAdInsertionUriBuilderTest {
builder.setContentUrl(CONTENT_URL); builder.setContentUrl(CONTENT_URL);
builder.setAuthToken(AUTH_TOKEN); builder.setAuthToken(AUTH_TOKEN);
builder.setStreamActivityMonitorId(STREAM_ACTIVITY_MONITOR_ID); builder.setStreamActivityMonitorId(STREAM_ACTIVITY_MONITOR_ID);
builder.setFormat(C.TYPE_DASH); builder.setFormat(C.CONTENT_TYPE_DASH);
builder.setAdTagParameters(adTagParameters); builder.setAdTagParameters(adTagParameters);
builder.setLoadVideoTimeoutMs(ADS_LOADER_TIMEOUT_MS); builder.setLoadVideoTimeoutMs(ADS_LOADER_TIMEOUT_MS);
Uri uri = builder.build(); Uri uri = builder.build();
@ -127,7 +127,7 @@ public final class ImaServerSideAdInsertionUriBuilderTest {
ImaServerSideAdInsertionUriBuilder builder = new ImaServerSideAdInsertionUriBuilder(); ImaServerSideAdInsertionUriBuilder builder = new ImaServerSideAdInsertionUriBuilder();
builder.setContentSourceId(CONTENT_SOURCE_ID); builder.setContentSourceId(CONTENT_SOURCE_ID);
builder.setVideoId(VIDEO_ID); builder.setVideoId(VIDEO_ID);
builder.setFormat(C.TYPE_DASH); builder.setFormat(C.CONTENT_TYPE_DASH);
Uri streamRequest = builder.build(); Uri streamRequest = builder.build();
@ -139,7 +139,7 @@ public final class ImaServerSideAdInsertionUriBuilderTest {
public void build_liveWithNoAdsId_usesAssetKeyAsDefault() { public void build_liveWithNoAdsId_usesAssetKeyAsDefault() {
ImaServerSideAdInsertionUriBuilder builder = new ImaServerSideAdInsertionUriBuilder(); ImaServerSideAdInsertionUriBuilder builder = new ImaServerSideAdInsertionUriBuilder();
builder.setAssetKey(ASSET_KEY); builder.setAssetKey(ASSET_KEY);
builder.setFormat(C.TYPE_DASH); builder.setFormat(C.CONTENT_TYPE_DASH);
Uri streamRequest = builder.build(); Uri streamRequest = builder.build();
@ -177,7 +177,7 @@ public final class ImaServerSideAdInsertionUriBuilderTest {
Uri uri = Uri uri =
new ImaServerSideAdInsertionUriBuilder() new ImaServerSideAdInsertionUriBuilder()
.setAssetKey(ASSET_KEY) .setAssetKey(ASSET_KEY)
.setFormat(C.TYPE_DASH) .setFormat(C.CONTENT_TYPE_DASH)
.build(); .build();
int loadVideoTimeoutMs = ImaServerSideAdInsertionUriBuilder.getLoadVideoTimeoutMs(uri); int loadVideoTimeoutMs = ImaServerSideAdInsertionUriBuilder.getLoadVideoTimeoutMs(uri);

View file

@ -29,12 +29,16 @@ import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.DataSourceException; import com.google.android.exoplayer2.upstream.DataSourceException;
import com.google.android.exoplayer2.upstream.DataSpec; import com.google.android.exoplayer2.upstream.DataSpec;
import com.google.android.exoplayer2.upstream.HttpDataSource; import com.google.android.exoplayer2.upstream.HttpDataSource;
import com.google.android.exoplayer2.upstream.HttpDataSource.HttpDataSourceException;
import com.google.android.exoplayer2.upstream.HttpDataSource.InvalidContentTypeException;
import com.google.android.exoplayer2.upstream.HttpDataSource.InvalidResponseCodeException;
import com.google.android.exoplayer2.upstream.HttpUtil; import com.google.android.exoplayer2.upstream.HttpUtil;
import com.google.android.exoplayer2.upstream.TransferListener; import com.google.android.exoplayer2.upstream.TransferListener;
import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.util.Util;
import com.google.common.base.Predicate; import com.google.common.base.Predicate;
import com.google.common.net.HttpHeaders; import com.google.common.net.HttpHeaders;
import com.google.common.util.concurrent.SettableFuture;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.InterruptedIOException; import java.io.InterruptedIOException;
@ -42,8 +46,10 @@ import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.concurrent.ExecutionException;
import okhttp3.CacheControl; import okhttp3.CacheControl;
import okhttp3.Call; import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.HttpUrl; import okhttp3.HttpUrl;
import okhttp3.MediaType; import okhttp3.MediaType;
import okhttp3.OkHttpClient; import okhttp3.OkHttpClient;
@ -179,21 +185,27 @@ public class OkHttpDataSource extends BaseDataSource implements HttpDataSource {
private long bytesToRead; private long bytesToRead;
private long bytesRead; private long bytesRead;
/** @deprecated Use {@link OkHttpDataSource.Factory} instead. */ /**
* @deprecated Use {@link OkHttpDataSource.Factory} instead.
*/
@SuppressWarnings("deprecation") @SuppressWarnings("deprecation")
@Deprecated @Deprecated
public OkHttpDataSource(Call.Factory callFactory) { public OkHttpDataSource(Call.Factory callFactory) {
this(callFactory, /* userAgent= */ null); this(callFactory, /* userAgent= */ null);
} }
/** @deprecated Use {@link OkHttpDataSource.Factory} instead. */ /**
* @deprecated Use {@link OkHttpDataSource.Factory} instead.
*/
@SuppressWarnings("deprecation") @SuppressWarnings("deprecation")
@Deprecated @Deprecated
public OkHttpDataSource(Call.Factory callFactory, @Nullable String userAgent) { public OkHttpDataSource(Call.Factory callFactory, @Nullable String userAgent) {
this(callFactory, userAgent, /* cacheControl= */ null, /* defaultRequestProperties= */ null); this(callFactory, userAgent, /* cacheControl= */ null, /* defaultRequestProperties= */ null);
} }
/** @deprecated Use {@link OkHttpDataSource.Factory} instead. */ /**
* @deprecated Use {@link OkHttpDataSource.Factory} instead.
*/
@Deprecated @Deprecated
public OkHttpDataSource( public OkHttpDataSource(
Call.Factory callFactory, Call.Factory callFactory,
@ -275,8 +287,9 @@ public class OkHttpDataSource extends BaseDataSource implements HttpDataSource {
Request request = makeRequest(dataSpec); Request request = makeRequest(dataSpec);
Response response; Response response;
ResponseBody responseBody; ResponseBody responseBody;
Call call = callFactory.newCall(request);
try { try {
this.response = callFactory.newCall(request).execute(); this.response = executeCall(call);
response = this.response; response = this.response;
responseBody = Assertions.checkNotNull(response.body()); responseBody = Assertions.checkNotNull(response.body());
responseByteStream = responseBody.byteStream(); responseByteStream = responseBody.byteStream();
@ -422,6 +435,35 @@ public class OkHttpDataSource extends BaseDataSource implements HttpDataSource {
return builder.build(); return builder.build();
} }
/**
* This method is an interrupt safe replacement of OkHttp Call.execute() which can get in bad
* states if interrupted while writing to the shared connection socket.
*/
private Response executeCall(Call call) throws IOException {
SettableFuture<Response> future = SettableFuture.create();
call.enqueue(
new Callback() {
@Override
public void onFailure(Call call, IOException e) {
future.setException(e);
}
@Override
public void onResponse(Call call, Response response) {
future.set(response);
}
});
try {
return future.get();
} catch (InterruptedException e) {
call.cancel();
throw new InterruptedIOException();
} catch (ExecutionException ee) {
throw new IOException(ee);
}
}
/** /**
* Attempts to skip the specified number of bytes in full. * Attempts to skip the specified number of bytes in full.
* *

View file

@ -22,7 +22,9 @@ import com.google.android.exoplayer2.upstream.TransferListener;
import okhttp3.CacheControl; import okhttp3.CacheControl;
import okhttp3.Call; import okhttp3.Call;
/** @deprecated Use {@link OkHttpDataSource.Factory} instead. */ /**
* @deprecated Use {@link OkHttpDataSource.Factory} instead.
*/
@Deprecated @Deprecated
public final class OkHttpDataSourceFactory extends BaseFactory { public final class OkHttpDataSourceFactory extends BaseFactory {

View file

@ -113,6 +113,7 @@ public class LibopusAudioRenderer extends DecoderAudioRenderer<OpusDecoder> {
format.initializationData, format.initializationData,
cryptoConfig, cryptoConfig,
outputFloat); outputFloat);
decoder.experimentalSetDiscardPaddingEnabled(experimentalGetDiscardPaddingEnabled());
TraceUtil.endSection(); TraceUtil.endSection();
return decoder; return decoder;
@ -124,4 +125,14 @@ public class LibopusAudioRenderer extends DecoderAudioRenderer<OpusDecoder> {
int pcmEncoding = decoder.outputFloat ? C.ENCODING_PCM_FLOAT : C.ENCODING_PCM_16BIT; int pcmEncoding = decoder.outputFloat ? C.ENCODING_PCM_FLOAT : C.ENCODING_PCM_16BIT;
return Util.getPcmFormat(pcmEncoding, decoder.channelCount, OpusDecoder.SAMPLE_RATE); return Util.getPcmFormat(pcmEncoding, decoder.channelCount, OpusDecoder.SAMPLE_RATE);
} }
/**
* Returns true if support for padding removal from the end of decoder output buffer should be
* enabled.
*
* <p>This method is experimental, and will be renamed or removed in a future release.
*/
protected boolean experimentalGetDiscardPaddingEnabled() {
return false;
}
} }

View file

@ -54,6 +54,7 @@ public final class OpusDecoder
private final int preSkipSamples; private final int preSkipSamples;
private final int seekPreRollSamples; private final int seekPreRollSamples;
private final long nativeDecoderContext; private final long nativeDecoderContext;
private boolean experimentalDiscardPaddingEnabled;
private int skipSamples; private int skipSamples;
@ -97,6 +98,7 @@ public final class OpusDecoder
} }
preSkipSamples = getPreSkipSamples(initializationData); preSkipSamples = getPreSkipSamples(initializationData);
seekPreRollSamples = getSeekPreRollSamples(initializationData); seekPreRollSamples = getSeekPreRollSamples(initializationData);
skipSamples = preSkipSamples;
byte[] headerBytes = initializationData.get(0); byte[] headerBytes = initializationData.get(0);
if (headerBytes.length < 19) { if (headerBytes.length < 19) {
@ -142,6 +144,16 @@ public final class OpusDecoder
} }
} }
/**
* Sets whether discard padding is enabled. When enabled, discard padding samples (provided as
* supplemental data on the input buffer) will be removed from the end of the decoder output.
*
* <p>This method is experimental, and will be renamed or removed in a future release.
*/
public void experimentalSetDiscardPaddingEnabled(boolean enabled) {
this.experimentalDiscardPaddingEnabled = enabled;
}
@Override @Override
public String getName() { public String getName() {
return "libopus" + OpusLibrary.getVersion(); return "libopus" + OpusLibrary.getVersion();
@ -221,6 +233,14 @@ public final class OpusDecoder
skipSamples = 0; skipSamples = 0;
outputData.position(skipBytes); outputData.position(skipBytes);
} }
} else if (experimentalDiscardPaddingEnabled && inputBuffer.hasSupplementalData()) {
int discardPaddingSamples = getDiscardPaddingSamples(inputBuffer.supplementalData);
if (discardPaddingSamples > 0) {
int discardBytes = samplesToBytes(discardPaddingSamples, channelCount, outputFloat);
if (result >= discardBytes) {
outputData.limit(result - discardBytes);
}
}
} }
return null; return null;
} }
@ -278,6 +298,25 @@ public final class OpusDecoder
return DEFAULT_SEEK_PRE_ROLL_SAMPLES; return DEFAULT_SEEK_PRE_ROLL_SAMPLES;
} }
/**
* Returns the number of discard padding samples specified by the supplemental data attached to an
* input buffer.
*
* @param supplementalData Supplemental data related to the an input buffer.
* @return The number of discard padding samples to remove from the decoder output.
*/
@VisibleForTesting
/* package */ static int getDiscardPaddingSamples(@Nullable ByteBuffer supplementalData) {
if (supplementalData == null || supplementalData.remaining() != 8) {
return 0;
}
long discardPaddingNs = supplementalData.order(ByteOrder.LITTLE_ENDIAN).getLong();
if (discardPaddingNs < 0) {
return 0;
}
return (int) ((discardPaddingNs * SAMPLE_RATE) / C.NANOS_PER_SECOND);
}
/** Returns number of bytes to represent {@code samples}. */ /** Returns number of bytes to represent {@code samples}. */
private static int samplesToBytes(int samples, int channelCount, boolean outputFloat) { private static int samplesToBytes(int samples, int channelCount, boolean outputFloat) {
int bytesPerChannel = outputFloat ? 4 : 2; int bytesPerChannel = outputFloat ? 4 : 2;

View file

@ -14,17 +14,26 @@
* limitations under the License. * limitations under the License.
*/ */
#ifdef __ANDROID__
#include <android/log.h> #include <android/log.h>
#endif
#include <jni.h> #include <jni.h>
#include <cstdint>
#include <cstdlib> #include <cstdlib>
#include "opus.h" // NOLINT #include "opus.h" // NOLINT
#include "opus_multistream.h" // NOLINT #include "opus_multistream.h" // NOLINT
#ifdef __ANDROID__
#define LOG_TAG "opus_jni" #define LOG_TAG "opus_jni"
#define LOGE(...) \ #define LOGE(...) \
((void)__android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)) ((void)__android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__))
#else // __ANDROID__
#define LOGE(...) \
do { \
} while (0)
#endif // __ANDROID__
#define DECODER_FUNC(RETURN_TYPE, NAME, ...) \ #define DECODER_FUNC(RETURN_TYPE, NAME, ...) \
extern "C" { \ extern "C" { \

View file

@ -16,9 +16,14 @@
package com.google.android.exoplayer2.ext.opus; package com.google.android.exoplayer2.ext.opus;
import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assume.assumeTrue;
import androidx.annotation.Nullable;
import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.decoder.DecoderInputBuffer;
import com.google.android.exoplayer2.decoder.SimpleDecoderOutputBuffer;
import com.google.android.exoplayer2.util.LibraryLoader;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.ByteOrder; import java.nio.ByteOrder;
@ -29,16 +34,30 @@ import org.junit.runner.RunWith;
@RunWith(AndroidJUnit4.class) @RunWith(AndroidJUnit4.class)
public final class OpusDecoderTest { public final class OpusDecoderTest {
private static final LibraryLoader LOADER =
new LibraryLoader("opusV2JNI") {
@Override
protected void loadLibrary(String name) {
System.loadLibrary(name);
}
};
private static final byte[] HEADER = private static final byte[] HEADER =
new byte[] {79, 112, 117, 115, 72, 101, 97, 100, 0, 2, 1, 56, 0, 0, -69, -128, 0, 0, 0}; new byte[] {79, 112, 117, 115, 72, 101, 97, 100, 0, 2, 1, 56, 0, 0, -69, -128, 0, 0, 0};
private static final byte[] ENCODED_DATA = new byte[] {-4};
private static final int DECODED_DATA_SIZE = 3840;
private static final int HEADER_PRE_SKIP_SAMPLES = 14337; private static final int HEADER_PRE_SKIP_SAMPLES = 14337;
private static final int DEFAULT_SEEK_PRE_ROLL_SAMPLES = 3840; private static final int DEFAULT_SEEK_PRE_ROLL_SAMPLES = 3840;
private static final int DISCARD_PADDING_NANOS = 166667;
private static final ImmutableList<byte[]> HEADER_ONLY_INITIALIZATION_DATA = private static final ImmutableList<byte[]> HEADER_ONLY_INITIALIZATION_DATA =
ImmutableList.of(HEADER); ImmutableList.of(HEADER);
private static final long PRE_SKIP_NANOS = 6_500_000;
private static final long CUSTOM_PRE_SKIP_SAMPLES = 28674; private static final long CUSTOM_PRE_SKIP_SAMPLES = 28674;
private static final byte[] CUSTOM_PRE_SKIP_BYTES = private static final byte[] CUSTOM_PRE_SKIP_BYTES =
buildNativeOrderByteArray(sampleCountToNanoseconds(CUSTOM_PRE_SKIP_SAMPLES)); buildNativeOrderByteArray(sampleCountToNanoseconds(CUSTOM_PRE_SKIP_SAMPLES));
@ -80,11 +99,124 @@ public final class OpusDecoderTest {
assertThat(seekPreRollSamples).isEqualTo(DEFAULT_SEEK_PRE_ROLL_SAMPLES); assertThat(seekPreRollSamples).isEqualTo(DEFAULT_SEEK_PRE_ROLL_SAMPLES);
} }
@Test
public void getDiscardPaddingSamples_positiveSampleLength_returnSampleLength() {
int discardPaddingSamples =
OpusDecoder.getDiscardPaddingSamples(createSupplementalData(DISCARD_PADDING_NANOS));
assertThat(discardPaddingSamples).isEqualTo(nanosecondsToSampleCount(DISCARD_PADDING_NANOS));
}
@Test
public void getDiscardPaddingSamples_negativeSampleLength_returnZero() {
int discardPaddingSamples =
OpusDecoder.getDiscardPaddingSamples(createSupplementalData(-DISCARD_PADDING_NANOS));
assertThat(discardPaddingSamples).isEqualTo(0);
}
@Test
public void decode_removesPreSkipFromOutput() throws OpusDecoderException {
assumeTrue(LOADER.isAvailable());
OpusDecoder decoder =
new OpusDecoder(
/* numInputBuffers= */ 0,
/* numOutputBuffers= */ 0,
/* initialInputBufferSize= */ 0,
createInitializationData(/* preSkipNanos= */ PRE_SKIP_NANOS),
/* cryptoConfig= */ null,
/* outputFloat= */ false);
DecoderInputBuffer input =
createInputBuffer(decoder, ENCODED_DATA, /* supplementalData= */ null);
SimpleDecoderOutputBuffer output = decoder.createOutputBuffer();
assertThat(decoder.decode(input, output, false)).isNull();
assertThat(output.data.remaining())
.isEqualTo(DECODED_DATA_SIZE - nanosecondsToBytes(PRE_SKIP_NANOS));
}
@Test
public void decode_whenDiscardPaddingDisabled_returnsDiscardPadding()
throws OpusDecoderException {
assumeTrue(LOADER.isAvailable());
OpusDecoder decoder =
new OpusDecoder(
/* numInputBuffers= */ 0,
/* numOutputBuffers= */ 0,
/* initialInputBufferSize= */ 0,
createInitializationData(/* preSkipNanos= */ 0),
/* cryptoConfig= */ null,
/* outputFloat= */ false);
DecoderInputBuffer input =
createInputBuffer(
decoder,
ENCODED_DATA,
/* supplementalData= */ buildNativeOrderByteArray(DISCARD_PADDING_NANOS));
SimpleDecoderOutputBuffer output = decoder.createOutputBuffer();
assertThat(decoder.decode(input, output, false)).isNull();
assertThat(output.data.remaining()).isEqualTo(DECODED_DATA_SIZE);
}
@Test
public void decode_whenDiscardPaddingEnabled_removesDiscardPadding() throws OpusDecoderException {
assumeTrue(LOADER.isAvailable());
OpusDecoder decoder =
new OpusDecoder(
/* numInputBuffers= */ 0,
/* numOutputBuffers= */ 0,
/* initialInputBufferSize= */ 0,
createInitializationData(/* preSkipNanos= */ 0),
/* cryptoConfig= */ null,
/* outputFloat= */ false);
decoder.experimentalSetDiscardPaddingEnabled(true);
DecoderInputBuffer input =
createInputBuffer(
decoder,
ENCODED_DATA,
/* supplementalData= */ buildNativeOrderByteArray(DISCARD_PADDING_NANOS));
SimpleDecoderOutputBuffer output = decoder.createOutputBuffer();
assertThat(decoder.decode(input, output, false)).isNull();
assertThat(output.data.limit())
.isEqualTo(DECODED_DATA_SIZE - nanosecondsToBytes(DISCARD_PADDING_NANOS));
}
private static long sampleCountToNanoseconds(long sampleCount) { private static long sampleCountToNanoseconds(long sampleCount) {
return (sampleCount * C.NANOS_PER_SECOND) / OpusDecoder.SAMPLE_RATE; return (sampleCount * C.NANOS_PER_SECOND) / OpusDecoder.SAMPLE_RATE;
} }
private static long nanosecondsToSampleCount(long nanoseconds) {
return (nanoseconds * OpusDecoder.SAMPLE_RATE) / C.NANOS_PER_SECOND;
}
private static long nanosecondsToBytes(long nanoseconds) {
return nanosecondsToSampleCount(nanoseconds) * 4;
}
private static byte[] buildNativeOrderByteArray(long value) { private static byte[] buildNativeOrderByteArray(long value) {
return ByteBuffer.allocate(8).order(ByteOrder.nativeOrder()).putLong(value).array(); return ByteBuffer.allocate(8).order(ByteOrder.nativeOrder()).putLong(value).array();
} }
private static ImmutableList<byte[]> createInitializationData(long preSkipNanos) {
byte[] preSkip = buildNativeOrderByteArray(preSkipNanos);
return ImmutableList.of(HEADER, preSkip, CUSTOM_SEEK_PRE_ROLL_BYTES);
}
// The cast to ByteBuffer is required for Java 8 compatibility. See
// https://issues.apache.org/jira/browse/MRESOLVER-85
@SuppressWarnings("UnnecessaryCast")
private static ByteBuffer createSupplementalData(long value) {
return (ByteBuffer)
ByteBuffer.allocate(8).order(ByteOrder.LITTLE_ENDIAN).putLong(value).rewind();
}
private static DecoderInputBuffer createInputBuffer(
OpusDecoder decoder, byte[] data, @Nullable byte[] supplementalData) {
DecoderInputBuffer input = decoder.createInputBuffer();
input.ensureSpaceForWrite(data.length);
input.data.put(data);
input.data.position(0).limit(data.length);
if (supplementalData != null) {
input.resetSupplementalData(supplementalData.length);
input.supplementalData.put(supplementalData).rewind();
input.addFlag(C.BUFFER_FLAG_HAS_SUPPLEMENTAL_DATA);
}
return input;
}
} }

View file

@ -39,7 +39,7 @@ injected from application code.
`DefaultDataSource` will automatically use the RTMP extension whenever it's `DefaultDataSource` will automatically use the RTMP extension whenever it's
available. Hence if your application is using `DefaultDataSource` or available. Hence if your application is using `DefaultDataSource` or
`DefaultDataSourceFactory`, adding support for RTMP streams is as simple as `DefaultDataSource.Factory`, adding support for RTMP streams is as simple as
adding a dependency to the RTMP extension as described above. No changes to your adding a dependency to the RTMP extension as described above. No changes to your
application code are required. Alternatively, if you know that your application application code are required. Alternatively, if you know that your application
doesn't need to handle any other protocols, you can update any doesn't need to handle any other protocols, you can update any

View file

@ -19,7 +19,9 @@ import androidx.annotation.Nullable;
import com.google.android.exoplayer2.upstream.DataSource; import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.TransferListener; import com.google.android.exoplayer2.upstream.TransferListener;
/** @deprecated Use {@link RtmpDataSource.Factory} instead. */ /**
* @deprecated Use {@link RtmpDataSource.Factory} instead.
*/
@Deprecated @Deprecated
public final class RtmpDataSourceFactory implements DataSource.Factory { public final class RtmpDataSourceFactory implements DataSource.Factory {
@ -29,7 +31,9 @@ public final class RtmpDataSourceFactory implements DataSource.Factory {
this(null); this(null);
} }
/** @param listener An optional listener. */ /**
* @param listener An optional listener.
*/
public RtmpDataSourceFactory(@Nullable TransferListener listener) { public RtmpDataSourceFactory(@Nullable TransferListener listener) {
this.listener = listener; this.listener = listener;
} }

View file

@ -54,7 +54,9 @@ public final class WorkManagerScheduler implements Scheduler {
private final WorkManager workManager; private final WorkManager workManager;
private final String workName; private final String workName;
/** @deprecated Call {@link #WorkManagerScheduler(Context, String)} instead. */ /**
* @deprecated Call {@link #WorkManagerScheduler(Context, String)} instead.
*/
@Deprecated @Deprecated
@SuppressWarnings("deprecation") @SuppressWarnings("deprecation")
public WorkManagerScheduler(String workName) { public WorkManagerScheduler(String workName) {

View file

@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-all.zip distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-all.zip
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists

View file

@ -48,9 +48,21 @@ class CombinedJavadocPlugin implements Plugin<Project> {
libraryModule.android.libraryVariants.all { variant -> libraryModule.android.libraryVariants.all { variant ->
def name = variant.buildType.name def name = variant.buildType.name
if (name == "release") { if (name == "release") {
// Works around b/234569640 that causes different versions of the androidx.media
// jar to be on the classpath.
def allJarFiles = []
allJarFiles.addAll(variant.javaCompileProvider.get().classpath.files)
def filteredJarFiles = allJarFiles.findAll { file ->
if (file ==~ /.*media-.\..\..-api.jar$/
&& !file.path.endsWith(
"media-" + project.ext.androidxMediaVersion + "-api.jar")) {
return false;
}
return true;
}
classpath += classpath +=
libraryModule.project.files( libraryModule.project.files(
variant.javaCompileProvider.get().classpath.files, filteredJarFiles,
libraryModule.project.android.getBootClasspath()) libraryModule.project.android.getBootClasspath())
} }
} }

View file

@ -92,7 +92,7 @@ public abstract class BasePlayer implements Player {
/** /**
* {@inheritDoc} * {@inheritDoc}
* *
* <p>BasePlayer and its descendents will return {@code true}. * <p>BasePlayer and its descendants will return {@code true}.
*/ */
@Override @Override
public final boolean canAdvertiseSession() { public final boolean canAdvertiseSession() {
@ -141,12 +141,18 @@ public abstract class BasePlayer implements Player {
seekToOffset(getSeekForwardIncrement()); seekToOffset(getSeekForwardIncrement());
} }
/**
* @deprecated Use {@link #hasPreviousMediaItem()} instead.
*/
@Deprecated @Deprecated
@Override @Override
public final boolean hasPrevious() { public final boolean hasPrevious() {
return hasPreviousMediaItem(); return hasPreviousMediaItem();
} }
/**
* @deprecated Use {@link #hasPreviousMediaItem()} instead.
*/
@Deprecated @Deprecated
@Override @Override
public final boolean hasPreviousWindow() { public final boolean hasPreviousWindow() {
@ -158,12 +164,18 @@ public abstract class BasePlayer implements Player {
return getPreviousMediaItemIndex() != C.INDEX_UNSET; return getPreviousMediaItemIndex() != C.INDEX_UNSET;
} }
/**
* @deprecated Use {@link #seekToPreviousMediaItem()} instead.
*/
@Deprecated @Deprecated
@Override @Override
public final void previous() { public final void previous() {
seekToPreviousMediaItem(); seekToPreviousMediaItem();
} }
/**
* @deprecated Use {@link #seekToPreviousMediaItem()} instead.
*/
@Deprecated @Deprecated
@Override @Override
public final void seekToPreviousWindow() { public final void seekToPreviousWindow() {
@ -196,12 +208,18 @@ public abstract class BasePlayer implements Player {
} }
} }
/**
* @deprecated Use {@link #hasNextMediaItem()} instead.
*/
@Deprecated @Deprecated
@Override @Override
public final boolean hasNext() { public final boolean hasNext() {
return hasNextMediaItem(); return hasNextMediaItem();
} }
/**
* @deprecated Use {@link #hasNextMediaItem()} instead.
*/
@Deprecated @Deprecated
@Override @Override
public final boolean hasNextWindow() { public final boolean hasNextWindow() {
@ -213,12 +231,18 @@ public abstract class BasePlayer implements Player {
return getNextMediaItemIndex() != C.INDEX_UNSET; return getNextMediaItemIndex() != C.INDEX_UNSET;
} }
/**
* @deprecated Use {@link #seekToNextMediaItem()} instead.
*/
@Deprecated @Deprecated
@Override @Override
public final void next() { public final void next() {
seekToNextMediaItem(); seekToNextMediaItem();
} }
/**
* @deprecated Use {@link #seekToNextMediaItem()} instead.
*/
@Deprecated @Deprecated
@Override @Override
public final void seekToNextWindow() { public final void seekToNextWindow() {
@ -251,12 +275,18 @@ public abstract class BasePlayer implements Player {
setPlaybackParameters(getPlaybackParameters().withSpeed(speed)); setPlaybackParameters(getPlaybackParameters().withSpeed(speed));
} }
/**
* @deprecated Use {@link #getCurrentMediaItemIndex()} instead.
*/
@Deprecated @Deprecated
@Override @Override
public final int getCurrentWindowIndex() { public final int getCurrentWindowIndex() {
return getCurrentMediaItemIndex(); return getCurrentMediaItemIndex();
} }
/**
* @deprecated Use {@link #getNextMediaItemIndex()} instead.
*/
@Deprecated @Deprecated
@Override @Override
public final int getNextWindowIndex() { public final int getNextWindowIndex() {
@ -272,6 +302,9 @@ public abstract class BasePlayer implements Player {
getCurrentMediaItemIndex(), getRepeatModeForNavigation(), getShuffleModeEnabled()); getCurrentMediaItemIndex(), getRepeatModeForNavigation(), getShuffleModeEnabled());
} }
/**
* @deprecated Use {@link #getPreviousMediaItemIndex()} instead.
*/
@Deprecated @Deprecated
@Override @Override
public final int getPreviousWindowIndex() { public final int getPreviousWindowIndex() {
@ -324,6 +357,9 @@ public abstract class BasePlayer implements Player {
: duration == 0 ? 100 : Util.constrainValue((int) ((position * 100) / duration), 0, 100); : duration == 0 ? 100 : Util.constrainValue((int) ((position * 100) / duration), 0, 100);
} }
/**
* @deprecated Use {@link #isCurrentMediaItemDynamic()} instead.
*/
@Deprecated @Deprecated
@Override @Override
public final boolean isCurrentWindowDynamic() { public final boolean isCurrentWindowDynamic() {
@ -336,6 +372,9 @@ public abstract class BasePlayer implements Player {
return !timeline.isEmpty() && timeline.getWindow(getCurrentMediaItemIndex(), window).isDynamic; return !timeline.isEmpty() && timeline.getWindow(getCurrentMediaItemIndex(), window).isDynamic;
} }
/**
* @deprecated Use {@link #isCurrentMediaItemLive()} instead.
*/
@Deprecated @Deprecated
@Override @Override
public final boolean isCurrentWindowLive() { public final boolean isCurrentWindowLive() {
@ -362,6 +401,9 @@ public abstract class BasePlayer implements Player {
return window.getCurrentUnixTimeMs() - window.windowStartTimeMs - getContentPosition(); return window.getCurrentUnixTimeMs() - window.windowStartTimeMs - getContentPosition();
} }
/**
* @deprecated Use {@link #isCurrentMediaItemSeekable()} instead.
*/
@Deprecated @Deprecated
@Override @Override
public final boolean isCurrentWindowSeekable() { public final boolean isCurrentWindowSeekable() {

View file

@ -69,6 +69,9 @@ public final class C {
/** Represents an unset or unknown rate. */ /** Represents an unset or unknown rate. */
public static final float RATE_UNSET = -Float.MAX_VALUE; public static final float RATE_UNSET = -Float.MAX_VALUE;
/** Represents an unset or unknown integer rate. */
public static final int RATE_UNSET_INT = Integer.MIN_VALUE + 1;
/** Represents an unset or unknown length. */ /** Represents an unset or unknown length. */
public static final int LENGTH_UNSET = -1; public static final int LENGTH_UNSET = -1;
@ -166,11 +169,17 @@ public final class C {
@Target(TYPE_USE) @Target(TYPE_USE)
@IntDef({CRYPTO_MODE_UNENCRYPTED, CRYPTO_MODE_AES_CTR, CRYPTO_MODE_AES_CBC}) @IntDef({CRYPTO_MODE_UNENCRYPTED, CRYPTO_MODE_AES_CTR, CRYPTO_MODE_AES_CBC})
public @interface CryptoMode {} public @interface CryptoMode {}
/** @see MediaCodec#CRYPTO_MODE_UNENCRYPTED */ /**
* @see MediaCodec#CRYPTO_MODE_UNENCRYPTED
*/
public static final int CRYPTO_MODE_UNENCRYPTED = MediaCodec.CRYPTO_MODE_UNENCRYPTED; public static final int CRYPTO_MODE_UNENCRYPTED = MediaCodec.CRYPTO_MODE_UNENCRYPTED;
/** @see MediaCodec#CRYPTO_MODE_AES_CTR */ /**
* @see MediaCodec#CRYPTO_MODE_AES_CTR
*/
public static final int CRYPTO_MODE_AES_CTR = MediaCodec.CRYPTO_MODE_AES_CTR; public static final int CRYPTO_MODE_AES_CTR = MediaCodec.CRYPTO_MODE_AES_CTR;
/** @see MediaCodec#CRYPTO_MODE_AES_CBC */ /**
* @see MediaCodec#CRYPTO_MODE_AES_CBC
*/
public static final int CRYPTO_MODE_AES_CBC = MediaCodec.CRYPTO_MODE_AES_CBC; public static final int CRYPTO_MODE_AES_CBC = MediaCodec.CRYPTO_MODE_AES_CBC;
/** /**
@ -236,11 +245,17 @@ public final class C {
ENCODING_PCM_FLOAT ENCODING_PCM_FLOAT
}) })
public @interface PcmEncoding {} public @interface PcmEncoding {}
/** @see AudioFormat#ENCODING_INVALID */ /**
* @see AudioFormat#ENCODING_INVALID
*/
public static final int ENCODING_INVALID = AudioFormat.ENCODING_INVALID; public static final int ENCODING_INVALID = AudioFormat.ENCODING_INVALID;
/** @see AudioFormat#ENCODING_PCM_8BIT */ /**
* @see AudioFormat#ENCODING_PCM_8BIT
*/
public static final int ENCODING_PCM_8BIT = AudioFormat.ENCODING_PCM_8BIT; public static final int ENCODING_PCM_8BIT = AudioFormat.ENCODING_PCM_8BIT;
/** @see AudioFormat#ENCODING_PCM_16BIT */ /**
* @see AudioFormat#ENCODING_PCM_16BIT
*/
public static final int ENCODING_PCM_16BIT = AudioFormat.ENCODING_PCM_16BIT; public static final int ENCODING_PCM_16BIT = AudioFormat.ENCODING_PCM_16BIT;
/** Like {@link #ENCODING_PCM_16BIT}, but with the bytes in big endian order. */ /** Like {@link #ENCODING_PCM_16BIT}, but with the bytes in big endian order. */
public static final int ENCODING_PCM_16BIT_BIG_ENDIAN = 0x10000000; public static final int ENCODING_PCM_16BIT_BIG_ENDIAN = 0x10000000;
@ -248,35 +263,63 @@ public final class C {
public static final int ENCODING_PCM_24BIT = 0x20000000; public static final int ENCODING_PCM_24BIT = 0x20000000;
/** PCM encoding with 32 bits per sample. */ /** PCM encoding with 32 bits per sample. */
public static final int ENCODING_PCM_32BIT = 0x30000000; public static final int ENCODING_PCM_32BIT = 0x30000000;
/** @see AudioFormat#ENCODING_PCM_FLOAT */ /**
* @see AudioFormat#ENCODING_PCM_FLOAT
*/
public static final int ENCODING_PCM_FLOAT = AudioFormat.ENCODING_PCM_FLOAT; public static final int ENCODING_PCM_FLOAT = AudioFormat.ENCODING_PCM_FLOAT;
/** @see AudioFormat#ENCODING_MP3 */ /**
* @see AudioFormat#ENCODING_MP3
*/
public static final int ENCODING_MP3 = AudioFormat.ENCODING_MP3; public static final int ENCODING_MP3 = AudioFormat.ENCODING_MP3;
/** @see AudioFormat#ENCODING_AAC_LC */ /**
* @see AudioFormat#ENCODING_AAC_LC
*/
public static final int ENCODING_AAC_LC = AudioFormat.ENCODING_AAC_LC; public static final int ENCODING_AAC_LC = AudioFormat.ENCODING_AAC_LC;
/** @see AudioFormat#ENCODING_AAC_HE_V1 */ /**
* @see AudioFormat#ENCODING_AAC_HE_V1
*/
public static final int ENCODING_AAC_HE_V1 = AudioFormat.ENCODING_AAC_HE_V1; public static final int ENCODING_AAC_HE_V1 = AudioFormat.ENCODING_AAC_HE_V1;
/** @see AudioFormat#ENCODING_AAC_HE_V2 */ /**
* @see AudioFormat#ENCODING_AAC_HE_V2
*/
public static final int ENCODING_AAC_HE_V2 = AudioFormat.ENCODING_AAC_HE_V2; public static final int ENCODING_AAC_HE_V2 = AudioFormat.ENCODING_AAC_HE_V2;
/** @see AudioFormat#ENCODING_AAC_XHE */ /**
* @see AudioFormat#ENCODING_AAC_XHE
*/
public static final int ENCODING_AAC_XHE = AudioFormat.ENCODING_AAC_XHE; public static final int ENCODING_AAC_XHE = AudioFormat.ENCODING_AAC_XHE;
/** @see AudioFormat#ENCODING_AAC_ELD */ /**
* @see AudioFormat#ENCODING_AAC_ELD
*/
public static final int ENCODING_AAC_ELD = AudioFormat.ENCODING_AAC_ELD; public static final int ENCODING_AAC_ELD = AudioFormat.ENCODING_AAC_ELD;
/** AAC Error Resilient Bit-Sliced Arithmetic Coding. */ /** AAC Error Resilient Bit-Sliced Arithmetic Coding. */
public static final int ENCODING_AAC_ER_BSAC = 0x40000000; public static final int ENCODING_AAC_ER_BSAC = 0x40000000;
/** @see AudioFormat#ENCODING_AC3 */ /**
* @see AudioFormat#ENCODING_AC3
*/
public static final int ENCODING_AC3 = AudioFormat.ENCODING_AC3; public static final int ENCODING_AC3 = AudioFormat.ENCODING_AC3;
/** @see AudioFormat#ENCODING_E_AC3 */ /**
* @see AudioFormat#ENCODING_E_AC3
*/
public static final int ENCODING_E_AC3 = AudioFormat.ENCODING_E_AC3; public static final int ENCODING_E_AC3 = AudioFormat.ENCODING_E_AC3;
/** @see AudioFormat#ENCODING_E_AC3_JOC */ /**
* @see AudioFormat#ENCODING_E_AC3_JOC
*/
public static final int ENCODING_E_AC3_JOC = AudioFormat.ENCODING_E_AC3_JOC; public static final int ENCODING_E_AC3_JOC = AudioFormat.ENCODING_E_AC3_JOC;
/** @see AudioFormat#ENCODING_AC4 */ /**
* @see AudioFormat#ENCODING_AC4
*/
public static final int ENCODING_AC4 = AudioFormat.ENCODING_AC4; public static final int ENCODING_AC4 = AudioFormat.ENCODING_AC4;
/** @see AudioFormat#ENCODING_DTS */ /**
* @see AudioFormat#ENCODING_DTS
*/
public static final int ENCODING_DTS = AudioFormat.ENCODING_DTS; public static final int ENCODING_DTS = AudioFormat.ENCODING_DTS;
/** @see AudioFormat#ENCODING_DTS_HD */ /**
* @see AudioFormat#ENCODING_DTS_HD
*/
public static final int ENCODING_DTS_HD = AudioFormat.ENCODING_DTS_HD; public static final int ENCODING_DTS_HD = AudioFormat.ENCODING_DTS_HD;
/** @see AudioFormat#ENCODING_DOLBY_TRUEHD */ /**
* @see AudioFormat#ENCODING_DOLBY_TRUEHD
*/
public static final int ENCODING_DOLBY_TRUEHD = AudioFormat.ENCODING_DOLBY_TRUEHD; public static final int ENCODING_DOLBY_TRUEHD = AudioFormat.ENCODING_DOLBY_TRUEHD;
/** Represents the behavior affecting whether spatialization will be used. */ /** Represents the behavior affecting whether spatialization will be used. */
@ -286,12 +329,16 @@ public final class C {
@IntDef({SPATIALIZATION_BEHAVIOR_AUTO, SPATIALIZATION_BEHAVIOR_NEVER}) @IntDef({SPATIALIZATION_BEHAVIOR_AUTO, SPATIALIZATION_BEHAVIOR_NEVER})
public @interface SpatializationBehavior {} public @interface SpatializationBehavior {}
// TODO[b/190759307]: Update constant values and javadoc to use SDK once compile SDK target is set /**
// to 32. * @see AudioAttributes#SPATIALIZATION_BEHAVIOR_AUTO
/** See AudioAttributes#SPATIALIZATION_BEHAVIOR_AUTO */ */
public static final int SPATIALIZATION_BEHAVIOR_AUTO = 0; public static final int SPATIALIZATION_BEHAVIOR_AUTO =
/** See AudioAttributes#SPATIALIZATION_BEHAVIOR_NEVER */ AudioAttributes.SPATIALIZATION_BEHAVIOR_AUTO;
public static final int SPATIALIZATION_BEHAVIOR_NEVER = 1; /**
* @see AudioAttributes#SPATIALIZATION_BEHAVIOR_NEVER
*/
public static final int SPATIALIZATION_BEHAVIOR_NEVER =
AudioAttributes.SPATIALIZATION_BEHAVIOR_NEVER;
/** /**
* Stream types for an {@link android.media.AudioTrack}. One of {@link #STREAM_TYPE_ALARM}, {@link * Stream types for an {@link android.media.AudioTrack}. One of {@link #STREAM_TYPE_ALARM}, {@link
@ -316,27 +363,47 @@ public final class C {
STREAM_TYPE_DEFAULT STREAM_TYPE_DEFAULT
}) })
public @interface StreamType {} public @interface StreamType {}
/** @see AudioManager#STREAM_ALARM */ /**
* @see AudioManager#STREAM_ALARM
*/
public static final int STREAM_TYPE_ALARM = AudioManager.STREAM_ALARM; public static final int STREAM_TYPE_ALARM = AudioManager.STREAM_ALARM;
/** @see AudioManager#STREAM_DTMF */ /**
* @see AudioManager#STREAM_DTMF
*/
public static final int STREAM_TYPE_DTMF = AudioManager.STREAM_DTMF; public static final int STREAM_TYPE_DTMF = AudioManager.STREAM_DTMF;
/** @see AudioManager#STREAM_MUSIC */ /**
* @see AudioManager#STREAM_MUSIC
*/
public static final int STREAM_TYPE_MUSIC = AudioManager.STREAM_MUSIC; public static final int STREAM_TYPE_MUSIC = AudioManager.STREAM_MUSIC;
/** @see AudioManager#STREAM_NOTIFICATION */ /**
* @see AudioManager#STREAM_NOTIFICATION
*/
public static final int STREAM_TYPE_NOTIFICATION = AudioManager.STREAM_NOTIFICATION; public static final int STREAM_TYPE_NOTIFICATION = AudioManager.STREAM_NOTIFICATION;
/** @see AudioManager#STREAM_RING */ /**
* @see AudioManager#STREAM_RING
*/
public static final int STREAM_TYPE_RING = AudioManager.STREAM_RING; public static final int STREAM_TYPE_RING = AudioManager.STREAM_RING;
/** @see AudioManager#STREAM_SYSTEM */ /**
* @see AudioManager#STREAM_SYSTEM
*/
public static final int STREAM_TYPE_SYSTEM = AudioManager.STREAM_SYSTEM; public static final int STREAM_TYPE_SYSTEM = AudioManager.STREAM_SYSTEM;
/** @see AudioManager#STREAM_VOICE_CALL */ /**
* @see AudioManager#STREAM_VOICE_CALL
*/
public static final int STREAM_TYPE_VOICE_CALL = AudioManager.STREAM_VOICE_CALL; public static final int STREAM_TYPE_VOICE_CALL = AudioManager.STREAM_VOICE_CALL;
/** The default stream type used by audio renderers. Equal to {@link #STREAM_TYPE_MUSIC}. */ /** The default stream type used by audio renderers. Equal to {@link #STREAM_TYPE_MUSIC}. */
public static final int STREAM_TYPE_DEFAULT = STREAM_TYPE_MUSIC; public static final int STREAM_TYPE_DEFAULT = STREAM_TYPE_MUSIC;
/** /**
* Content types for audio attributes. One of {@link #CONTENT_TYPE_MOVIE}, {@link * Content types for audio attributes. One of:
* #CONTENT_TYPE_MUSIC}, {@link #CONTENT_TYPE_SONIFICATION}, {@link #CONTENT_TYPE_SPEECH} or *
* {@link #CONTENT_TYPE_UNKNOWN}. * <ul>
* <li>{@link #AUDIO_CONTENT_TYPE_MOVIE}
* <li>{@link #AUDIO_CONTENT_TYPE_MUSIC}
* <li>{@link #AUDIO_CONTENT_TYPE_SONIFICATION}
* <li>{@link #AUDIO_CONTENT_TYPE_SPEECH}
* <li>{@link #AUDIO_CONTENT_TYPE_UNKNOWN}
* </ul>
*/ */
// @Target list includes both 'default' targets and TYPE_USE, to ensure backwards compatibility // @Target list includes both 'default' targets and TYPE_USE, to ensure backwards compatibility
// with Kotlin usages from before TYPE_USE was added. // with Kotlin usages from before TYPE_USE was added.
@ -344,24 +411,44 @@ public final class C {
@Retention(RetentionPolicy.SOURCE) @Retention(RetentionPolicy.SOURCE)
@Target({FIELD, METHOD, PARAMETER, LOCAL_VARIABLE, TYPE_USE}) @Target({FIELD, METHOD, PARAMETER, LOCAL_VARIABLE, TYPE_USE})
@IntDef({ @IntDef({
CONTENT_TYPE_MOVIE, AUDIO_CONTENT_TYPE_MOVIE,
CONTENT_TYPE_MUSIC, AUDIO_CONTENT_TYPE_MUSIC,
CONTENT_TYPE_SONIFICATION, AUDIO_CONTENT_TYPE_SONIFICATION,
CONTENT_TYPE_SPEECH, AUDIO_CONTENT_TYPE_SPEECH,
CONTENT_TYPE_UNKNOWN AUDIO_CONTENT_TYPE_UNKNOWN
}) })
public @interface AudioContentType {} public @interface AudioContentType {}
/** @see android.media.AudioAttributes#CONTENT_TYPE_MOVIE */ /** See {@link AudioAttributes#CONTENT_TYPE_MOVIE}. */
public static final int CONTENT_TYPE_MOVIE = android.media.AudioAttributes.CONTENT_TYPE_MOVIE; public static final int AUDIO_CONTENT_TYPE_MOVIE = AudioAttributes.CONTENT_TYPE_MOVIE;
/** @see android.media.AudioAttributes#CONTENT_TYPE_MUSIC */ /**
public static final int CONTENT_TYPE_MUSIC = android.media.AudioAttributes.CONTENT_TYPE_MUSIC; * @deprecated Use {@link #AUDIO_CONTENT_TYPE_MOVIE} instead.
/** @see android.media.AudioAttributes#CONTENT_TYPE_SONIFICATION */ */
public static final int CONTENT_TYPE_SONIFICATION = @Deprecated public static final int CONTENT_TYPE_MOVIE = AUDIO_CONTENT_TYPE_MOVIE;
android.media.AudioAttributes.CONTENT_TYPE_SONIFICATION; /** See {@link AudioAttributes#CONTENT_TYPE_MUSIC}. */
/** @see android.media.AudioAttributes#CONTENT_TYPE_SPEECH */ public static final int AUDIO_CONTENT_TYPE_MUSIC = AudioAttributes.CONTENT_TYPE_MUSIC;
public static final int CONTENT_TYPE_SPEECH = android.media.AudioAttributes.CONTENT_TYPE_SPEECH; /**
/** @see android.media.AudioAttributes#CONTENT_TYPE_UNKNOWN */ * @deprecated Use {@link #AUDIO_CONTENT_TYPE_MUSIC} instead.
public static final int CONTENT_TYPE_UNKNOWN = android.media.AudioAttributes.CONTENT_TYPE_UNKNOWN; */
@Deprecated public static final int CONTENT_TYPE_MUSIC = AUDIO_CONTENT_TYPE_MUSIC;
/** See {@link AudioAttributes#CONTENT_TYPE_SONIFICATION}. */
public static final int AUDIO_CONTENT_TYPE_SONIFICATION =
AudioAttributes.CONTENT_TYPE_SONIFICATION;
/**
* @deprecated Use {@link #AUDIO_CONTENT_TYPE_SONIFICATION} instead.
*/
@Deprecated public static final int CONTENT_TYPE_SONIFICATION = AUDIO_CONTENT_TYPE_SONIFICATION;
/** See {@link AudioAttributes#CONTENT_TYPE_SPEECH}. */
public static final int AUDIO_CONTENT_TYPE_SPEECH = AudioAttributes.CONTENT_TYPE_SPEECH;
/**
* @deprecated Use {@link #AUDIO_CONTENT_TYPE_SPEECH} instead.
*/
@Deprecated public static final int CONTENT_TYPE_SPEECH = AUDIO_CONTENT_TYPE_SPEECH;
/** See {@link AudioAttributes#CONTENT_TYPE_UNKNOWN}. */
public static final int AUDIO_CONTENT_TYPE_UNKNOWN = AudioAttributes.CONTENT_TYPE_UNKNOWN;
/**
* @deprecated Use {@link #AUDIO_CONTENT_TYPE_UNKNOWN} instead.
*/
@Deprecated public static final int CONTENT_TYPE_UNKNOWN = AUDIO_CONTENT_TYPE_UNKNOWN;
/** /**
* Flags for audio attributes. Possible flag value is {@link #FLAG_AUDIBILITY_ENFORCED}. * Flags for audio attributes. Possible flag value is {@link #FLAG_AUDIBILITY_ENFORCED}.
@ -378,7 +465,9 @@ public final class C {
flag = true, flag = true,
value = {FLAG_AUDIBILITY_ENFORCED}) value = {FLAG_AUDIBILITY_ENFORCED})
public @interface AudioFlags {} public @interface AudioFlags {}
/** @see android.media.AudioAttributes#FLAG_AUDIBILITY_ENFORCED */ /**
* @see android.media.AudioAttributes#FLAG_AUDIBILITY_ENFORCED
*/
public static final int FLAG_AUDIBILITY_ENFORCED = public static final int FLAG_AUDIBILITY_ENFORCED =
android.media.AudioAttributes.FLAG_AUDIBILITY_ENFORCED; android.media.AudioAttributes.FLAG_AUDIBILITY_ENFORCED;
@ -416,46 +505,78 @@ public final class C {
USAGE_VOICE_COMMUNICATION_SIGNALLING USAGE_VOICE_COMMUNICATION_SIGNALLING
}) })
public @interface AudioUsage {} public @interface AudioUsage {}
/** @see android.media.AudioAttributes#USAGE_ALARM */ /**
* @see android.media.AudioAttributes#USAGE_ALARM
*/
public static final int USAGE_ALARM = android.media.AudioAttributes.USAGE_ALARM; public static final int USAGE_ALARM = android.media.AudioAttributes.USAGE_ALARM;
/** @see android.media.AudioAttributes#USAGE_ASSISTANCE_ACCESSIBILITY */ /**
* @see android.media.AudioAttributes#USAGE_ASSISTANCE_ACCESSIBILITY
*/
public static final int USAGE_ASSISTANCE_ACCESSIBILITY = public static final int USAGE_ASSISTANCE_ACCESSIBILITY =
android.media.AudioAttributes.USAGE_ASSISTANCE_ACCESSIBILITY; android.media.AudioAttributes.USAGE_ASSISTANCE_ACCESSIBILITY;
/** @see android.media.AudioAttributes#USAGE_ASSISTANCE_NAVIGATION_GUIDANCE */ /**
* @see android.media.AudioAttributes#USAGE_ASSISTANCE_NAVIGATION_GUIDANCE
*/
public static final int USAGE_ASSISTANCE_NAVIGATION_GUIDANCE = public static final int USAGE_ASSISTANCE_NAVIGATION_GUIDANCE =
android.media.AudioAttributes.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE; android.media.AudioAttributes.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE;
/** @see android.media.AudioAttributes#USAGE_ASSISTANCE_SONIFICATION */ /**
* @see android.media.AudioAttributes#USAGE_ASSISTANCE_SONIFICATION
*/
public static final int USAGE_ASSISTANCE_SONIFICATION = public static final int USAGE_ASSISTANCE_SONIFICATION =
android.media.AudioAttributes.USAGE_ASSISTANCE_SONIFICATION; android.media.AudioAttributes.USAGE_ASSISTANCE_SONIFICATION;
/** @see android.media.AudioAttributes#USAGE_ASSISTANT */ /**
* @see android.media.AudioAttributes#USAGE_ASSISTANT
*/
public static final int USAGE_ASSISTANT = android.media.AudioAttributes.USAGE_ASSISTANT; public static final int USAGE_ASSISTANT = android.media.AudioAttributes.USAGE_ASSISTANT;
/** @see android.media.AudioAttributes#USAGE_GAME */ /**
* @see android.media.AudioAttributes#USAGE_GAME
*/
public static final int USAGE_GAME = android.media.AudioAttributes.USAGE_GAME; public static final int USAGE_GAME = android.media.AudioAttributes.USAGE_GAME;
/** @see android.media.AudioAttributes#USAGE_MEDIA */ /**
* @see android.media.AudioAttributes#USAGE_MEDIA
*/
public static final int USAGE_MEDIA = android.media.AudioAttributes.USAGE_MEDIA; public static final int USAGE_MEDIA = android.media.AudioAttributes.USAGE_MEDIA;
/** @see android.media.AudioAttributes#USAGE_NOTIFICATION */ /**
* @see android.media.AudioAttributes#USAGE_NOTIFICATION
*/
public static final int USAGE_NOTIFICATION = android.media.AudioAttributes.USAGE_NOTIFICATION; public static final int USAGE_NOTIFICATION = android.media.AudioAttributes.USAGE_NOTIFICATION;
/** @see android.media.AudioAttributes#USAGE_NOTIFICATION_COMMUNICATION_DELAYED */ /**
* @see android.media.AudioAttributes#USAGE_NOTIFICATION_COMMUNICATION_DELAYED
*/
public static final int USAGE_NOTIFICATION_COMMUNICATION_DELAYED = public static final int USAGE_NOTIFICATION_COMMUNICATION_DELAYED =
android.media.AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_DELAYED; android.media.AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_DELAYED;
/** @see android.media.AudioAttributes#USAGE_NOTIFICATION_COMMUNICATION_INSTANT */ /**
* @see android.media.AudioAttributes#USAGE_NOTIFICATION_COMMUNICATION_INSTANT
*/
public static final int USAGE_NOTIFICATION_COMMUNICATION_INSTANT = public static final int USAGE_NOTIFICATION_COMMUNICATION_INSTANT =
android.media.AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_INSTANT; android.media.AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_INSTANT;
/** @see android.media.AudioAttributes#USAGE_NOTIFICATION_COMMUNICATION_REQUEST */ /**
* @see android.media.AudioAttributes#USAGE_NOTIFICATION_COMMUNICATION_REQUEST
*/
public static final int USAGE_NOTIFICATION_COMMUNICATION_REQUEST = public static final int USAGE_NOTIFICATION_COMMUNICATION_REQUEST =
android.media.AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_REQUEST; android.media.AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_REQUEST;
/** @see android.media.AudioAttributes#USAGE_NOTIFICATION_EVENT */ /**
* @see android.media.AudioAttributes#USAGE_NOTIFICATION_EVENT
*/
public static final int USAGE_NOTIFICATION_EVENT = public static final int USAGE_NOTIFICATION_EVENT =
android.media.AudioAttributes.USAGE_NOTIFICATION_EVENT; android.media.AudioAttributes.USAGE_NOTIFICATION_EVENT;
/** @see android.media.AudioAttributes#USAGE_NOTIFICATION_RINGTONE */ /**
* @see android.media.AudioAttributes#USAGE_NOTIFICATION_RINGTONE
*/
public static final int USAGE_NOTIFICATION_RINGTONE = public static final int USAGE_NOTIFICATION_RINGTONE =
android.media.AudioAttributes.USAGE_NOTIFICATION_RINGTONE; android.media.AudioAttributes.USAGE_NOTIFICATION_RINGTONE;
/** @see android.media.AudioAttributes#USAGE_UNKNOWN */ /**
* @see android.media.AudioAttributes#USAGE_UNKNOWN
*/
public static final int USAGE_UNKNOWN = android.media.AudioAttributes.USAGE_UNKNOWN; public static final int USAGE_UNKNOWN = android.media.AudioAttributes.USAGE_UNKNOWN;
/** @see android.media.AudioAttributes#USAGE_VOICE_COMMUNICATION */ /**
* @see android.media.AudioAttributes#USAGE_VOICE_COMMUNICATION
*/
public static final int USAGE_VOICE_COMMUNICATION = public static final int USAGE_VOICE_COMMUNICATION =
android.media.AudioAttributes.USAGE_VOICE_COMMUNICATION; android.media.AudioAttributes.USAGE_VOICE_COMMUNICATION;
/** @see android.media.AudioAttributes#USAGE_VOICE_COMMUNICATION_SIGNALLING */ /**
* @see android.media.AudioAttributes#USAGE_VOICE_COMMUNICATION_SIGNALLING
*/
public static final int USAGE_VOICE_COMMUNICATION_SIGNALLING = public static final int USAGE_VOICE_COMMUNICATION_SIGNALLING =
android.media.AudioAttributes.USAGE_VOICE_COMMUNICATION_SIGNALLING; android.media.AudioAttributes.USAGE_VOICE_COMMUNICATION_SIGNALLING;
@ -479,8 +600,9 @@ 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_LAST_SAMPLE}, * #BUFFER_FLAG_KEY_FRAME}, {@link #BUFFER_FLAG_END_OF_STREAM}, {@link #BUFFER_FLAG_FIRST_SAMPLE},
* {@link #BUFFER_FLAG_ENCRYPTED} and {@link #BUFFER_FLAG_DECODE_ONLY}. * {@link #BUFFER_FLAG_LAST_SAMPLE}, {@link #BUFFER_FLAG_ENCRYPTED} and {@link
* #BUFFER_FLAG_DECODE_ONLY}.
*/ */
@Documented @Documented
@Retention(RetentionPolicy.SOURCE) @Retention(RetentionPolicy.SOURCE)
@ -490,6 +612,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_FIRST_SAMPLE,
BUFFER_FLAG_HAS_SUPPLEMENTAL_DATA, BUFFER_FLAG_HAS_SUPPLEMENTAL_DATA,
BUFFER_FLAG_LAST_SAMPLE, BUFFER_FLAG_LAST_SAMPLE,
BUFFER_FLAG_ENCRYPTED, BUFFER_FLAG_ENCRYPTED,
@ -500,6 +623,8 @@ public final class C {
public static final int BUFFER_FLAG_KEY_FRAME = MediaCodec.BUFFER_FLAG_KEY_FRAME; public static final int BUFFER_FLAG_KEY_FRAME = MediaCodec.BUFFER_FLAG_KEY_FRAME;
/** 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 first media sample of the stream. */
public static final int BUFFER_FLAG_FIRST_SAMPLE = 1 << 27; // 0x08000000
/** Indicates that a buffer has supplemental data. */ /** Indicates that a buffer has supplemental data. */
public static final int BUFFER_FLAG_HAS_SUPPLEMENTAL_DATA = 1 << 28; // 0x10000000 public static final int BUFFER_FLAG_HAS_SUPPLEMENTAL_DATA = 1 << 28; // 0x10000000
/** Indicates that a buffer is known to contain the last media sample of the stream. */ /** Indicates that a buffer is known to contain the last media sample of the stream. */
@ -607,29 +732,59 @@ public final class C {
public static final String LANGUAGE_UNDETERMINED = "und"; public static final String LANGUAGE_UNDETERMINED = "und";
/** /**
* Represents a streaming or other media type. One of {@link #TYPE_DASH}, {@link #TYPE_SS}, {@link * Represents a streaming or other media type. One of:
* #TYPE_HLS}, {@link #TYPE_RTSP} or {@link #TYPE_OTHER}. *
* <ul>
* <li>{@link #CONTENT_TYPE_DASH}
* <li>{@link #CONTENT_TYPE_SS}
* <li>{@link #CONTENT_TYPE_HLS}
* <li>{@link #CONTENT_TYPE_RTSP}
* <li>{@link #CONTENT_TYPE_OTHER}
* </ul>
*/ */
// @Target list includes both 'default' targets and TYPE_USE, to ensure backwards compatibility // @Target list includes both 'default' targets and TYPE_USE, to ensure backwards compatibility
// with Kotlin usages from before TYPE_USE was added. // with Kotlin usages from before TYPE_USE was added.
@Documented @Documented
@Retention(RetentionPolicy.SOURCE) @Retention(RetentionPolicy.SOURCE)
@Target({FIELD, METHOD, PARAMETER, LOCAL_VARIABLE, TYPE_USE}) @Target({FIELD, METHOD, PARAMETER, LOCAL_VARIABLE, TYPE_USE})
@IntDef({TYPE_DASH, TYPE_SS, TYPE_HLS, TYPE_RTSP, TYPE_OTHER}) @IntDef({
CONTENT_TYPE_DASH,
CONTENT_TYPE_SS,
CONTENT_TYPE_HLS,
CONTENT_TYPE_RTSP,
CONTENT_TYPE_OTHER
})
public @interface ContentType {} public @interface ContentType {}
/** Value returned by {@link Util#inferContentType(String)} for DASH manifests. */ /** Value representing a DASH manifest. */
public static final int TYPE_DASH = 0; public static final int CONTENT_TYPE_DASH = 0;
/** Value returned by {@link Util#inferContentType(String)} for Smooth Streaming manifests. */
public static final int TYPE_SS = 1;
/** Value returned by {@link Util#inferContentType(String)} for HLS manifests. */
public static final int TYPE_HLS = 2;
/** Value returned by {@link Util#inferContentType(String)} for RTSP. */
public static final int TYPE_RTSP = 3;
/** /**
* Value returned by {@link Util#inferContentType(String)} for files other than DASH, HLS or * @deprecated Use {@link #CONTENT_TYPE_DASH} instead.
* Smooth Streaming manifests, or RTSP URIs.
*/ */
public static final int TYPE_OTHER = 4; @Deprecated public static final int TYPE_DASH = CONTENT_TYPE_DASH;
/** Value representing a Smooth Streaming manifest. */
public static final int CONTENT_TYPE_SS = 1;
/**
* @deprecated Use {@link #CONTENT_TYPE_SS} instead.
*/
@Deprecated public static final int TYPE_SS = CONTENT_TYPE_SS;
/** Value representing an HLS manifest. */
public static final int CONTENT_TYPE_HLS = 2;
/**
* @deprecated Use {@link #CONTENT_TYPE_HLS} instead.
*/
@Deprecated public static final int TYPE_HLS = CONTENT_TYPE_HLS;
/** Value representing an RTSP stream. */
public static final int CONTENT_TYPE_RTSP = 3;
/**
* @deprecated Use {@link #CONTENT_TYPE_RTSP} instead.
*/
@Deprecated public static final int TYPE_RTSP = CONTENT_TYPE_RTSP;
/** Value representing files other than DASH, HLS or Smooth Streaming manifests, or RTSP URIs. */
public static final int CONTENT_TYPE_OTHER = 4;
/**
* @deprecated Use {@link #CONTENT_TYPE_OTHER} instead.
*/
@Deprecated public static final int TYPE_OTHER = CONTENT_TYPE_OTHER;
/** A return value for methods where the end of an input was encountered. */ /** A return value for methods where the end of an input was encountered. */
public static final int RESULT_END_OF_INPUT = -1; public static final int RESULT_END_OF_INPUT = -1;
@ -876,11 +1031,17 @@ public final class C {
@Target(TYPE_USE) @Target(TYPE_USE)
@IntDef({Format.NO_VALUE, COLOR_SPACE_BT709, COLOR_SPACE_BT601, COLOR_SPACE_BT2020}) @IntDef({Format.NO_VALUE, COLOR_SPACE_BT709, COLOR_SPACE_BT601, COLOR_SPACE_BT2020})
public @interface ColorSpace {} public @interface ColorSpace {}
/** @see MediaFormat#COLOR_STANDARD_BT709 */ /**
* @see MediaFormat#COLOR_STANDARD_BT709
*/
public static final int COLOR_SPACE_BT709 = MediaFormat.COLOR_STANDARD_BT709; public static final int COLOR_SPACE_BT709 = MediaFormat.COLOR_STANDARD_BT709;
/** @see MediaFormat#COLOR_STANDARD_BT601_PAL */ /**
* @see MediaFormat#COLOR_STANDARD_BT601_PAL
*/
public static final int COLOR_SPACE_BT601 = MediaFormat.COLOR_STANDARD_BT601_PAL; public static final int COLOR_SPACE_BT601 = MediaFormat.COLOR_STANDARD_BT601_PAL;
/** @see MediaFormat#COLOR_STANDARD_BT2020 */ /**
* @see MediaFormat#COLOR_STANDARD_BT2020
*/
public static final int COLOR_SPACE_BT2020 = MediaFormat.COLOR_STANDARD_BT2020; public static final int COLOR_SPACE_BT2020 = MediaFormat.COLOR_STANDARD_BT2020;
/** /**
@ -892,11 +1053,17 @@ public final class C {
@Target(TYPE_USE) @Target(TYPE_USE)
@IntDef({Format.NO_VALUE, COLOR_TRANSFER_SDR, COLOR_TRANSFER_ST2084, COLOR_TRANSFER_HLG}) @IntDef({Format.NO_VALUE, COLOR_TRANSFER_SDR, COLOR_TRANSFER_ST2084, COLOR_TRANSFER_HLG})
public @interface ColorTransfer {} public @interface ColorTransfer {}
/** @see MediaFormat#COLOR_TRANSFER_SDR_VIDEO */ /**
* @see MediaFormat#COLOR_TRANSFER_SDR_VIDEO
*/
public static final int COLOR_TRANSFER_SDR = MediaFormat.COLOR_TRANSFER_SDR_VIDEO; public static final int COLOR_TRANSFER_SDR = MediaFormat.COLOR_TRANSFER_SDR_VIDEO;
/** @see MediaFormat#COLOR_TRANSFER_ST2084 */ /**
* @see MediaFormat#COLOR_TRANSFER_ST2084
*/
public static final int COLOR_TRANSFER_ST2084 = MediaFormat.COLOR_TRANSFER_ST2084; public static final int COLOR_TRANSFER_ST2084 = MediaFormat.COLOR_TRANSFER_ST2084;
/** @see MediaFormat#COLOR_TRANSFER_HLG */ /**
* @see MediaFormat#COLOR_TRANSFER_HLG
*/
public static final int COLOR_TRANSFER_HLG = MediaFormat.COLOR_TRANSFER_HLG; public static final int COLOR_TRANSFER_HLG = MediaFormat.COLOR_TRANSFER_HLG;
/** /**
@ -908,9 +1075,13 @@ public final class C {
@Target(TYPE_USE) @Target(TYPE_USE)
@IntDef({Format.NO_VALUE, COLOR_RANGE_LIMITED, COLOR_RANGE_FULL}) @IntDef({Format.NO_VALUE, COLOR_RANGE_LIMITED, COLOR_RANGE_FULL})
public @interface ColorRange {} public @interface ColorRange {}
/** @see MediaFormat#COLOR_RANGE_LIMITED */ /**
* @see MediaFormat#COLOR_RANGE_LIMITED
*/
public static final int COLOR_RANGE_LIMITED = MediaFormat.COLOR_RANGE_LIMITED; public static final int COLOR_RANGE_LIMITED = MediaFormat.COLOR_RANGE_LIMITED;
/** @see MediaFormat#COLOR_RANGE_FULL */ /**
* @see MediaFormat#COLOR_RANGE_FULL
*/
public static final int COLOR_RANGE_FULL = MediaFormat.COLOR_RANGE_FULL; public static final int COLOR_RANGE_FULL = MediaFormat.COLOR_RANGE_FULL;
/** Video projection types. */ /** Video projection types. */
@ -1173,7 +1344,9 @@ public final class C {
*/ */
public static final int FORMAT_UNSUPPORTED_TYPE = 0b000; public static final int FORMAT_UNSUPPORTED_TYPE = 0b000;
/** @deprecated Use {@link Util#usToMs(long)}. */ /**
* @deprecated Use {@link Util#usToMs(long)}.
*/
@InlineMe( @InlineMe(
replacement = "Util.usToMs(timeUs)", replacement = "Util.usToMs(timeUs)",
imports = {"com.google.android.exoplayer2.util.Util"}) imports = {"com.google.android.exoplayer2.util.Util"})
@ -1182,7 +1355,9 @@ public final class C {
return Util.usToMs(timeUs); return Util.usToMs(timeUs);
} }
/** @deprecated Use {@link Util#msToUs(long)}. */ /**
* @deprecated Use {@link Util#msToUs(long)}.
*/
@InlineMe( @InlineMe(
replacement = "Util.msToUs(timeMs)", replacement = "Util.msToUs(timeMs)",
imports = {"com.google.android.exoplayer2.util.Util"}) imports = {"com.google.android.exoplayer2.util.Util"})
@ -1191,7 +1366,9 @@ public final class C {
return Util.msToUs(timeMs); return Util.msToUs(timeMs);
} }
/** @deprecated Use {@link Util#generateAudioSessionIdV21(Context)}. */ /**
* @deprecated Use {@link Util#generateAudioSessionIdV21(Context)}.
*/
@InlineMe( @InlineMe(
replacement = "Util.generateAudioSessionIdV21(context)", replacement = "Util.generateAudioSessionIdV21(context)",
imports = {"com.google.android.exoplayer2.util.Util"}) imports = {"com.google.android.exoplayer2.util.Util"})
@ -1201,7 +1378,9 @@ public final class C {
return Util.generateAudioSessionIdV21(context); return Util.generateAudioSessionIdV21(context);
} }
/** @deprecated Use {@link Util#getFormatSupportString(int)}. */ /**
* @deprecated Use {@link Util#getFormatSupportString(int)}.
*/
@InlineMe( @InlineMe(
replacement = "Util.getFormatSupportString(formatSupport)", replacement = "Util.getFormatSupportString(formatSupport)",
imports = {"com.google.android.exoplayer2.util.Util"}) imports = {"com.google.android.exoplayer2.util.Util"})
@ -1210,7 +1389,9 @@ public final class C {
return Util.getFormatSupportString(formatSupport); return Util.getFormatSupportString(formatSupport);
} }
/** @deprecated Use {@link Util#getErrorCodeForMediaDrmErrorCode(int)}. */ /**
* @deprecated Use {@link Util#getErrorCodeForMediaDrmErrorCode(int)}.
*/
@InlineMe( @InlineMe(
replacement = "Util.getErrorCodeForMediaDrmErrorCode(mediaDrmErrorCode)", replacement = "Util.getErrorCodeForMediaDrmErrorCode(mediaDrmErrorCode)",
imports = {"com.google.android.exoplayer2.util.Util"}) imports = {"com.google.android.exoplayer2.util.Util"})

View file

@ -771,7 +771,9 @@ public final class Format implements Bundleable {
// Video. // Video.
/** @deprecated Use {@link Format.Builder}. */ /**
* @deprecated Use {@link Format.Builder}.
*/
@Deprecated @Deprecated
public static Format createVideoSampleFormat( public static Format createVideoSampleFormat(
@Nullable String id, @Nullable String id,
@ -799,7 +801,9 @@ public final class Format implements Bundleable {
.build(); .build();
} }
/** @deprecated Use {@link Format.Builder}. */ /**
* @deprecated Use {@link Format.Builder}.
*/
@Deprecated @Deprecated
public static Format createVideoSampleFormat( public static Format createVideoSampleFormat(
@Nullable String id, @Nullable String id,
@ -833,7 +837,9 @@ public final class Format implements Bundleable {
// Audio. // Audio.
/** @deprecated Use {@link Format.Builder}. */ /**
* @deprecated Use {@link Format.Builder}.
*/
@Deprecated @Deprecated
public static Format createAudioSampleFormat( public static Format createAudioSampleFormat(
@Nullable String id, @Nullable String id,
@ -863,7 +869,9 @@ public final class Format implements Bundleable {
.build(); .build();
} }
/** @deprecated Use {@link Format.Builder}. */ /**
* @deprecated Use {@link Format.Builder}.
*/
@Deprecated @Deprecated
public static Format createAudioSampleFormat( public static Format createAudioSampleFormat(
@Nullable String id, @Nullable String id,
@ -897,7 +905,9 @@ public final class Format implements Bundleable {
// Generic. // Generic.
/** @deprecated Use {@link Format.Builder}. */ /**
* @deprecated Use {@link Format.Builder}.
*/
@Deprecated @Deprecated
public static Format createContainerFormat( public static Format createContainerFormat(
@Nullable String id, @Nullable String id,
@ -923,7 +933,9 @@ public final class Format implements Bundleable {
.build(); .build();
} }
/** @deprecated Use {@link Format.Builder}. */ /**
* @deprecated Use {@link Format.Builder}.
*/
@Deprecated @Deprecated
public static Format createSampleFormat(@Nullable String id, @Nullable String sampleMimeType) { public static Format createSampleFormat(@Nullable String id, @Nullable String sampleMimeType) {
return new Builder().setId(id).setSampleMimeType(sampleMimeType).build(); return new Builder().setId(id).setSampleMimeType(sampleMimeType).build();
@ -981,25 +993,33 @@ public final class Format implements Bundleable {
return new Builder(this); return new Builder(this);
} }
/** @deprecated Use {@link #buildUpon()} and {@link Builder#setMaxInputSize(int)}. */ /**
* @deprecated Use {@link #buildUpon()} and {@link Builder#setMaxInputSize(int)}.
*/
@Deprecated @Deprecated
public Format copyWithMaxInputSize(int maxInputSize) { public Format copyWithMaxInputSize(int maxInputSize) {
return buildUpon().setMaxInputSize(maxInputSize).build(); return buildUpon().setMaxInputSize(maxInputSize).build();
} }
/** @deprecated Use {@link #buildUpon()} and {@link Builder#setSubsampleOffsetUs(long)}. */ /**
* @deprecated Use {@link #buildUpon()} and {@link Builder#setSubsampleOffsetUs(long)}.
*/
@Deprecated @Deprecated
public Format copyWithSubsampleOffsetUs(long subsampleOffsetUs) { public Format copyWithSubsampleOffsetUs(long subsampleOffsetUs) {
return buildUpon().setSubsampleOffsetUs(subsampleOffsetUs).build(); return buildUpon().setSubsampleOffsetUs(subsampleOffsetUs).build();
} }
/** @deprecated Use {@link #buildUpon()} and {@link Builder#setLabel(String)} . */ /**
* @deprecated Use {@link #buildUpon()} and {@link Builder#setLabel(String)} .
*/
@Deprecated @Deprecated
public Format copyWithLabel(@Nullable String label) { public Format copyWithLabel(@Nullable String label) {
return buildUpon().setLabel(label).build(); return buildUpon().setLabel(label).build();
} }
/** @deprecated Use {@link #withManifestFormatInfo(Format)}. */ /**
* @deprecated Use {@link #withManifestFormatInfo(Format)}.
*/
@Deprecated @Deprecated
public Format copyWithManifestFormatInfo(Format manifestFormat) { public Format copyWithManifestFormatInfo(Format manifestFormat) {
return withManifestFormatInfo(manifestFormat); return withManifestFormatInfo(manifestFormat);
@ -1081,19 +1101,25 @@ public final class Format implements Bundleable {
return buildUpon().setEncoderDelay(encoderDelay).setEncoderPadding(encoderPadding).build(); return buildUpon().setEncoderDelay(encoderDelay).setEncoderPadding(encoderPadding).build();
} }
/** @deprecated Use {@link #buildUpon()} and {@link Builder#setFrameRate(float)}. */ /**
* @deprecated Use {@link #buildUpon()} and {@link Builder#setFrameRate(float)}.
*/
@Deprecated @Deprecated
public Format copyWithFrameRate(float frameRate) { public Format copyWithFrameRate(float frameRate) {
return buildUpon().setFrameRate(frameRate).build(); return buildUpon().setFrameRate(frameRate).build();
} }
/** @deprecated Use {@link #buildUpon()} and {@link Builder#setDrmInitData(DrmInitData)}. */ /**
* @deprecated Use {@link #buildUpon()} and {@link Builder#setDrmInitData(DrmInitData)}.
*/
@Deprecated @Deprecated
public Format copyWithDrmInitData(@Nullable DrmInitData drmInitData) { public Format copyWithDrmInitData(@Nullable DrmInitData drmInitData) {
return buildUpon().setDrmInitData(drmInitData).build(); return buildUpon().setDrmInitData(drmInitData).build();
} }
/** @deprecated Use {@link #buildUpon()} and {@link Builder#setMetadata(Metadata)}. */ /**
* @deprecated Use {@link #buildUpon()} and {@link Builder#setMetadata(Metadata)}.
*/
@Deprecated @Deprecated
public Format copyWithMetadata(@Nullable Metadata metadata) { public Format copyWithMetadata(@Nullable Metadata metadata) {
return buildUpon().setMetadata(metadata).build(); return buildUpon().setMetadata(metadata).build();
@ -1501,7 +1527,9 @@ public final class Format implements Bundleable {
bundle.putFloat(keyForField(FIELD_PIXEL_WIDTH_HEIGHT_RATIO), pixelWidthHeightRatio); bundle.putFloat(keyForField(FIELD_PIXEL_WIDTH_HEIGHT_RATIO), pixelWidthHeightRatio);
bundle.putByteArray(keyForField(FIELD_PROJECTION_DATA), projectionData); bundle.putByteArray(keyForField(FIELD_PROJECTION_DATA), projectionData);
bundle.putInt(keyForField(FIELD_STEREO_MODE), stereoMode); bundle.putInt(keyForField(FIELD_STEREO_MODE), stereoMode);
bundle.putBundle(keyForField(FIELD_COLOR_INFO), BundleableUtil.toNullableBundle(colorInfo)); if (colorInfo != null) {
bundle.putBundle(keyForField(FIELD_COLOR_INFO), colorInfo.toBundle());
}
// Audio specific. // Audio specific.
bundle.putInt(keyForField(FIELD_CHANNEL_COUNT), channelCount); bundle.putInt(keyForField(FIELD_CHANNEL_COUNT), channelCount);
bundle.putInt(keyForField(FIELD_SAMPLE_RATE), sampleRate); bundle.putInt(keyForField(FIELD_SAMPLE_RATE), sampleRate);
@ -1568,11 +1596,13 @@ public final class Format implements Bundleable {
bundle.getFloat( bundle.getFloat(
keyForField(FIELD_PIXEL_WIDTH_HEIGHT_RATIO), DEFAULT.pixelWidthHeightRatio)) keyForField(FIELD_PIXEL_WIDTH_HEIGHT_RATIO), DEFAULT.pixelWidthHeightRatio))
.setProjectionData(bundle.getByteArray(keyForField(FIELD_PROJECTION_DATA))) .setProjectionData(bundle.getByteArray(keyForField(FIELD_PROJECTION_DATA)))
.setStereoMode(bundle.getInt(keyForField(FIELD_STEREO_MODE), DEFAULT.stereoMode)) .setStereoMode(bundle.getInt(keyForField(FIELD_STEREO_MODE), DEFAULT.stereoMode));
.setColorInfo( Bundle colorInfoBundle = bundle.getBundle(keyForField(FIELD_COLOR_INFO));
BundleableUtil.fromNullableBundle( if (colorInfoBundle != null) {
ColorInfo.CREATOR, bundle.getBundle(keyForField(FIELD_COLOR_INFO)))) builder.setColorInfo(ColorInfo.CREATOR.fromBundle(colorInfoBundle));
// Audio specific. }
// Audio specific.
builder
.setChannelCount(bundle.getInt(keyForField(FIELD_CHANNEL_COUNT), DEFAULT.channelCount)) .setChannelCount(bundle.getInt(keyForField(FIELD_CHANNEL_COUNT), DEFAULT.channelCount))
.setSampleRate(bundle.getInt(keyForField(FIELD_SAMPLE_RATE), DEFAULT.sampleRate)) .setSampleRate(bundle.getInt(keyForField(FIELD_SAMPLE_RATE), DEFAULT.sampleRate))
.setPcmEncoding(bundle.getInt(keyForField(FIELD_PCM_ENCODING), DEFAULT.pcmEncoding)) .setPcmEncoding(bundle.getInt(keyForField(FIELD_PCM_ENCODING), DEFAULT.pcmEncoding))

View file

@ -23,9 +23,8 @@ import android.view.TextureView;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import com.google.android.exoplayer2.audio.AudioAttributes; import com.google.android.exoplayer2.audio.AudioAttributes;
import com.google.android.exoplayer2.metadata.Metadata; import com.google.android.exoplayer2.metadata.Metadata;
import com.google.android.exoplayer2.source.TrackGroupArray;
import com.google.android.exoplayer2.text.Cue; import com.google.android.exoplayer2.text.Cue;
import com.google.android.exoplayer2.trackselection.TrackSelectionArray; import com.google.android.exoplayer2.text.CueGroup;
import com.google.android.exoplayer2.trackselection.TrackSelectionParameters; import com.google.android.exoplayer2.trackselection.TrackSelectionParameters;
import com.google.android.exoplayer2.video.VideoSize; import com.google.android.exoplayer2.video.VideoSize;
import java.util.List; import java.util.List;
@ -302,7 +301,11 @@ public class ForwardingPlayer implements Player {
player.seekForward(); player.seekForward();
} }
/** Calls {@link Player#hasPrevious()} on the delegate and returns the result. */ /**
* Calls {@link Player#hasPrevious()} on the delegate and returns the result.
*
* @deprecated Use {@link #hasPreviousMediaItem()} instead.
*/
@SuppressWarnings("deprecation") // Forwarding to deprecated method @SuppressWarnings("deprecation") // Forwarding to deprecated method
@Deprecated @Deprecated
@Override @Override
@ -310,7 +313,11 @@ public class ForwardingPlayer implements Player {
return player.hasPrevious(); return player.hasPrevious();
} }
/** Calls {@link Player#hasPreviousWindow()} on the delegate and returns the result. */ /**
* Calls {@link Player#hasPreviousWindow()} on the delegate and returns the result.
*
* @deprecated Use {@link #hasPreviousMediaItem()} instead.
*/
@SuppressWarnings("deprecation") // Forwarding to deprecated method @SuppressWarnings("deprecation") // Forwarding to deprecated method
@Deprecated @Deprecated
@Override @Override
@ -324,7 +331,11 @@ public class ForwardingPlayer implements Player {
return player.hasPreviousMediaItem(); return player.hasPreviousMediaItem();
} }
/** Calls {@link Player#previous()} on the delegate. */ /**
* Calls {@link Player#previous()} on the delegate.
*
* @deprecated Use {@link #seekToPreviousMediaItem()} instead.
*/
@SuppressWarnings("deprecation") // Forwarding to deprecated method @SuppressWarnings("deprecation") // Forwarding to deprecated method
@Deprecated @Deprecated
@Override @Override
@ -332,7 +343,11 @@ public class ForwardingPlayer implements Player {
player.previous(); player.previous();
} }
/** Calls {@link Player#seekToPreviousWindow()} on the delegate. */ /**
* Calls {@link Player#seekToPreviousWindow()} on the delegate.
*
* @deprecated Use {@link #seekToPreviousMediaItem()} instead.
*/
@SuppressWarnings("deprecation") // Forwarding to deprecated method @SuppressWarnings("deprecation") // Forwarding to deprecated method
@Deprecated @Deprecated
@Override @Override
@ -358,7 +373,11 @@ public class ForwardingPlayer implements Player {
return player.getMaxSeekToPreviousPosition(); return player.getMaxSeekToPreviousPosition();
} }
/** Calls {@link Player#hasNext()} on the delegate and returns the result. */ /**
* Calls {@link Player#hasNext()} on the delegate and returns the result.
*
* @deprecated Use {@link #hasNextMediaItem()} instead.
*/
@SuppressWarnings("deprecation") // Forwarding to deprecated method @SuppressWarnings("deprecation") // Forwarding to deprecated method
@Deprecated @Deprecated
@Override @Override
@ -366,7 +385,11 @@ public class ForwardingPlayer implements Player {
return player.hasNext(); return player.hasNext();
} }
/** Calls {@link Player#hasNextWindow()} on the delegate and returns the result. */ /**
* Calls {@link Player#hasNextWindow()} on the delegate and returns the result.
*
* @deprecated Use {@link #hasNextMediaItem()} instead.
*/
@SuppressWarnings("deprecation") // Forwarding to deprecated method @SuppressWarnings("deprecation") // Forwarding to deprecated method
@Deprecated @Deprecated
@Override @Override
@ -380,7 +403,11 @@ public class ForwardingPlayer implements Player {
return player.hasNextMediaItem(); return player.hasNextMediaItem();
} }
/** Calls {@link Player#next()} on the delegate. */ /**
* Calls {@link Player#next()} on the delegate.
*
* @deprecated Use {@link #seekToNextMediaItem()} instead.
*/
@SuppressWarnings("deprecation") // Forwarding to deprecated method @SuppressWarnings("deprecation") // Forwarding to deprecated method
@Deprecated @Deprecated
@Override @Override
@ -388,7 +415,11 @@ public class ForwardingPlayer implements Player {
player.next(); player.next();
} }
/** Calls {@link Player#seekToNextWindow()} on the delegate. */ /**
* Calls {@link Player#seekToNextWindow()} on the delegate.
*
* @deprecated Use {@link #seekToNextMediaItem()} instead.
*/
@SuppressWarnings("deprecation") // Forwarding to deprecated method @SuppressWarnings("deprecation") // Forwarding to deprecated method
@Deprecated @Deprecated
@Override @Override
@ -432,7 +463,13 @@ public class ForwardingPlayer implements Player {
player.stop(); player.stop();
} }
/** Calls {@link Player#stop(boolean)} on the delegate. */ /**
* Calls {@link Player#stop(boolean)} on the delegate.
*
* @deprecated Use {@link #stop()} and {@link #clearMediaItems()} (if {@code reset} is true) or
* just {@link #stop()} (if {@code reset} is false). Any player error will be cleared when
* {@link #prepare() re-preparing} the player.
*/
@SuppressWarnings("deprecation") // Forwarding to deprecated method @SuppressWarnings("deprecation") // Forwarding to deprecated method
@Deprecated @Deprecated
@Override @Override
@ -446,26 +483,10 @@ public class ForwardingPlayer implements Player {
player.release(); player.release();
} }
/** Calls {@link Player#getCurrentTrackGroups()} on the delegate and returns the result. */ /** Calls {@link Player#getCurrentTracks()} on the delegate and returns the result. */
@SuppressWarnings("deprecation") // Forwarding to deprecated method
@Deprecated
@Override @Override
public TrackGroupArray getCurrentTrackGroups() { public Tracks getCurrentTracks() {
return player.getCurrentTrackGroups(); return player.getCurrentTracks();
}
/** Calls {@link Player#getCurrentTrackSelections()} on the delegate and returns the result. */
@SuppressWarnings("deprecation") // Forwarding to deprecated method
@Deprecated
@Override
public TrackSelectionArray getCurrentTrackSelections() {
return player.getCurrentTrackSelections();
}
/** Calls {@link Player#getCurrentTracksInfo()} on the delegate and returns the result. */
@Override
public TracksInfo getCurrentTracksInfo() {
return player.getCurrentTracksInfo();
} }
/** Calls {@link Player#getTrackSelectionParameters()} on the delegate and returns the result. */ /** Calls {@link Player#getTrackSelectionParameters()} on the delegate and returns the result. */
@ -517,7 +538,11 @@ public class ForwardingPlayer implements Player {
return player.getCurrentPeriodIndex(); return player.getCurrentPeriodIndex();
} }
/** Calls {@link Player#getCurrentWindowIndex()} on the delegate and returns the result. */ /**
* Calls {@link Player#getCurrentWindowIndex()} on the delegate and returns the result.
*
* @deprecated Use {@link #getCurrentMediaItemIndex()} instead.
*/
@SuppressWarnings("deprecation") // Forwarding to deprecated method @SuppressWarnings("deprecation") // Forwarding to deprecated method
@Deprecated @Deprecated
@Override @Override
@ -531,7 +556,11 @@ public class ForwardingPlayer implements Player {
return player.getCurrentMediaItemIndex(); return player.getCurrentMediaItemIndex();
} }
/** Calls {@link Player#getNextWindowIndex()} on the delegate and returns the result. */ /**
* Calls {@link Player#getNextWindowIndex()} on the delegate and returns the result.
*
* @deprecated Use {@link #getNextMediaItemIndex()} instead.
*/
@SuppressWarnings("deprecation") // Forwarding to deprecated method @SuppressWarnings("deprecation") // Forwarding to deprecated method
@Deprecated @Deprecated
@Override @Override
@ -545,7 +574,11 @@ public class ForwardingPlayer implements Player {
return player.getNextMediaItemIndex(); return player.getNextMediaItemIndex();
} }
/** Calls {@link Player#getPreviousWindowIndex()} on the delegate and returns the result. */ /**
* Calls {@link Player#getPreviousWindowIndex()} on the delegate and returns the result.
*
* @deprecated Use {@link #getPreviousMediaItemIndex()} instead.
*/
@SuppressWarnings("deprecation") // Forwarding to deprecated method @SuppressWarnings("deprecation") // Forwarding to deprecated method
@Deprecated @Deprecated
@Override @Override
@ -608,7 +641,11 @@ public class ForwardingPlayer implements Player {
return player.getTotalBufferedDuration(); return player.getTotalBufferedDuration();
} }
/** Calls {@link Player#isCurrentWindowDynamic()} on the delegate and returns the result. */ /**
* Calls {@link Player#isCurrentWindowDynamic()} on the delegate and returns the result.
*
* @deprecated Use {@link #isCurrentMediaItemDynamic()} instead.
*/
@SuppressWarnings("deprecation") // Forwarding to deprecated method @SuppressWarnings("deprecation") // Forwarding to deprecated method
@Deprecated @Deprecated
@Override @Override
@ -622,7 +659,11 @@ public class ForwardingPlayer implements Player {
return player.isCurrentMediaItemDynamic(); return player.isCurrentMediaItemDynamic();
} }
/** Calls {@link Player#isCurrentWindowLive()} on the delegate and returns the result. */ /**
* Calls {@link Player#isCurrentWindowLive()} on the delegate and returns the result.
*
* @deprecated Use {@link #isCurrentMediaItemLive()} instead.
*/
@SuppressWarnings("deprecation") // Forwarding to deprecated method @SuppressWarnings("deprecation") // Forwarding to deprecated method
@Deprecated @Deprecated
@Override @Override
@ -642,7 +683,11 @@ public class ForwardingPlayer implements Player {
return player.getCurrentLiveOffset(); return player.getCurrentLiveOffset();
} }
/** Calls {@link Player#isCurrentWindowSeekable()} on the delegate and returns the result. */ /**
* Calls {@link Player#isCurrentWindowSeekable()} on the delegate and returns the result.
*
* @deprecated Use {@link #isCurrentMediaItemSeekable()} instead.
*/
@SuppressWarnings("deprecation") // Forwarding to deprecated method @SuppressWarnings("deprecation") // Forwarding to deprecated method
@Deprecated @Deprecated
@Override @Override
@ -772,7 +817,7 @@ public class ForwardingPlayer implements Player {
/** Calls {@link Player#getCurrentCues()} on the delegate and returns the result. */ /** Calls {@link Player#getCurrentCues()} on the delegate and returns the result. */
@Override @Override
public List<Cue> getCurrentCues() { public CueGroup getCurrentCues() {
return player.getCurrentCues(); return player.getCurrentCues();
} }
@ -851,14 +896,8 @@ public class ForwardingPlayer implements Player {
} }
@Override @Override
@SuppressWarnings("deprecation") public void onTracksChanged(Tracks tracks) {
public void onTracksChanged(TrackGroupArray trackGroups, TrackSelectionArray trackSelections) { listener.onTracksChanged(tracks);
listener.onTracksChanged(trackGroups, trackSelections);
}
@Override
public void onTracksInfoChanged(TracksInfo tracksInfo) {
listener.onTracksInfoChanged(tracksInfo);
} }
@Override @Override
@ -1018,6 +1057,11 @@ public class ForwardingPlayer implements Player {
listener.onCues(cues); listener.onCues(cues);
} }
@Override
public void onCues(CueGroup cueGroup) {
listener.onCues(cueGroup);
}
@Override @Override
public void onMetadata(Metadata metadata) { public void onMetadata(Metadata metadata) {
listener.onMetadata(metadata); listener.onMetadata(metadata);

View file

@ -29,6 +29,7 @@ import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.util.Util;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap;
import com.google.errorprone.annotations.InlineMe;
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;
@ -84,6 +85,7 @@ public final class MediaItem implements Bundleable {
// TODO: Change this to LiveConfiguration once all the deprecated individual setters // TODO: Change this to LiveConfiguration once all the deprecated individual setters
// are removed. // are removed.
private LiveConfiguration.Builder liveConfiguration; private LiveConfiguration.Builder liveConfiguration;
private RequestMetadata requestMetadata;
/** Creates a builder. */ /** Creates a builder. */
@SuppressWarnings("deprecation") // Temporarily uses DrmConfiguration.Builder() constructor. @SuppressWarnings("deprecation") // Temporarily uses DrmConfiguration.Builder() constructor.
@ -93,6 +95,7 @@ public final class MediaItem implements Bundleable {
streamKeys = Collections.emptyList(); streamKeys = Collections.emptyList();
subtitleConfigurations = ImmutableList.of(); subtitleConfigurations = ImmutableList.of();
liveConfiguration = new LiveConfiguration.Builder(); liveConfiguration = new LiveConfiguration.Builder();
requestMetadata = RequestMetadata.EMPTY;
} }
private Builder(MediaItem mediaItem) { private Builder(MediaItem mediaItem) {
@ -101,6 +104,7 @@ public final class MediaItem implements Bundleable {
mediaId = mediaItem.mediaId; mediaId = mediaItem.mediaId;
mediaMetadata = mediaItem.mediaMetadata; mediaMetadata = mediaItem.mediaMetadata;
liveConfiguration = mediaItem.liveConfiguration.buildUpon(); liveConfiguration = mediaItem.liveConfiguration.buildUpon();
requestMetadata = mediaItem.requestMetadata;
@Nullable LocalConfiguration localConfiguration = mediaItem.localConfiguration; @Nullable LocalConfiguration localConfiguration = mediaItem.localConfiguration;
if (localConfiguration != null) { if (localConfiguration != null) {
customCacheKey = localConfiguration.customCacheKey; customCacheKey = localConfiguration.customCacheKey;
@ -303,11 +307,11 @@ public final class MediaItem implements Bundleable {
/** /**
* @deprecated Use {@link #setDrmConfiguration(DrmConfiguration)} and {@link * @deprecated Use {@link #setDrmConfiguration(DrmConfiguration)} and {@link
* DrmConfiguration.Builder#forceSessionsForAudioAndVideoTracks(boolean)} instead. * DrmConfiguration.Builder#setForceSessionsForAudioAndVideoTracks(boolean)} instead.
*/ */
@Deprecated @Deprecated
public Builder setDrmSessionForClearPeriods(boolean sessionForClearPeriods) { public Builder setDrmSessionForClearPeriods(boolean sessionForClearPeriods) {
drmConfiguration.forceSessionsForAudioAndVideoTracks(sessionForClearPeriods); drmConfiguration.setForceSessionsForAudioAndVideoTracks(sessionForClearPeriods);
return this; return this;
} }
@ -499,6 +503,12 @@ public final class MediaItem implements Bundleable {
return this; return this;
} }
/** Sets the request metadata. */
public Builder setRequestMetadata(RequestMetadata requestMetadata) {
this.requestMetadata = requestMetadata;
return this;
}
/** Returns a new {@link MediaItem} instance with the current builder values. */ /** Returns a new {@link MediaItem} instance with the current builder values. */
@SuppressWarnings("deprecation") // Using PlaybackProperties while it exists. @SuppressWarnings("deprecation") // Using PlaybackProperties while it exists.
public MediaItem build() { public MediaItem build() {
@ -523,7 +533,8 @@ public final class MediaItem implements Bundleable {
clippingConfiguration.buildClippingProperties(), clippingConfiguration.buildClippingProperties(),
localConfiguration, localConfiguration,
liveConfiguration.build(), liveConfiguration.build(),
mediaMetadata != null ? mediaMetadata : MediaMetadata.EMPTY); mediaMetadata != null ? mediaMetadata : MediaMetadata.EMPTY,
requestMetadata);
} }
} }
@ -633,6 +644,18 @@ public final class MediaItem implements Bundleable {
return this; return this;
} }
/**
* @deprecated Use {@link #setForceSessionsForAudioAndVideoTracks(boolean)} instead.
*/
@Deprecated
@InlineMe(
replacement =
"this.setForceSessionsForAudioAndVideoTracks(forceSessionsForAudioAndVideoTracks)")
public Builder forceSessionsForAudioAndVideoTracks(
boolean forceSessionsForAudioAndVideoTracks) {
return setForceSessionsForAudioAndVideoTracks(forceSessionsForAudioAndVideoTracks);
}
/** /**
* Sets whether a DRM session should be used for clear tracks of type {@link * Sets whether a DRM session should be used for clear tracks of type {@link
* C#TRACK_TYPE_VIDEO} and {@link C#TRACK_TYPE_AUDIO}. * C#TRACK_TYPE_VIDEO} and {@link C#TRACK_TYPE_AUDIO}.
@ -640,10 +663,10 @@ public final class MediaItem implements Bundleable {
* <p>This method overrides what has been set by previously calling {@link * <p>This method overrides what has been set by previously calling {@link
* #setForcedSessionTrackTypes(List)}. * #setForcedSessionTrackTypes(List)}.
*/ */
public Builder forceSessionsForAudioAndVideoTracks( public Builder setForceSessionsForAudioAndVideoTracks(
boolean useClearSessionsForAudioAndVideoTracks) { boolean forceSessionsForAudioAndVideoTracks) {
this.setForcedSessionTrackTypes( this.setForcedSessionTrackTypes(
useClearSessionsForAudioAndVideoTracks forceSessionsForAudioAndVideoTracks
? ImmutableList.of(C.TRACK_TYPE_VIDEO, C.TRACK_TYPE_AUDIO) ? ImmutableList.of(C.TRACK_TYPE_VIDEO, C.TRACK_TYPE_AUDIO)
: ImmutableList.of()); : ImmutableList.of());
return this; return this;
@ -654,10 +677,10 @@ public final class MediaItem implements Bundleable {
* when the tracks are in the clear. * when the tracks are in the clear.
* *
* <p>For the common case of using a DRM session for {@link C#TRACK_TYPE_VIDEO} and {@link * <p>For the common case of using a DRM session for {@link C#TRACK_TYPE_VIDEO} and {@link
* C#TRACK_TYPE_AUDIO}, {@link #forceSessionsForAudioAndVideoTracks(boolean)} can be used. * C#TRACK_TYPE_AUDIO}, {@link #setForceSessionsForAudioAndVideoTracks(boolean)} can be used.
* *
* <p>This method overrides what has been set by previously calling {@link * <p>This method overrides what has been set by previously calling {@link
* #forceSessionsForAudioAndVideoTracks(boolean)}. * #setForceSessionsForAudioAndVideoTracks(boolean)}.
*/ */
public Builder setForcedSessionTrackTypes( public Builder setForcedSessionTrackTypes(
List<@C.TrackType Integer> forcedSessionTrackTypes) { List<@C.TrackType Integer> forcedSessionTrackTypes) {
@ -686,7 +709,9 @@ public final class MediaItem implements Bundleable {
/** The UUID of the protection scheme. */ /** The UUID of the protection scheme. */
public final UUID scheme; public final UUID scheme;
/** @deprecated Use {@link #scheme} instead. */ /**
* @deprecated Use {@link #scheme} instead.
*/
@Deprecated public final UUID uuid; @Deprecated public final UUID uuid;
/** /**
@ -695,7 +720,9 @@ public final class MediaItem implements Bundleable {
*/ */
@Nullable public final Uri licenseUri; @Nullable public final Uri licenseUri;
/** @deprecated Use {@link #licenseRequestHeaders} instead. */ /**
* @deprecated Use {@link #licenseRequestHeaders} instead.
*/
@Deprecated public final ImmutableMap<String, String> requestHeaders; @Deprecated public final ImmutableMap<String, String> requestHeaders;
/** The headers to attach to requests sent to the DRM license server. */ /** The headers to attach to requests sent to the DRM license server. */
@ -716,7 +743,9 @@ public final class MediaItem implements Bundleable {
*/ */
public final boolean forceDefaultLicenseUri; public final boolean forceDefaultLicenseUri;
/** @deprecated Use {@link #forcedSessionTrackTypes}. */ /**
* @deprecated Use {@link #forcedSessionTrackTypes}.
*/
@Deprecated public final ImmutableList<@C.TrackType Integer> sessionForClearTypes; @Deprecated public final ImmutableList<@C.TrackType Integer> sessionForClearTypes;
/** /**
* The types of tracks for which to always use a DRM session even if the content is unencrypted. * The types of tracks for which to always use a DRM session even if the content is unencrypted.
@ -903,7 +932,9 @@ public final class MediaItem implements Bundleable {
/** Optional subtitles to be sideloaded. */ /** Optional subtitles to be sideloaded. */
public final ImmutableList<SubtitleConfiguration> subtitleConfigurations; public final ImmutableList<SubtitleConfiguration> subtitleConfigurations;
/** @deprecated Use {@link #subtitleConfigurations} instead. */ /**
* @deprecated Use {@link #subtitleConfigurations} instead.
*/
@Deprecated public final List<Subtitle> subtitles; @Deprecated public final List<Subtitle> subtitles;
/** /**
@ -972,7 +1003,9 @@ public final class MediaItem implements Bundleable {
} }
} }
/** @deprecated Use {@link LocalConfiguration}. */ /**
* @deprecated Use {@link LocalConfiguration}.
*/
@Deprecated @Deprecated
public static final class PlaybackProperties extends LocalConfiguration { public static final class PlaybackProperties extends LocalConfiguration {
@ -1133,7 +1166,9 @@ public final class MediaItem implements Bundleable {
builder.maxPlaybackSpeed); builder.maxPlaybackSpeed);
} }
/** @deprecated Use {@link Builder} instead. */ /**
* @deprecated Use {@link Builder} instead.
*/
@Deprecated @Deprecated
public LiveConfiguration( public LiveConfiguration(
long targetOffsetMs, long targetOffsetMs,
@ -1269,7 +1304,7 @@ public final class MediaItem implements Bundleable {
} }
/** Sets the MIME type. */ /** Sets the MIME type. */
public Builder setMimeType(String mimeType) { public Builder setMimeType(@Nullable String mimeType) {
this.mimeType = mimeType; this.mimeType = mimeType;
return this; return this;
} }
@ -1397,24 +1432,32 @@ public final class MediaItem implements Bundleable {
} }
} }
/** @deprecated Use {@link MediaItem.SubtitleConfiguration} instead */ /**
* @deprecated Use {@link MediaItem.SubtitleConfiguration} instead
*/
@Deprecated @Deprecated
public static final class Subtitle extends SubtitleConfiguration { public static final class Subtitle extends SubtitleConfiguration {
/** @deprecated Use {@link Builder} instead. */ /**
* @deprecated Use {@link Builder} instead.
*/
@Deprecated @Deprecated
public Subtitle(Uri uri, String mimeType, @Nullable String language) { public Subtitle(Uri uri, String mimeType, @Nullable String language) {
this(uri, mimeType, language, /* selectionFlags= */ 0); this(uri, mimeType, language, /* selectionFlags= */ 0);
} }
/** @deprecated Use {@link Builder} instead. */ /**
* @deprecated Use {@link Builder} instead.
*/
@Deprecated @Deprecated
public Subtitle( public Subtitle(
Uri uri, String mimeType, @Nullable String language, @C.SelectionFlags int selectionFlags) { Uri uri, String mimeType, @Nullable String language, @C.SelectionFlags int selectionFlags) {
this(uri, mimeType, language, selectionFlags, /* roleFlags= */ 0, /* label= */ null); this(uri, mimeType, language, selectionFlags, /* roleFlags= */ 0, /* label= */ null);
} }
/** @deprecated Use {@link Builder} instead. */ /**
* @deprecated Use {@link Builder} instead.
*/
@Deprecated @Deprecated
public Subtitle( public Subtitle(
Uri uri, Uri uri,
@ -1516,7 +1559,9 @@ public final class MediaItem implements Bundleable {
return buildClippingProperties(); return buildClippingProperties();
} }
/** @deprecated Use {@link #build()} instead. */ /**
* @deprecated Use {@link #build()} instead.
*/
@Deprecated @Deprecated
public ClippingProperties buildClippingProperties() { public ClippingProperties buildClippingProperties() {
return new ClippingProperties(this); return new ClippingProperties(this);
@ -1643,7 +1688,9 @@ public final class MediaItem implements Bundleable {
} }
} }
/** @deprecated Use {@link ClippingConfiguration} instead. */ /**
* @deprecated Use {@link ClippingConfiguration} instead.
*/
@Deprecated @Deprecated
public static final class ClippingProperties extends ClippingConfiguration { public static final class ClippingProperties extends ClippingConfiguration {
public static final ClippingProperties UNSET = public static final ClippingProperties UNSET =
@ -1654,6 +1701,144 @@ public final class MediaItem implements Bundleable {
} }
} }
/**
* Metadata that helps the player to understand a playback request represented by a {@link
* MediaItem}.
*
* <p>This metadata is most useful for cases where playback requests are forwarded to other player
* instances (e.g. from a {@link android.media.session.MediaController}) and the player creating
* the request doesn't know the required {@link LocalConfiguration} for playback.
*/
public static final class RequestMetadata implements Bundleable {
/** Empty request metadata. */
public static final RequestMetadata EMPTY = new Builder().build();
/** Builder for {@link RequestMetadata} instances. */
public static final class Builder {
@Nullable private Uri mediaUri;
@Nullable private String searchQuery;
@Nullable private Bundle extras;
/** Constructs an instance. */
public Builder() {}
private Builder(RequestMetadata requestMetadata) {
this.mediaUri = requestMetadata.mediaUri;
this.searchQuery = requestMetadata.searchQuery;
this.extras = requestMetadata.extras;
}
/** Sets the URI of the requested media, or null if not known or applicable. */
public Builder setMediaUri(@Nullable Uri mediaUri) {
this.mediaUri = mediaUri;
return this;
}
/** Sets the search query for the requested media, or null if not applicable. */
public Builder setSearchQuery(@Nullable String searchQuery) {
this.searchQuery = searchQuery;
return this;
}
/** Sets optional extras {@link Bundle}. */
public Builder setExtras(@Nullable Bundle extras) {
this.extras = extras;
return this;
}
/** Builds the request metadata. */
public RequestMetadata build() {
return new RequestMetadata(this);
}
}
/** The URI of the requested media, or null if not known or applicable. */
@Nullable public final Uri mediaUri;
/** The search query for the requested media, or null if not applicable. */
@Nullable public final String searchQuery;
/**
* Optional extras {@link Bundle}.
*
* <p>Given the complexities of checking the equality of two {@link Bundle}s, this is not
* considered in the {@link #equals(Object)} or {@link #hashCode()}.
*/
@Nullable public final Bundle extras;
private RequestMetadata(Builder builder) {
this.mediaUri = builder.mediaUri;
this.searchQuery = builder.searchQuery;
this.extras = builder.extras;
}
/** Returns a {@link Builder} initialized with the values of this instance. */
public Builder buildUpon() {
return new Builder(this);
}
@Override
public boolean equals(@Nullable Object o) {
if (this == o) {
return true;
}
if (!(o instanceof RequestMetadata)) {
return false;
}
RequestMetadata that = (RequestMetadata) o;
return Util.areEqual(mediaUri, that.mediaUri) && Util.areEqual(searchQuery, that.searchQuery);
}
@Override
public int hashCode() {
int result = mediaUri == null ? 0 : mediaUri.hashCode();
result = 31 * result + (searchQuery == null ? 0 : searchQuery.hashCode());
return result;
}
// Bundleable implementation.
@Documented
@Retention(RetentionPolicy.SOURCE)
@Target(TYPE_USE)
@IntDef({FIELD_MEDIA_URI, FIELD_SEARCH_QUERY, FIELD_EXTRAS})
private @interface FieldNumber {}
private static final int FIELD_MEDIA_URI = 0;
private static final int FIELD_SEARCH_QUERY = 1;
private static final int FIELD_EXTRAS = 2;
@Override
public Bundle toBundle() {
Bundle bundle = new Bundle();
if (mediaUri != null) {
bundle.putParcelable(keyForField(FIELD_MEDIA_URI), mediaUri);
}
if (searchQuery != null) {
bundle.putString(keyForField(FIELD_SEARCH_QUERY), searchQuery);
}
if (extras != null) {
bundle.putBundle(keyForField(FIELD_EXTRAS), extras);
}
return bundle;
}
/** Object that can restore {@link RequestMetadata} from a {@link Bundle}. */
public static final Creator<RequestMetadata> CREATOR =
bundle ->
new RequestMetadata.Builder()
.setMediaUri(bundle.getParcelable(keyForField(FIELD_MEDIA_URI)))
.setSearchQuery(bundle.getString(keyForField(FIELD_SEARCH_QUERY)))
.setExtras(bundle.getBundle(keyForField(FIELD_EXTRAS)))
.build();
private static String keyForField(@RequestMetadata.FieldNumber int field) {
return Integer.toString(field, Character.MAX_RADIX);
}
}
/** /**
* The default media ID that is used if the media ID is not explicitly set by {@link * The default media ID that is used if the media ID is not explicitly set by {@link
* Builder#setMediaId(String)}. * Builder#setMediaId(String)}.
@ -1671,7 +1856,9 @@ public final class MediaItem implements Bundleable {
* boundaries. * boundaries.
*/ */
@Nullable public final LocalConfiguration localConfiguration; @Nullable public final LocalConfiguration localConfiguration;
/** @deprecated Use {@link #localConfiguration} instead. */ /**
* @deprecated Use {@link #localConfiguration} instead.
*/
@Deprecated @Nullable public final PlaybackProperties playbackProperties; @Deprecated @Nullable public final PlaybackProperties playbackProperties;
/** The live playback configuration. */ /** The live playback configuration. */
@ -1682,9 +1869,14 @@ public final class MediaItem implements Bundleable {
/** The clipping properties. */ /** The clipping properties. */
public final ClippingConfiguration clippingConfiguration; public final ClippingConfiguration clippingConfiguration;
/** @deprecated Use {@link #clippingConfiguration} instead. */ /**
* @deprecated Use {@link #clippingConfiguration} instead.
*/
@Deprecated public final ClippingProperties clippingProperties; @Deprecated public final ClippingProperties clippingProperties;
/** The media {@link RequestMetadata}. */
public final RequestMetadata requestMetadata;
// Using PlaybackProperties and ClippingProperties until they're deleted. // Using PlaybackProperties and ClippingProperties until they're deleted.
@SuppressWarnings("deprecation") @SuppressWarnings("deprecation")
private MediaItem( private MediaItem(
@ -1692,7 +1884,8 @@ public final class MediaItem implements Bundleable {
ClippingProperties clippingConfiguration, ClippingProperties clippingConfiguration,
@Nullable PlaybackProperties localConfiguration, @Nullable PlaybackProperties localConfiguration,
LiveConfiguration liveConfiguration, LiveConfiguration liveConfiguration,
MediaMetadata mediaMetadata) { MediaMetadata mediaMetadata,
RequestMetadata requestMetadata) {
this.mediaId = mediaId; this.mediaId = mediaId;
this.localConfiguration = localConfiguration; this.localConfiguration = localConfiguration;
this.playbackProperties = localConfiguration; this.playbackProperties = localConfiguration;
@ -1700,6 +1893,7 @@ public final class MediaItem implements Bundleable {
this.mediaMetadata = mediaMetadata; this.mediaMetadata = mediaMetadata;
this.clippingConfiguration = clippingConfiguration; this.clippingConfiguration = clippingConfiguration;
this.clippingProperties = clippingConfiguration; this.clippingProperties = clippingConfiguration;
this.requestMetadata = requestMetadata;
} }
/** Returns a {@link Builder} initialized with the values of this instance. */ /** Returns a {@link Builder} initialized with the values of this instance. */
@ -1722,7 +1916,8 @@ public final class MediaItem implements Bundleable {
&& clippingConfiguration.equals(other.clippingConfiguration) && clippingConfiguration.equals(other.clippingConfiguration)
&& Util.areEqual(localConfiguration, other.localConfiguration) && Util.areEqual(localConfiguration, other.localConfiguration)
&& Util.areEqual(liveConfiguration, other.liveConfiguration) && Util.areEqual(liveConfiguration, other.liveConfiguration)
&& Util.areEqual(mediaMetadata, other.mediaMetadata); && Util.areEqual(mediaMetadata, other.mediaMetadata)
&& Util.areEqual(requestMetadata, other.requestMetadata);
} }
@Override @Override
@ -1732,6 +1927,7 @@ public final class MediaItem implements Bundleable {
result = 31 * result + liveConfiguration.hashCode(); result = 31 * result + liveConfiguration.hashCode();
result = 31 * result + clippingConfiguration.hashCode(); result = 31 * result + clippingConfiguration.hashCode();
result = 31 * result + mediaMetadata.hashCode(); result = 31 * result + mediaMetadata.hashCode();
result = 31 * result + requestMetadata.hashCode();
return result; return result;
} }
@ -1744,7 +1940,8 @@ public final class MediaItem implements Bundleable {
FIELD_MEDIA_ID, FIELD_MEDIA_ID,
FIELD_LIVE_CONFIGURATION, FIELD_LIVE_CONFIGURATION,
FIELD_MEDIA_METADATA, FIELD_MEDIA_METADATA,
FIELD_CLIPPING_PROPERTIES FIELD_CLIPPING_PROPERTIES,
FIELD_REQUEST_METADATA
}) })
private @interface FieldNumber {} private @interface FieldNumber {}
@ -1752,6 +1949,7 @@ public final class MediaItem implements Bundleable {
private static final int FIELD_LIVE_CONFIGURATION = 1; private static final int FIELD_LIVE_CONFIGURATION = 1;
private static final int FIELD_MEDIA_METADATA = 2; private static final int FIELD_MEDIA_METADATA = 2;
private static final int FIELD_CLIPPING_PROPERTIES = 3; private static final int FIELD_CLIPPING_PROPERTIES = 3;
private static final int FIELD_REQUEST_METADATA = 4;
/** /**
* {@inheritDoc} * {@inheritDoc}
@ -1766,6 +1964,7 @@ public final class MediaItem implements Bundleable {
bundle.putBundle(keyForField(FIELD_LIVE_CONFIGURATION), liveConfiguration.toBundle()); bundle.putBundle(keyForField(FIELD_LIVE_CONFIGURATION), liveConfiguration.toBundle());
bundle.putBundle(keyForField(FIELD_MEDIA_METADATA), mediaMetadata.toBundle()); bundle.putBundle(keyForField(FIELD_MEDIA_METADATA), mediaMetadata.toBundle());
bundle.putBundle(keyForField(FIELD_CLIPPING_PROPERTIES), clippingConfiguration.toBundle()); bundle.putBundle(keyForField(FIELD_CLIPPING_PROPERTIES), clippingConfiguration.toBundle());
bundle.putBundle(keyForField(FIELD_REQUEST_METADATA), requestMetadata.toBundle());
return bundle; return bundle;
} }
@ -1802,12 +2001,20 @@ public final class MediaItem implements Bundleable {
} else { } else {
clippingConfiguration = ClippingConfiguration.CREATOR.fromBundle(clippingConfigurationBundle); clippingConfiguration = ClippingConfiguration.CREATOR.fromBundle(clippingConfigurationBundle);
} }
@Nullable Bundle requestMetadataBundle = bundle.getBundle(keyForField(FIELD_REQUEST_METADATA));
RequestMetadata requestMetadata;
if (requestMetadataBundle == null) {
requestMetadata = RequestMetadata.EMPTY;
} else {
requestMetadata = RequestMetadata.CREATOR.fromBundle(requestMetadataBundle);
}
return new MediaItem( return new MediaItem(
mediaId, mediaId,
clippingConfiguration, clippingConfiguration,
/* localConfiguration= */ null, /* localConfiguration= */ null,
liveConfiguration, liveConfiguration,
mediaMetadata); mediaMetadata,
requestMetadata);
} }
private static String keyForField(@FieldNumber int field) { private static String keyForField(@FieldNumber int field) {

View file

@ -52,7 +52,6 @@ public final class MediaMetadata implements Bundleable {
@Nullable private CharSequence displayTitle; @Nullable private CharSequence displayTitle;
@Nullable private CharSequence subtitle; @Nullable private CharSequence subtitle;
@Nullable private CharSequence description; @Nullable private CharSequence description;
@Nullable private Uri mediaUri;
@Nullable private Rating userRating; @Nullable private Rating userRating;
@Nullable private Rating overallRating; @Nullable private Rating overallRating;
@Nullable private byte[] artworkData; @Nullable private byte[] artworkData;
@ -88,7 +87,6 @@ public final class MediaMetadata implements Bundleable {
this.displayTitle = mediaMetadata.displayTitle; this.displayTitle = mediaMetadata.displayTitle;
this.subtitle = mediaMetadata.subtitle; this.subtitle = mediaMetadata.subtitle;
this.description = mediaMetadata.description; this.description = mediaMetadata.description;
this.mediaUri = mediaMetadata.mediaUri;
this.userRating = mediaMetadata.userRating; this.userRating = mediaMetadata.userRating;
this.overallRating = mediaMetadata.overallRating; this.overallRating = mediaMetadata.overallRating;
this.artworkData = mediaMetadata.artworkData; this.artworkData = mediaMetadata.artworkData;
@ -161,12 +159,6 @@ public final class MediaMetadata implements Bundleable {
return this; return this;
} }
/** Sets the media {@link Uri}. */
public Builder setMediaUri(@Nullable Uri mediaUri) {
this.mediaUri = mediaUri;
return this;
}
/** Sets the user {@link Rating}. */ /** Sets the user {@link Rating}. */
public Builder setUserRating(@Nullable Rating userRating) { public Builder setUserRating(@Nullable Rating userRating) {
this.userRating = userRating; this.userRating = userRating;
@ -247,7 +239,9 @@ public final class MediaMetadata implements Bundleable {
return this; return this;
} }
/** @deprecated Use {@link #setRecordingYear(Integer)} instead. */ /**
* @deprecated Use {@link #setRecordingYear(Integer)} instead.
*/
@Deprecated @Deprecated
public Builder setYear(@Nullable Integer year) { public Builder setYear(@Nullable Integer year) {
return setRecordingYear(year); return setRecordingYear(year);
@ -424,9 +418,6 @@ public final class MediaMetadata implements Bundleable {
if (mediaMetadata.description != null) { if (mediaMetadata.description != null) {
setDescription(mediaMetadata.description); setDescription(mediaMetadata.description);
} }
if (mediaMetadata.mediaUri != null) {
setMediaUri(mediaMetadata.mediaUri);
}
if (mediaMetadata.userRating != null) { if (mediaMetadata.userRating != null) {
setUserRating(mediaMetadata.userRating); setUserRating(mediaMetadata.userRating);
} }
@ -629,8 +620,6 @@ public final class MediaMetadata implements Bundleable {
@Nullable public final CharSequence subtitle; @Nullable public final CharSequence subtitle;
/** Optional description. */ /** Optional description. */
@Nullable public final CharSequence description; @Nullable public final CharSequence description;
/** Optional media {@link Uri}. */
@Nullable public final Uri mediaUri;
/** Optional user {@link Rating}. */ /** Optional user {@link Rating}. */
@Nullable public final Rating userRating; @Nullable public final Rating userRating;
/** Optional overall {@link Rating}. */ /** Optional overall {@link Rating}. */
@ -649,7 +638,9 @@ public final class MediaMetadata implements Bundleable {
@Nullable public final @FolderType Integer folderType; @Nullable public final @FolderType Integer folderType;
/** Optional boolean for media playability. */ /** Optional boolean for media playability. */
@Nullable public final Boolean isPlayable; @Nullable public final Boolean isPlayable;
/** @deprecated Use {@link #recordingYear} instead. */ /**
* @deprecated Use {@link #recordingYear} instead.
*/
@Deprecated @Nullable public final Integer year; @Deprecated @Nullable public final Integer year;
/** Optional year of the recording date. */ /** Optional year of the recording date. */
@Nullable public final Integer recordingYear; @Nullable public final Integer recordingYear;
@ -713,7 +704,6 @@ public final class MediaMetadata implements Bundleable {
this.displayTitle = builder.displayTitle; this.displayTitle = builder.displayTitle;
this.subtitle = builder.subtitle; this.subtitle = builder.subtitle;
this.description = builder.description; this.description = builder.description;
this.mediaUri = builder.mediaUri;
this.userRating = builder.userRating; this.userRating = builder.userRating;
this.overallRating = builder.overallRating; this.overallRating = builder.overallRating;
this.artworkData = builder.artworkData; this.artworkData = builder.artworkData;
@ -762,7 +752,6 @@ public final class MediaMetadata implements Bundleable {
&& Util.areEqual(displayTitle, that.displayTitle) && Util.areEqual(displayTitle, that.displayTitle)
&& Util.areEqual(subtitle, that.subtitle) && Util.areEqual(subtitle, that.subtitle)
&& Util.areEqual(description, that.description) && Util.areEqual(description, that.description)
&& Util.areEqual(mediaUri, that.mediaUri)
&& Util.areEqual(userRating, that.userRating) && Util.areEqual(userRating, that.userRating)
&& Util.areEqual(overallRating, that.overallRating) && Util.areEqual(overallRating, that.overallRating)
&& Arrays.equals(artworkData, that.artworkData) && Arrays.equals(artworkData, that.artworkData)
@ -798,7 +787,6 @@ public final class MediaMetadata implements Bundleable {
displayTitle, displayTitle,
subtitle, subtitle,
description, description,
mediaUri,
userRating, userRating,
overallRating, overallRating,
Arrays.hashCode(artworkData), Arrays.hashCode(artworkData),
@ -908,7 +896,6 @@ public final class MediaMetadata implements Bundleable {
bundle.putCharSequence(keyForField(FIELD_DISPLAY_TITLE), displayTitle); bundle.putCharSequence(keyForField(FIELD_DISPLAY_TITLE), displayTitle);
bundle.putCharSequence(keyForField(FIELD_SUBTITLE), subtitle); bundle.putCharSequence(keyForField(FIELD_SUBTITLE), subtitle);
bundle.putCharSequence(keyForField(FIELD_DESCRIPTION), description); bundle.putCharSequence(keyForField(FIELD_DESCRIPTION), description);
bundle.putParcelable(keyForField(FIELD_MEDIA_URI), mediaUri);
bundle.putByteArray(keyForField(FIELD_ARTWORK_DATA), artworkData); bundle.putByteArray(keyForField(FIELD_ARTWORK_DATA), artworkData);
bundle.putParcelable(keyForField(FIELD_ARTWORK_URI), artworkUri); bundle.putParcelable(keyForField(FIELD_ARTWORK_URI), artworkUri);
bundle.putCharSequence(keyForField(FIELD_WRITER), writer); bundle.putCharSequence(keyForField(FIELD_WRITER), writer);
@ -982,7 +969,6 @@ public final class MediaMetadata implements Bundleable {
.setDisplayTitle(bundle.getCharSequence(keyForField(FIELD_DISPLAY_TITLE))) .setDisplayTitle(bundle.getCharSequence(keyForField(FIELD_DISPLAY_TITLE)))
.setSubtitle(bundle.getCharSequence(keyForField(FIELD_SUBTITLE))) .setSubtitle(bundle.getCharSequence(keyForField(FIELD_SUBTITLE)))
.setDescription(bundle.getCharSequence(keyForField(FIELD_DESCRIPTION))) .setDescription(bundle.getCharSequence(keyForField(FIELD_DESCRIPTION)))
.setMediaUri(bundle.getParcelable(keyForField(FIELD_MEDIA_URI)))
.setArtworkData( .setArtworkData(
bundle.getByteArray(keyForField(FIELD_ARTWORK_DATA)), bundle.getByteArray(keyForField(FIELD_ARTWORK_DATA)),
bundle.containsKey(keyForField(FIELD_ARTWORK_DATA_TYPE)) bundle.containsKey(keyForField(FIELD_ARTWORK_DATA_TYPE))

View file

@ -397,27 +397,6 @@ public class PlaybackException extends Exception implements Bundleable {
// Bundleable implementation. // Bundleable implementation.
/**
* Identifiers for fields in a {@link Bundle} which represents a playback exception. Subclasses
* may use {@link #FIELD_CUSTOM_ID_BASE} to generate more keys using {@link #keyForField(int)}.
*
* <p>Note: Changes to the Bundleable implementation must be backwards compatible, so as to avoid
* breaking communication across different Bundleable implementation versions.
*/
@Documented
@Retention(RetentionPolicy.SOURCE)
@Target(TYPE_USE)
@IntDef(
open = true,
value = {
FIELD_INT_ERROR_CODE,
FIELD_LONG_TIMESTAMP_MS,
FIELD_STRING_MESSAGE,
FIELD_STRING_CAUSE_CLASS_NAME,
FIELD_STRING_CAUSE_MESSAGE,
})
protected @interface FieldNumber {}
private static final int FIELD_INT_ERROR_CODE = 0; private static final int FIELD_INT_ERROR_CODE = 0;
private static final int FIELD_LONG_TIMESTAMP_MS = 1; private static final int FIELD_LONG_TIMESTAMP_MS = 1;
private static final int FIELD_STRING_MESSAGE = 2; private static final int FIELD_STRING_MESSAGE = 2;
@ -425,7 +404,7 @@ public class PlaybackException extends Exception implements Bundleable {
private static final int FIELD_STRING_CAUSE_MESSAGE = 4; private static final int FIELD_STRING_CAUSE_MESSAGE = 4;
/** /**
* Defines a minimum field id value for subclasses to use when implementing {@link #toBundle()} * Defines a minimum field ID value for subclasses to use when implementing {@link #toBundle()}
* and {@link Bundleable.Creator}. * and {@link Bundleable.Creator}.
* *
* <p>Subclasses should obtain their {@link Bundle Bundle's} field keys by applying a non-negative * <p>Subclasses should obtain their {@link Bundle Bundle's} field keys by applying a non-negative
@ -452,10 +431,13 @@ public class PlaybackException extends Exception implements Bundleable {
} }
/** /**
* Converts the given {@link FieldNumber} to a string which can be used as a field key when * Converts the given field number to a string which can be used as a field key when implementing
* implementing {@link #toBundle()} and {@link Bundleable.Creator}. * {@link #toBundle()} and {@link Bundleable.Creator}.
*
* <p>Subclasses should use {@code field} values greater than or equal to {@link
* #FIELD_CUSTOM_ID_BASE}.
*/ */
protected static String keyForField(@FieldNumber int field) { protected static String keyForField(int field) {
return Integer.toString(field, Character.MAX_RADIX); return Integer.toString(field, Character.MAX_RADIX);
} }

View file

@ -33,12 +33,9 @@ import androidx.annotation.IntRange;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import com.google.android.exoplayer2.audio.AudioAttributes; import com.google.android.exoplayer2.audio.AudioAttributes;
import com.google.android.exoplayer2.metadata.Metadata; import com.google.android.exoplayer2.metadata.Metadata;
import com.google.android.exoplayer2.source.TrackGroupArray;
import com.google.android.exoplayer2.text.Cue; import com.google.android.exoplayer2.text.Cue;
import com.google.android.exoplayer2.trackselection.TrackSelection; import com.google.android.exoplayer2.text.CueGroup;
import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
import com.google.android.exoplayer2.trackselection.TrackSelectionParameters; import com.google.android.exoplayer2.trackselection.TrackSelectionParameters;
import com.google.android.exoplayer2.util.BundleableUtil;
import com.google.android.exoplayer2.util.FlagSet; import com.google.android.exoplayer2.util.FlagSet;
import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.util.Util;
import com.google.android.exoplayer2.video.VideoSize; import com.google.android.exoplayer2.video.VideoSize;
@ -64,8 +61,8 @@ import java.util.List;
* <ul> * <ul>
* <li>They can provide a {@link Timeline} representing the structure of the media being played, * <li>They can provide a {@link Timeline} representing the structure of the media being played,
* which can be obtained by calling {@link #getCurrentTimeline()}. * which can be obtained by calling {@link #getCurrentTimeline()}.
* <li>They can provide a {@link TracksInfo} defining the currently available tracks and which are * <li>They can provide a {@link Tracks} defining the currently available tracks and which are
* selected to be rendered, which can be obtained by calling {@link #getCurrentTracksInfo()}. * selected to be rendered, which can be obtained by calling {@link #getCurrentTracks()}.
* </ul> * </ul>
*/ */
public interface Player { public interface Player {
@ -148,7 +145,9 @@ public interface Player {
* The UID of the window, or {@code null} if the timeline is {@link Timeline#isEmpty() empty}. * The UID of the window, or {@code null} if the timeline is {@link Timeline#isEmpty() empty}.
*/ */
@Nullable public final Object windowUid; @Nullable public final Object windowUid;
/** @deprecated Use {@link #mediaItemIndex} instead. */ /**
* @deprecated Use {@link #mediaItemIndex} instead.
*/
@Deprecated public final int windowIndex; @Deprecated public final int windowIndex;
/** The media item index. */ /** The media item index. */
public final int mediaItemIndex; public final int mediaItemIndex;
@ -295,7 +294,9 @@ public interface Player {
public Bundle toBundle() { public Bundle toBundle() {
Bundle bundle = new Bundle(); Bundle bundle = new Bundle();
bundle.putInt(keyForField(FIELD_MEDIA_ITEM_INDEX), mediaItemIndex); bundle.putInt(keyForField(FIELD_MEDIA_ITEM_INDEX), mediaItemIndex);
bundle.putBundle(keyForField(FIELD_MEDIA_ITEM), BundleableUtil.toNullableBundle(mediaItem)); if (mediaItem != null) {
bundle.putBundle(keyForField(FIELD_MEDIA_ITEM), mediaItem.toBundle());
}
bundle.putInt(keyForField(FIELD_PERIOD_INDEX), periodIndex); bundle.putInt(keyForField(FIELD_PERIOD_INDEX), periodIndex);
bundle.putLong(keyForField(FIELD_POSITION_MS), positionMs); bundle.putLong(keyForField(FIELD_POSITION_MS), positionMs);
bundle.putLong(keyForField(FIELD_CONTENT_POSITION_MS), contentPositionMs); bundle.putLong(keyForField(FIELD_CONTENT_POSITION_MS), contentPositionMs);
@ -310,10 +311,10 @@ public interface Player {
private static PositionInfo fromBundle(Bundle bundle) { private static PositionInfo fromBundle(Bundle bundle) {
int mediaItemIndex = int mediaItemIndex =
bundle.getInt(keyForField(FIELD_MEDIA_ITEM_INDEX), /* defaultValue= */ C.INDEX_UNSET); bundle.getInt(keyForField(FIELD_MEDIA_ITEM_INDEX), /* defaultValue= */ C.INDEX_UNSET);
@Nullable Bundle mediaItemBundle = bundle.getBundle(keyForField(FIELD_MEDIA_ITEM));
@Nullable @Nullable
MediaItem mediaItem = MediaItem mediaItem =
BundleableUtil.fromNullableBundle( mediaItemBundle == null ? null : MediaItem.CREATOR.fromBundle(mediaItemBundle);
MediaItem.CREATOR, bundle.getBundle(keyForField(FIELD_MEDIA_ITEM)));
int periodIndex = int periodIndex =
bundle.getInt(keyForField(FIELD_PERIOD_INDEX), /* defaultValue= */ C.INDEX_UNSET); bundle.getInt(keyForField(FIELD_PERIOD_INDEX), /* defaultValue= */ C.INDEX_UNSET);
long positionMs = long positionMs =
@ -381,7 +382,7 @@ public interface Player {
COMMAND_SET_VIDEO_SURFACE, COMMAND_SET_VIDEO_SURFACE,
COMMAND_GET_TEXT, COMMAND_GET_TEXT,
COMMAND_SET_TRACK_SELECTION_PARAMETERS, COMMAND_SET_TRACK_SELECTION_PARAMETERS,
COMMAND_GET_TRACK_INFOS, COMMAND_GET_TRACKS,
}; };
private final FlagSet.Builder flagsBuilder; private final FlagSet.Builder flagsBuilder;
@ -522,6 +523,11 @@ public interface Player {
return flags.contains(command); return flags.contains(command);
} }
/** Returns whether the set of commands contains at least one of the given {@code commands}. */
public boolean containsAny(@Command int... commands) {
return flags.containsAny(commands);
}
/** Returns the number of commands in this set. */ /** Returns the number of commands in this set. */
public int size() { public int size() {
return flags.size(); return flags.size();
@ -669,40 +675,24 @@ public interface Player {
@Nullable MediaItem mediaItem, @MediaItemTransitionReason int reason) {} @Nullable MediaItem mediaItem, @MediaItemTransitionReason int reason) {}
/** /**
* Called when the available or selected tracks change. * Called when the tracks change.
* *
* <p>{@link #onEvents(Player, Events)} will also be called to report this event along with * <p>{@link #onEvents(Player, Events)} will also be called to report this event along with
* other events that happen in the same {@link Looper} message queue iteration. * other events that happen in the same {@link Looper} message queue iteration.
* *
* @param trackGroups The available tracks. Never null, but may be of length zero. * @param tracks The available tracks information. Never null, but may be of length zero.
* @param trackSelections The selected tracks. Never null, but may contain null elements. A
* concrete implementation may include null elements if it has a fixed number of renderer
* components, wishes to report a TrackSelection for each of them, and has one or more
* renderer components that is not assigned any selected tracks.
* @deprecated Use {@link #onTracksInfoChanged(TracksInfo)} instead.
*/ */
@Deprecated default void onTracksChanged(Tracks tracks) {}
default void onTracksChanged(
TrackGroupArray trackGroups, TrackSelectionArray trackSelections) {}
/**
* Called when the available or selected tracks change.
*
* <p>{@link #onEvents(Player, Events)} will also be called to report this event along with
* other events that happen in the same {@link Looper} message queue iteration.
*
* @param tracksInfo The available tracks information. Never null, but may be of length zero.
*/
default void onTracksInfoChanged(TracksInfo tracksInfo) {}
/** /**
* Called when the combined {@link MediaMetadata} changes. * Called when the combined {@link MediaMetadata} changes.
* *
* <p>The provided {@link MediaMetadata} is a combination of the {@link MediaItem#mediaMetadata} * <p>The provided {@link MediaMetadata} is a combination of the {@link MediaItem#mediaMetadata
* and the static and dynamic metadata from the {@link TrackSelection#getFormat(int) track * MediaItem metadata}, the static metadata in the media's {@link Format#metadata Format}, and
* selections' formats} and {@link Listener#onMetadata(Metadata)}. If a field is populated in * any timed metadata that has been parsed from the media and output via {@link
* the {@link MediaItem#mediaMetadata}, it will be prioritised above the same field coming from * Listener#onMetadata(Metadata)}. If a field is populated in the {@link
* static or dynamic metadata. * MediaItem#mediaMetadata}, it will be prioritised above the same field coming from static or
* timed metadata.
* *
* <p>This method may be called multiple times in quick succession. * <p>This method may be called multiple times in quick succession.
* *
@ -731,7 +721,9 @@ public interface Player {
*/ */
default void onIsLoadingChanged(boolean isLoading) {} default void onIsLoadingChanged(boolean isLoading) {}
/** @deprecated Use {@link #onIsLoadingChanged(boolean)} instead. */ /**
* @deprecated Use {@link #onIsLoadingChanged(boolean)} instead.
*/
@Deprecated @Deprecated
default void onLoadingChanged(boolean isLoading) {} default void onLoadingChanged(boolean isLoading) {}
@ -1026,16 +1018,28 @@ public interface Player {
/** /**
* Called when there is a change in the {@link Cue Cues}. * Called when there is a change in the {@link Cue Cues}.
* *
* <p>{@code cues} is in ascending order of priority. If any of the cue boxes overlap when * <p>Both {@link #onCues(List)} and {@link #onCues(CueGroup)} are called when there is a change
* displayed, the {@link Cue} nearer the end of the list should be shown on top. * in the cues. You should only implement one or the other.
* *
* <p>{@link #onEvents(Player, Events)} will also be called to report this event along with * <p>{@link #onEvents(Player, Events)} will also be called to report this event along with
* other events that happen in the same {@link Looper} message queue iteration. * other events that happen in the same {@link Looper} message queue iteration.
* *
* @param cues The {@link Cue Cues}. May be empty. * @deprecated Use {@link #onCues(CueGroup)} instead.
*/ */
@Deprecated
default void onCues(List<Cue> cues) {} default void onCues(List<Cue> cues) {}
/**
* Called when there is a change in the {@link CueGroup}.
*
* <p>Both {@link #onCues(List)} and {@link #onCues(CueGroup)} are called when there is a change
* in the cues. You should only implement one or the other.
*
* <p>{@link #onEvents(Player, Events)} will also be called to report this event along with
* other events that happen in the same {@link Looper} message queue iteration.
*/
default void onCues(CueGroup cueGroup) {}
/** /**
* Called when there is metadata associated with the current playback time. * Called when there is metadata associated with the current playback time.
* *
@ -1309,7 +1313,7 @@ public interface Player {
int EVENT_TIMELINE_CHANGED = 0; int EVENT_TIMELINE_CHANGED = 0;
/** {@link #getCurrentMediaItem()} changed or the player started repeating the current item. */ /** {@link #getCurrentMediaItem()} changed or the player started repeating the current item. */
int EVENT_MEDIA_ITEM_TRANSITION = 1; int EVENT_MEDIA_ITEM_TRANSITION = 1;
/** {@link #getCurrentTracksInfo()} changed. */ /** {@link #getCurrentTracks()} changed. */
int EVENT_TRACKS_CHANGED = 2; int EVENT_TRACKS_CHANGED = 2;
/** {@link #isLoading()} ()} changed. */ /** {@link #isLoading()} ()} changed. */
int EVENT_IS_LOADING_CHANGED = 3; int EVENT_IS_LOADING_CHANGED = 3;
@ -1388,7 +1392,7 @@ public interface Player {
* #COMMAND_GET_VOLUME}, {@link #COMMAND_GET_DEVICE_VOLUME}, {@link #COMMAND_SET_VOLUME}, {@link * #COMMAND_GET_VOLUME}, {@link #COMMAND_GET_DEVICE_VOLUME}, {@link #COMMAND_SET_VOLUME}, {@link
* #COMMAND_SET_DEVICE_VOLUME}, {@link #COMMAND_ADJUST_DEVICE_VOLUME}, {@link * #COMMAND_SET_DEVICE_VOLUME}, {@link #COMMAND_ADJUST_DEVICE_VOLUME}, {@link
* #COMMAND_SET_VIDEO_SURFACE}, {@link #COMMAND_GET_TEXT}, {@link * #COMMAND_SET_VIDEO_SURFACE}, {@link #COMMAND_GET_TEXT}, {@link
* #COMMAND_SET_TRACK_SELECTION_PARAMETERS} or {@link #COMMAND_GET_TRACK_INFOS}. * #COMMAND_SET_TRACK_SELECTION_PARAMETERS} or {@link #COMMAND_GET_TRACKS}.
*/ */
// @Target list includes both 'default' targets and TYPE_USE, to ensure backwards compatibility // @Target list includes both 'default' targets and TYPE_USE, to ensure backwards compatibility
// with Kotlin usages from before TYPE_USE was added. // with Kotlin usages from before TYPE_USE was added.
@ -1426,7 +1430,7 @@ public interface Player {
COMMAND_SET_VIDEO_SURFACE, COMMAND_SET_VIDEO_SURFACE,
COMMAND_GET_TEXT, COMMAND_GET_TEXT,
COMMAND_SET_TRACK_SELECTION_PARAMETERS, COMMAND_SET_TRACK_SELECTION_PARAMETERS,
COMMAND_GET_TRACK_INFOS, COMMAND_GET_TRACKS,
}) })
@interface Command {} @interface Command {}
/** Command to start, pause or resume playback. */ /** Command to start, pause or resume playback. */
@ -1439,23 +1443,31 @@ public interface Player {
int COMMAND_SEEK_TO_DEFAULT_POSITION = 4; int COMMAND_SEEK_TO_DEFAULT_POSITION = 4;
/** Command to seek anywhere into the current {@link MediaItem}. */ /** Command to seek anywhere into the current {@link MediaItem}. */
int COMMAND_SEEK_IN_CURRENT_MEDIA_ITEM = 5; int COMMAND_SEEK_IN_CURRENT_MEDIA_ITEM = 5;
/** @deprecated Use {@link #COMMAND_SEEK_IN_CURRENT_MEDIA_ITEM} instead. */ /**
* @deprecated Use {@link #COMMAND_SEEK_IN_CURRENT_MEDIA_ITEM} instead.
*/
@Deprecated int COMMAND_SEEK_IN_CURRENT_WINDOW = COMMAND_SEEK_IN_CURRENT_MEDIA_ITEM; @Deprecated int COMMAND_SEEK_IN_CURRENT_WINDOW = COMMAND_SEEK_IN_CURRENT_MEDIA_ITEM;
/** Command to seek to the default position of the previous {@link MediaItem}. */ /** Command to seek to the default position of the previous {@link MediaItem}. */
int COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM = 6; int COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM = 6;
/** @deprecated Use {@link #COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM} instead. */ /**
* @deprecated Use {@link #COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM} instead.
*/
@Deprecated int COMMAND_SEEK_TO_PREVIOUS_WINDOW = COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM; @Deprecated int COMMAND_SEEK_TO_PREVIOUS_WINDOW = COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM;
/** Command to seek to an earlier position in the current or previous {@link MediaItem}. */ /** Command to seek to an earlier position in the current or previous {@link MediaItem}. */
int COMMAND_SEEK_TO_PREVIOUS = 7; int COMMAND_SEEK_TO_PREVIOUS = 7;
/** Command to seek to the default position of the next {@link MediaItem}. */ /** Command to seek to the default position of the next {@link MediaItem}. */
int COMMAND_SEEK_TO_NEXT_MEDIA_ITEM = 8; int COMMAND_SEEK_TO_NEXT_MEDIA_ITEM = 8;
/** @deprecated Use {@link #COMMAND_SEEK_TO_NEXT_MEDIA_ITEM} instead. */ /**
* @deprecated Use {@link #COMMAND_SEEK_TO_NEXT_MEDIA_ITEM} instead.
*/
@Deprecated int COMMAND_SEEK_TO_NEXT_WINDOW = COMMAND_SEEK_TO_NEXT_MEDIA_ITEM; @Deprecated int COMMAND_SEEK_TO_NEXT_WINDOW = COMMAND_SEEK_TO_NEXT_MEDIA_ITEM;
/** Command to seek to a later position in the current or next {@link MediaItem}. */ /** Command to seek to a later position in the current or next {@link MediaItem}. */
int COMMAND_SEEK_TO_NEXT = 9; int COMMAND_SEEK_TO_NEXT = 9;
/** Command to seek anywhere in any {@link MediaItem}. */ /** Command to seek anywhere in any {@link MediaItem}. */
int COMMAND_SEEK_TO_MEDIA_ITEM = 10; int COMMAND_SEEK_TO_MEDIA_ITEM = 10;
/** @deprecated Use {@link #COMMAND_SEEK_TO_MEDIA_ITEM} instead. */ /**
* @deprecated Use {@link #COMMAND_SEEK_TO_MEDIA_ITEM} instead.
*/
@Deprecated int COMMAND_SEEK_TO_WINDOW = COMMAND_SEEK_TO_MEDIA_ITEM; @Deprecated int COMMAND_SEEK_TO_WINDOW = COMMAND_SEEK_TO_MEDIA_ITEM;
/** Command to seek back by a fixed increment into the current {@link MediaItem}. */ /** Command to seek back by a fixed increment into the current {@link MediaItem}. */
int COMMAND_SEEK_BACK = 11; int COMMAND_SEEK_BACK = 11;
@ -1495,8 +1507,8 @@ public interface Player {
int COMMAND_GET_TEXT = 28; int COMMAND_GET_TEXT = 28;
/** Command to set the player's track selection parameters. */ /** Command to set the player's track selection parameters. */
int COMMAND_SET_TRACK_SELECTION_PARAMETERS = 29; int COMMAND_SET_TRACK_SELECTION_PARAMETERS = 29;
/** Command to get track infos. */ /** Command to get details of the current track selection. */
int COMMAND_GET_TRACK_INFOS = 30; int COMMAND_GET_TRACKS = 30;
/** Represents an invalid {@link Command}. */ /** Represents an invalid {@link Command}. */
int COMMAND_INVALID = -1; int COMMAND_INVALID = -1;
@ -1881,11 +1893,15 @@ public interface Player {
*/ */
void seekForward(); void seekForward();
/** @deprecated Use {@link #hasPreviousMediaItem()} instead. */ /**
* @deprecated Use {@link #hasPreviousMediaItem()} instead.
*/
@Deprecated @Deprecated
boolean hasPrevious(); boolean hasPrevious();
/** @deprecated Use {@link #hasPreviousMediaItem()} instead. */ /**
* @deprecated Use {@link #hasPreviousMediaItem()} instead.
*/
@Deprecated @Deprecated
boolean hasPreviousWindow(); boolean hasPreviousWindow();
@ -1899,11 +1915,15 @@ public interface Player {
*/ */
boolean hasPreviousMediaItem(); boolean hasPreviousMediaItem();
/** @deprecated Use {@link #seekToPreviousMediaItem()} instead. */ /**
* @deprecated Use {@link #seekToPreviousMediaItem()} instead.
*/
@Deprecated @Deprecated
void previous(); void previous();
/** @deprecated Use {@link #seekToPreviousMediaItem()} instead. */ /**
* @deprecated Use {@link #seekToPreviousMediaItem()} instead.
*/
@Deprecated @Deprecated
void seekToPreviousWindow(); void seekToPreviousWindow();
@ -1949,11 +1969,15 @@ public interface Player {
*/ */
void seekToPrevious(); void seekToPrevious();
/** @deprecated Use {@link #hasNextMediaItem()} instead. */ /**
* @deprecated Use {@link #hasNextMediaItem()} instead.
*/
@Deprecated @Deprecated
boolean hasNext(); boolean hasNext();
/** @deprecated Use {@link #hasNextMediaItem()} instead. */ /**
* @deprecated Use {@link #hasNextMediaItem()} instead.
*/
@Deprecated @Deprecated
boolean hasNextWindow(); boolean hasNextWindow();
@ -1967,11 +1991,15 @@ public interface Player {
*/ */
boolean hasNextMediaItem(); boolean hasNextMediaItem();
/** @deprecated Use {@link #seekToNextMediaItem()} instead. */ /**
* @deprecated Use {@link #seekToNextMediaItem()} instead.
*/
@Deprecated @Deprecated
void next(); void next();
/** @deprecated Use {@link #seekToNextMediaItem()} instead. */ /**
* @deprecated Use {@link #seekToNextMediaItem()} instead.
*/
@Deprecated @Deprecated
void seekToNextWindow(); void seekToNextWindow();
@ -2060,33 +2088,11 @@ public interface Player {
void release(); void release();
/** /**
* Returns the available track groups. * Returns the current tracks.
* *
* @see Listener#onTracksChanged(TrackGroupArray, TrackSelectionArray) * @see Listener#onTracksChanged(Tracks)
* @deprecated Use {@link #getCurrentTracksInfo()}.
*/ */
@Deprecated Tracks getCurrentTracks();
TrackGroupArray getCurrentTrackGroups();
/**
* Returns the current track selections.
*
* <p>A concrete implementation may include null elements if it has a fixed number of renderer
* components, wishes to report a TrackSelection for each of them, and has one or more renderer
* components that is not assigned any selected tracks.
*
* @see Listener#onTracksChanged(TrackGroupArray, TrackSelectionArray)
* @deprecated Use {@link #getCurrentTracksInfo()}.
*/
@Deprecated
TrackSelectionArray getCurrentTrackSelections();
/**
* Returns the available tracks, as well as the tracks' support, type, and selection status.
*
* @see Listener#onTracksChanged(TrackGroupArray, TrackSelectionArray)
*/
TracksInfo getCurrentTracksInfo();
/** /**
* Returns the parameters constraining the track selection. * Returns the parameters constraining the track selection.
@ -2118,11 +2124,11 @@ public interface Player {
* Returns the current combined {@link MediaMetadata}, or {@link MediaMetadata#EMPTY} if not * Returns the current combined {@link MediaMetadata}, or {@link MediaMetadata#EMPTY} if not
* supported. * supported.
* *
* <p>This {@link MediaMetadata} is a combination of the {@link MediaItem#mediaMetadata} and the * <p>This {@link MediaMetadata} is a combination of the {@link MediaItem#mediaMetadata MediaItem
* static and dynamic metadata from the {@link TrackSelection#getFormat(int) track selections' * metadata}, the static metadata in the media's {@link Format#metadata Format}, and any timed
* formats} and {@link Listener#onMetadata(Metadata)}. If a field is populated in the {@link * metadata that has been parsed from the media and output via {@link
* MediaItem#mediaMetadata}, it will be prioritised above the same field coming from static or * Listener#onMetadata(Metadata)}. If a field is populated in the {@link MediaItem#mediaMetadata},
* dynamic metadata. * it will be prioritised above the same field coming from static or timed metadata.
*/ */
MediaMetadata getMediaMetadata(); MediaMetadata getMediaMetadata();
@ -2151,7 +2157,9 @@ public interface Player {
/** Returns the index of the period currently being played. */ /** Returns the index of the period currently being played. */
int getCurrentPeriodIndex(); int getCurrentPeriodIndex();
/** @deprecated Use {@link #getCurrentMediaItemIndex()} instead. */ /**
* @deprecated Use {@link #getCurrentMediaItemIndex()} instead.
*/
@Deprecated @Deprecated
int getCurrentWindowIndex(); int getCurrentWindowIndex();
@ -2162,7 +2170,9 @@ public interface Player {
*/ */
int getCurrentMediaItemIndex(); int getCurrentMediaItemIndex();
/** @deprecated Use {@link #getNextMediaItemIndex()} instead. */ /**
* @deprecated Use {@link #getNextMediaItemIndex()} instead.
*/
@Deprecated @Deprecated
int getNextWindowIndex(); int getNextWindowIndex();
@ -2178,7 +2188,9 @@ public interface Player {
*/ */
int getNextMediaItemIndex(); int getNextMediaItemIndex();
/** @deprecated Use {@link #getPreviousMediaItemIndex()} instead. */ /**
* @deprecated Use {@link #getPreviousMediaItemIndex()} instead.
*/
@Deprecated @Deprecated
int getPreviousWindowIndex(); int getPreviousWindowIndex();
@ -2239,7 +2251,9 @@ public interface Player {
*/ */
long getTotalBufferedDuration(); long getTotalBufferedDuration();
/** @deprecated Use {@link #isCurrentMediaItemDynamic()} instead. */ /**
* @deprecated Use {@link #isCurrentMediaItemDynamic()} instead.
*/
@Deprecated @Deprecated
boolean isCurrentWindowDynamic(); boolean isCurrentWindowDynamic();
@ -2251,7 +2265,9 @@ public interface Player {
*/ */
boolean isCurrentMediaItemDynamic(); boolean isCurrentMediaItemDynamic();
/** @deprecated Use {@link #isCurrentMediaItemLive()} instead. */ /**
* @deprecated Use {@link #isCurrentMediaItemLive()} instead.
*/
@Deprecated @Deprecated
boolean isCurrentWindowLive(); boolean isCurrentWindowLive();
@ -2276,7 +2292,9 @@ public interface Player {
*/ */
long getCurrentLiveOffset(); long getCurrentLiveOffset();
/** @deprecated Use {@link #isCurrentMediaItemSeekable()} instead. */ /**
* @deprecated Use {@link #isCurrentMediaItemSeekable()} instead.
*/
@Deprecated @Deprecated
boolean isCurrentWindowSeekable(); boolean isCurrentWindowSeekable();
@ -2439,8 +2457,8 @@ public interface Player {
*/ */
VideoSize getVideoSize(); VideoSize getVideoSize();
/** Returns the current {@link Cue Cues}. This list may be empty. */ /** Returns the current {@link CueGroup}. */
List<Cue> getCurrentCues(); CueGroup getCurrentCues();
/** Gets the device information. */ /** Gets the device information. */
DeviceInfo getDeviceInfo(); DeviceInfo getDeviceInfo();

View file

@ -31,7 +31,7 @@ import java.lang.annotation.Target;
public abstract class Rating implements Bundleable { public abstract class Rating implements Bundleable {
/** A float value that denotes the rating is unset. */ /** A float value that denotes the rating is unset. */
public static final float RATING_UNSET = -1.0f; /* package */ static final float RATING_UNSET = -1.0f;
// Default package-private constructor to prevent extending Rating class outside this package. // Default package-private constructor to prevent extending Rating class outside this package.
/* package */ Rating() {} /* package */ Rating() {}

View file

@ -169,7 +169,9 @@ public abstract class Timeline implements Bundleable {
*/ */
public Object uid; public Object uid;
/** @deprecated Use {@link #mediaItem} instead. */ /**
* @deprecated Use {@link #mediaItem} instead.
*/
@Deprecated @Nullable public Object tag; @Deprecated @Nullable public Object tag;
/** The {@link MediaItem} associated to the window. Not necessarily unique. */ /** The {@link MediaItem} associated to the window. Not necessarily unique. */
@ -212,7 +214,9 @@ public abstract class Timeline implements Bundleable {
/** Whether this window may change when the timeline is updated. */ /** Whether this window may change when the timeline is updated. */
public boolean isDynamic; public boolean isDynamic;
/** @deprecated Use {@link #isLive()} instead. */ /**
* @deprecated Use {@link #isLive()} instead.
*/
@Deprecated public boolean isLive; @Deprecated public boolean isLive;
/** /**
@ -1169,14 +1173,18 @@ public abstract class Timeline implements Bundleable {
== C.INDEX_UNSET; == C.INDEX_UNSET;
} }
/** @deprecated Use {@link #getPeriodPositionUs(Window, Period, int, long)} instead. */ /**
* @deprecated Use {@link #getPeriodPositionUs(Window, Period, int, long)} instead.
*/
@Deprecated @Deprecated
@InlineMe(replacement = "this.getPeriodPositionUs(window, period, windowIndex, windowPositionUs)") @InlineMe(replacement = "this.getPeriodPositionUs(window, period, windowIndex, windowPositionUs)")
public final Pair<Object, Long> getPeriodPosition( public final Pair<Object, Long> getPeriodPosition(
Window window, Period period, int windowIndex, long windowPositionUs) { Window window, Period period, int windowIndex, long windowPositionUs) {
return getPeriodPositionUs(window, period, windowIndex, windowPositionUs); return getPeriodPositionUs(window, period, windowIndex, windowPositionUs);
} }
/** @deprecated Use {@link #getPeriodPositionUs(Window, Period, int, long, long)} instead. */ /**
* @deprecated Use {@link #getPeriodPositionUs(Window, Period, int, long, long)} instead.
*/
@Deprecated @Deprecated
@Nullable @Nullable
@InlineMe( @InlineMe(

View file

@ -17,8 +17,6 @@ package com.google.android.exoplayer2;
import static com.google.android.exoplayer2.util.Assertions.checkArgument; import static com.google.android.exoplayer2.util.Assertions.checkArgument;
import static com.google.android.exoplayer2.util.Assertions.checkNotNull; import static com.google.android.exoplayer2.util.Assertions.checkNotNull;
import static com.google.android.exoplayer2.util.BundleableUtil.fromBundleNullableList;
import static com.google.android.exoplayer2.util.BundleableUtil.fromNullableBundle;
import static com.google.android.exoplayer2.util.BundleableUtil.toBundleArrayList; import static com.google.android.exoplayer2.util.BundleableUtil.toBundleArrayList;
import static java.lang.annotation.ElementType.TYPE_USE; import static java.lang.annotation.ElementType.TYPE_USE;
@ -26,6 +24,7 @@ import android.os.Bundle;
import androidx.annotation.IntDef; import androidx.annotation.IntDef;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import com.google.android.exoplayer2.source.TrackGroup; import com.google.android.exoplayer2.source.TrackGroup;
import com.google.android.exoplayer2.util.BundleableUtil;
import com.google.common.base.MoreObjects; import com.google.common.base.MoreObjects;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.google.common.primitives.Booleans; import com.google.common.primitives.Booleans;
@ -37,48 +36,71 @@ import java.util.Arrays;
import java.util.List; import java.util.List;
/** Information about groups of tracks. */ /** Information about groups of tracks. */
public final class TracksInfo implements Bundleable { public final class Tracks implements Bundleable {
/** /**
* Information about a single group of tracks, including the underlying {@link TrackGroup}, the * Information about a single group of tracks, including the underlying {@link TrackGroup}, the
* {@link C.TrackType type} of tracks it contains, and the level to which each track is supported * level to which each track is supported by the player, and whether any of the tracks are
* by the player. * selected.
*/ */
public static final class TrackGroupInfo implements Bundleable { public static final class Group implements Bundleable {
private final TrackGroup trackGroup;
/** The number of tracks in the group. */
public final int length;
private final TrackGroup mediaTrackGroup;
private final boolean adaptiveSupported;
private final @C.FormatSupport int[] trackSupport; private final @C.FormatSupport int[] trackSupport;
private final @C.TrackType int trackType;
private final boolean[] trackSelected; private final boolean[] trackSelected;
/** /**
* Constructs a TrackGroupInfo. * Constructs an instance.
* *
* @param trackGroup The {@link TrackGroup} described. * @param mediaTrackGroup The underlying {@link TrackGroup} defined by the media.
* @param trackSupport The {@link C.FormatSupport} of each track in the {@code trackGroup}. * @param adaptiveSupported Whether the player supports adaptive selections containing more than
* @param trackType The {@link C.TrackType} of the tracks in the {@code trackGroup}. * one track in the group.
* @param tracksSelected Whether a track is selected for each track in {@code trackGroup}. * @param trackSupport The {@link C.FormatSupport} of each track in the group.
* @param trackSelected Whether each track in the {@code trackGroup} is selected.
*/ */
public TrackGroupInfo( public Group(
TrackGroup trackGroup, TrackGroup mediaTrackGroup,
boolean adaptiveSupported,
@C.FormatSupport int[] trackSupport, @C.FormatSupport int[] trackSupport,
@C.TrackType int trackType, boolean[] trackSelected) {
boolean[] tracksSelected) { length = mediaTrackGroup.length;
int length = trackGroup.length; checkArgument(length == trackSupport.length && length == trackSelected.length);
checkArgument(length == trackSupport.length && length == tracksSelected.length); this.mediaTrackGroup = mediaTrackGroup;
this.trackGroup = trackGroup; this.adaptiveSupported = adaptiveSupported && length > 1;
this.trackSupport = trackSupport.clone(); this.trackSupport = trackSupport.clone();
this.trackType = trackType; this.trackSelected = trackSelected.clone();
this.trackSelected = tracksSelected.clone();
} }
/** Returns the {@link TrackGroup} described by this {@code TrackGroupInfo}. */ /**
public TrackGroup getTrackGroup() { * Returns the underlying {@link TrackGroup} defined by the media.
return trackGroup; *
* <p>Unlike this class, {@link TrackGroup} only contains information defined by the media
* itself, and does not contain runtime information such as which tracks are supported and
* currently selected. This makes it suitable for use as a {@code key} in certain {@code (key,
* value)} data structures.
*/
public TrackGroup getMediaTrackGroup() {
return mediaTrackGroup;
}
/**
* Returns the {@link Format} for a specified track.
*
* @param trackIndex The index of the track in the group.
* @return The {@link Format} of the track.
*/
public Format getTrackFormat(int trackIndex) {
return mediaTrackGroup.getFormat(trackIndex);
} }
/** /**
* Returns the level of support for a specified track. * Returns the level of support for a specified track.
* *
* @param trackIndex The index of the track in the {@link TrackGroup}. * @param trackIndex The index of the track in the group.
* @return The {@link C.FormatSupport} of the track. * @return The {@link C.FormatSupport} of the track.
*/ */
public @C.FormatSupport int getTrackSupport(int trackIndex) { public @C.FormatSupport int getTrackSupport(int trackIndex) {
@ -89,7 +111,7 @@ public final class TracksInfo implements Bundleable {
* Returns whether a specified track is supported for playback, without exceeding the advertised * Returns whether a specified track is supported for playback, without exceeding the advertised
* capabilities of the device. Equivalent to {@code isTrackSupported(trackIndex, false)}. * capabilities of the device. Equivalent to {@code isTrackSupported(trackIndex, false)}.
* *
* @param trackIndex The index of the track in the {@link TrackGroup}. * @param trackIndex The index of the track in the group.
* @return True if the track's format can be played, false otherwise. * @return True if the track's format can be played, false otherwise.
*/ */
public boolean isTrackSupported(int trackIndex) { public boolean isTrackSupported(int trackIndex) {
@ -99,7 +121,7 @@ public final class TracksInfo implements Bundleable {
/** /**
* Returns whether a specified track is supported for playback. * Returns whether a specified track is supported for playback.
* *
* @param trackIndex The index of the track in the {@link TrackGroup}. * @param trackIndex The index of the track in the group.
* @param allowExceedsCapabilities Whether to consider the track as supported if it has a * @param allowExceedsCapabilities Whether to consider the track as supported if it has a
* supported {@link Format#sampleMimeType MIME type}, but otherwise exceeds the advertised * supported {@link Format#sampleMimeType MIME type}, but otherwise exceeds the advertised
* capabilities of the device. For example, a video track for which there's a corresponding * capabilities of the device. For example, a video track for which there's a corresponding
@ -118,6 +140,11 @@ public final class TracksInfo implements Bundleable {
return Booleans.contains(trackSelected, true); return Booleans.contains(trackSelected, true);
} }
/** Returns whether adaptive selections containing more than one track are supported. */
public boolean isAdaptiveSupported() {
return adaptiveSupported;
}
/** /**
* Returns whether at least one track in the group is supported for playback, without exceeding * Returns whether at least one track in the group is supported for playback, without exceeding
* the advertised capabilities of the device. Equivalent to {@code isSupported(false)}. * the advertised capabilities of the device. Equivalent to {@code isSupported(false)}.
@ -155,7 +182,7 @@ public final class TracksInfo implements Bundleable {
* playing, however some player implementations have ways of getting such information. For * playing, however some player implementations have ways of getting such information. For
* example, ExoPlayer provides this information via {@code ExoTrackSelection.getSelectedFormat}. * example, ExoPlayer provides this information via {@code ExoTrackSelection.getSelectedFormat}.
* *
* @param trackIndex The index of the track in the {@link TrackGroup}. * @param trackIndex The index of the track in the group.
* @return True if the track is selected, false otherwise. * @return True if the track is selected, false otherwise.
*/ */
public boolean isTrackSelected(int trackIndex) { public boolean isTrackSelected(int trackIndex) {
@ -163,8 +190,8 @@ public final class TracksInfo implements Bundleable {
} }
/** Returns the {@link C.TrackType} of the group. */ /** Returns the {@link C.TrackType} of the group. */
public @C.TrackType int getTrackType() { public @C.TrackType int getType() {
return trackType; return mediaTrackGroup.type;
} }
@Override @Override
@ -175,18 +202,18 @@ public final class TracksInfo implements Bundleable {
if (other == null || getClass() != other.getClass()) { if (other == null || getClass() != other.getClass()) {
return false; return false;
} }
TrackGroupInfo that = (TrackGroupInfo) other; Group that = (Group) other;
return trackType == that.trackType return adaptiveSupported == that.adaptiveSupported
&& trackGroup.equals(that.trackGroup) && mediaTrackGroup.equals(that.mediaTrackGroup)
&& Arrays.equals(trackSupport, that.trackSupport) && Arrays.equals(trackSupport, that.trackSupport)
&& Arrays.equals(trackSelected, that.trackSelected); && Arrays.equals(trackSelected, that.trackSelected);
} }
@Override @Override
public int hashCode() { public int hashCode() {
int result = trackGroup.hashCode(); int result = mediaTrackGroup.hashCode();
result = 31 * result + (adaptiveSupported ? 1 : 0);
result = 31 * result + Arrays.hashCode(trackSupport); result = 31 * result + Arrays.hashCode(trackSupport);
result = 31 * result + trackType;
result = 31 * result + Arrays.hashCode(trackSelected); result = 31 * result + Arrays.hashCode(trackSelected);
return result; return result;
} }
@ -198,43 +225,43 @@ public final class TracksInfo implements Bundleable {
@IntDef({ @IntDef({
FIELD_TRACK_GROUP, FIELD_TRACK_GROUP,
FIELD_TRACK_SUPPORT, FIELD_TRACK_SUPPORT,
FIELD_TRACK_TYPE,
FIELD_TRACK_SELECTED, FIELD_TRACK_SELECTED,
FIELD_ADAPTIVE_SUPPORTED,
}) })
private @interface FieldNumber {} private @interface FieldNumber {}
private static final int FIELD_TRACK_GROUP = 0; private static final int FIELD_TRACK_GROUP = 0;
private static final int FIELD_TRACK_SUPPORT = 1; private static final int FIELD_TRACK_SUPPORT = 1;
private static final int FIELD_TRACK_TYPE = 2;
private static final int FIELD_TRACK_SELECTED = 3; private static final int FIELD_TRACK_SELECTED = 3;
private static final int FIELD_ADAPTIVE_SUPPORTED = 4;
@Override @Override
public Bundle toBundle() { public Bundle toBundle() {
Bundle bundle = new Bundle(); Bundle bundle = new Bundle();
bundle.putBundle(keyForField(FIELD_TRACK_GROUP), trackGroup.toBundle()); bundle.putBundle(keyForField(FIELD_TRACK_GROUP), mediaTrackGroup.toBundle());
bundle.putIntArray(keyForField(FIELD_TRACK_SUPPORT), trackSupport); bundle.putIntArray(keyForField(FIELD_TRACK_SUPPORT), trackSupport);
bundle.putInt(keyForField(FIELD_TRACK_TYPE), trackType);
bundle.putBooleanArray(keyForField(FIELD_TRACK_SELECTED), trackSelected); bundle.putBooleanArray(keyForField(FIELD_TRACK_SELECTED), trackSelected);
bundle.putBoolean(keyForField(FIELD_ADAPTIVE_SUPPORTED), adaptiveSupported);
return bundle; return bundle;
} }
/** Object that can restores a {@code TracksInfo} from a {@link Bundle}. */ /** Object that can restore a group of tracks from a {@link Bundle}. */
public static final Creator<TrackGroupInfo> CREATOR = public static final Creator<Group> CREATOR =
bundle -> { bundle -> {
// Can't create a Tracks.Group without a TrackGroup
TrackGroup trackGroup = TrackGroup trackGroup =
fromNullableBundle( TrackGroup.CREATOR.fromBundle(
TrackGroup.CREATOR, bundle.getBundle(keyForField(FIELD_TRACK_GROUP))); checkNotNull(bundle.getBundle(keyForField(FIELD_TRACK_GROUP))));
checkNotNull(trackGroup); // Can't create a trackGroup info without a trackGroup
final @C.FormatSupport int[] trackSupport = final @C.FormatSupport int[] trackSupport =
MoreObjects.firstNonNull( MoreObjects.firstNonNull(
bundle.getIntArray(keyForField(FIELD_TRACK_SUPPORT)), new int[trackGroup.length]); bundle.getIntArray(keyForField(FIELD_TRACK_SUPPORT)), new int[trackGroup.length]);
@C.TrackType
int trackType = bundle.getInt(keyForField(FIELD_TRACK_TYPE), C.TRACK_TYPE_UNKNOWN);
boolean[] selected = boolean[] selected =
MoreObjects.firstNonNull( MoreObjects.firstNonNull(
bundle.getBooleanArray(keyForField(FIELD_TRACK_SELECTED)), bundle.getBooleanArray(keyForField(FIELD_TRACK_SELECTED)),
new boolean[trackGroup.length]); new boolean[trackGroup.length]);
return new TrackGroupInfo(trackGroup, trackSupport, trackType, selected); boolean adaptiveSupported =
bundle.getBoolean(keyForField(FIELD_ADAPTIVE_SUPPORTED), false);
return new Group(trackGroup, adaptiveSupported, trackSupport, selected);
}; };
private static String keyForField(@FieldNumber int field) { private static String keyForField(@FieldNumber int field) {
@ -242,38 +269,51 @@ public final class TracksInfo implements Bundleable {
} }
} }
private final ImmutableList<TrackGroupInfo> trackGroupInfos; /** Empty tracks. */
public static final Tracks EMPTY = new Tracks(ImmutableList.of());
/** An {@code TrackInfo} that contains no tracks. */ private final ImmutableList<Group> groups;
public static final TracksInfo EMPTY = new TracksInfo(ImmutableList.of());
/** /**
* Constructs an instance. * Constructs an instance.
* *
* @param trackGroupInfos The {@link TrackGroupInfo TrackGroupInfos} describing the groups of * @param groups The {@link Group groups} of tracks.
* tracks.
*/ */
public TracksInfo(List<TrackGroupInfo> trackGroupInfos) { public Tracks(List<Group> groups) {
this.trackGroupInfos = ImmutableList.copyOf(trackGroupInfos); this.groups = ImmutableList.copyOf(groups);
} }
/** Returns the {@link TrackGroupInfo TrackGroupInfos} describing the groups of tracks. */ /** Returns the {@link Group groups} of tracks. */
public ImmutableList<TrackGroupInfo> getTrackGroupInfos() { public ImmutableList<Group> getGroups() {
return trackGroupInfos; return groups;
}
/** Returns {@code true} if there are no tracks, and {@code false} otherwise. */
public boolean isEmpty() {
return groups.isEmpty();
}
/** Returns true if there are tracks of type {@code trackType}, and false otherwise. */
public boolean containsType(@C.TrackType int trackType) {
for (int i = 0; i < groups.size(); i++) {
if (groups.get(i).getType() == trackType) {
return true;
}
}
return false;
} }
/** /**
* Returns true if at least one track of type {@code trackType} is {@link * Returns true if at least one track of type {@code trackType} is {@link
* TrackGroupInfo#isTrackSupported(int) supported} or if there are no tracks of this type. * Group#isTrackSupported(int) supported}.
*/ */
public boolean isTypeSupportedOrEmpty(@C.TrackType int trackType) { public boolean isTypeSupported(@C.TrackType int trackType) {
return isTypeSupportedOrEmpty(trackType, /* allowExceedsCapabilities= */ false); return isTypeSupported(trackType, /* allowExceedsCapabilities= */ false);
} }
/** /**
* Returns true if at least one track of type {@code trackType} is {@link * Returns true if at least one track of type {@code trackType} is {@link
* TrackGroupInfo#isTrackSupported(int, boolean) supported} or if there are no tracks of this * Group#isTrackSupported(int, boolean) supported}.
* type.
* *
* @param allowExceedsCapabilities Whether to consider the track as supported if it has a * @param allowExceedsCapabilities Whether to consider the track as supported if it has a
* supported {@link Format#sampleMimeType MIME type}, but otherwise exceeds the advertised * supported {@link Format#sampleMimeType MIME type}, but otherwise exceeds the advertised
@ -281,26 +321,40 @@ public final class TracksInfo implements Bundleable {
* decoder whose maximum advertised resolution is exceeded by the resolution of the track. * decoder whose maximum advertised resolution is exceeded by the resolution of the track.
* Such tracks may be playable in some cases. * Such tracks may be playable in some cases.
*/ */
public boolean isTypeSupportedOrEmpty( public boolean isTypeSupported(@C.TrackType int trackType, boolean allowExceedsCapabilities) {
@C.TrackType int trackType, boolean allowExceedsCapabilities) { for (int i = 0; i < groups.size(); i++) {
boolean supported = true; if (groups.get(i).getType() == trackType) {
for (int i = 0; i < trackGroupInfos.size(); i++) { if (groups.get(i).isSupported(allowExceedsCapabilities)) {
if (trackGroupInfos.get(i).trackType == trackType) {
if (trackGroupInfos.get(i).isSupported(allowExceedsCapabilities)) {
return true; return true;
} else {
supported = false;
} }
} }
} }
return supported; return false;
}
/**
* @deprecated Use {@link #containsType(int)} and {@link #isTypeSupported(int)}.
*/
@Deprecated
@SuppressWarnings("deprecation")
public boolean isTypeSupportedOrEmpty(@C.TrackType int trackType) {
return isTypeSupportedOrEmpty(trackType, /* allowExceedsCapabilities= */ false);
}
/**
* @deprecated Use {@link #containsType(int)} and {@link #isTypeSupported(int, boolean)}.
*/
@Deprecated
public boolean isTypeSupportedOrEmpty(
@C.TrackType int trackType, boolean allowExceedsCapabilities) {
return !containsType(trackType) || isTypeSupported(trackType, allowExceedsCapabilities);
} }
/** Returns true if at least one track of the type {@code trackType} is selected for playback. */ /** Returns true if at least one track of the type {@code trackType} is selected for playback. */
public boolean isTypeSelected(@C.TrackType int trackType) { public boolean isTypeSelected(@C.TrackType int trackType) {
for (int i = 0; i < trackGroupInfos.size(); i++) { for (int i = 0; i < groups.size(); i++) {
TrackGroupInfo trackGroupInfo = trackGroupInfos.get(i); Group group = groups.get(i);
if (trackGroupInfo.isSelected() && trackGroupInfo.getTrackType() == trackType) { if (group.isSelected() && group.getType() == trackType) {
return true; return true;
} }
} }
@ -315,13 +369,13 @@ public final class TracksInfo implements Bundleable {
if (other == null || getClass() != other.getClass()) { if (other == null || getClass() != other.getClass()) {
return false; return false;
} }
TracksInfo that = (TracksInfo) other; Tracks that = (Tracks) other;
return trackGroupInfos.equals(that.trackGroupInfos); return groups.equals(that.groups);
} }
@Override @Override
public int hashCode() { public int hashCode() {
return trackGroupInfos.hashCode(); return groups.hashCode();
} }
// Bundleable implementation. // Bundleable implementation.
@ -329,29 +383,29 @@ public final class TracksInfo implements Bundleable {
@Retention(RetentionPolicy.SOURCE) @Retention(RetentionPolicy.SOURCE)
@Target(TYPE_USE) @Target(TYPE_USE)
@IntDef({ @IntDef({
FIELD_TRACK_GROUP_INFOS, FIELD_TRACK_GROUPS,
}) })
private @interface FieldNumber {} private @interface FieldNumber {}
private static final int FIELD_TRACK_GROUP_INFOS = 0; private static final int FIELD_TRACK_GROUPS = 0;
@Override @Override
public Bundle toBundle() { public Bundle toBundle() {
Bundle bundle = new Bundle(); Bundle bundle = new Bundle();
bundle.putParcelableArrayList( bundle.putParcelableArrayList(keyForField(FIELD_TRACK_GROUPS), toBundleArrayList(groups));
keyForField(FIELD_TRACK_GROUP_INFOS), toBundleArrayList(trackGroupInfos));
return bundle; return bundle;
} }
/** Object that can restore a {@code TracksInfo} from a {@link Bundle}. */ /** Object that can restore tracks from a {@link Bundle}. */
public static final Creator<TracksInfo> CREATOR = public static final Creator<Tracks> CREATOR =
bundle -> { bundle -> {
List<TrackGroupInfo> trackGroupInfos = @Nullable
fromBundleNullableList( List<Bundle> groupBundles = bundle.getParcelableArrayList(keyForField(FIELD_TRACK_GROUPS));
TrackGroupInfo.CREATOR, List<Group> groups =
bundle.getParcelableArrayList(keyForField(FIELD_TRACK_GROUP_INFOS)), groupBundles == null
/* defaultValue= */ ImmutableList.of()); ? ImmutableList.of()
return new TracksInfo(trackGroupInfos); : BundleableUtil.fromBundleList(Group.CREATOR, groupBundles);
return new Tracks(groups);
}; };
private static String keyForField(@FieldNumber int field) { private static String keyForField(@FieldNumber int field) {

View file

@ -29,7 +29,6 @@ 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.lang.annotation.Target; import java.lang.annotation.Target;
import java.lang.reflect.Method;
/** /**
* Attributes for audio playback, which configure the underlying platform {@link * Attributes for audio playback, which configure the underlying platform {@link
@ -44,10 +43,31 @@ import java.lang.reflect.Method;
*/ */
public final class AudioAttributes implements Bundleable { public final class AudioAttributes implements Bundleable {
/** A direct wrapper around {@link android.media.AudioAttributes}. */
@RequiresApi(21)
public static final class AudioAttributesV21 {
public final android.media.AudioAttributes audioAttributes;
private AudioAttributesV21(AudioAttributes audioAttributes) {
android.media.AudioAttributes.Builder builder =
new android.media.AudioAttributes.Builder()
.setContentType(audioAttributes.contentType)
.setFlags(audioAttributes.flags)
.setUsage(audioAttributes.usage);
if (Util.SDK_INT >= 29) {
Api29.setAllowedCapturePolicy(builder, audioAttributes.allowedCapturePolicy);
}
if (Util.SDK_INT >= 32) {
Api32.setSpatializationBehavior(builder, audioAttributes.spatializationBehavior);
}
this.audioAttributes = builder.build();
}
}
/** /**
* The default audio attributes, where the content type is {@link C#CONTENT_TYPE_UNKNOWN}, usage * The default audio attributes, where the content type is {@link C#AUDIO_CONTENT_TYPE_UNKNOWN},
* is {@link C#USAGE_MEDIA}, capture policy is {@link C#ALLOW_CAPTURE_BY_ALL} and no flags are * usage is {@link C#USAGE_MEDIA}, capture policy is {@link C#ALLOW_CAPTURE_BY_ALL} and no flags
* set. * are set.
*/ */
public static final AudioAttributes DEFAULT = new Builder().build(); public static final AudioAttributes DEFAULT = new Builder().build();
@ -63,11 +83,11 @@ public final class AudioAttributes implements Bundleable {
/** /**
* Creates a new builder for {@link AudioAttributes}. * Creates a new builder for {@link AudioAttributes}.
* *
* <p>By default the content type is {@link C#CONTENT_TYPE_UNKNOWN}, usage is {@link * <p>By default the content type is {@link C#AUDIO_CONTENT_TYPE_UNKNOWN}, usage is {@link
* C#USAGE_MEDIA}, capture policy is {@link C#ALLOW_CAPTURE_BY_ALL} and no flags are set. * C#USAGE_MEDIA}, capture policy is {@link C#ALLOW_CAPTURE_BY_ALL} and no flags are set.
*/ */
public Builder() { public Builder() {
contentType = C.CONTENT_TYPE_UNKNOWN; contentType = C.AUDIO_CONTENT_TYPE_UNKNOWN;
flags = 0; flags = 0;
usage = C.USAGE_MEDIA; usage = C.USAGE_MEDIA;
allowedCapturePolicy = C.ALLOW_CAPTURE_BY_ALL; allowedCapturePolicy = C.ALLOW_CAPTURE_BY_ALL;
@ -98,9 +118,7 @@ public final class AudioAttributes implements Bundleable {
return this; return this;
} }
// TODO[b/190759307] Update javadoc to link to AudioAttributes.Builder#setSpatializationBehavior /** See {@link android.media.AudioAttributes.Builder#setSpatializationBehavior(int)}. */
// once compile SDK target is set to 32.
/** See {@code android.media.AudioAttributes.Builder.setSpatializationBehavior(int)}. */
public Builder setSpatializationBehavior(@C.SpatializationBehavior int spatializationBehavior) { public Builder setSpatializationBehavior(@C.SpatializationBehavior int spatializationBehavior) {
this.spatializationBehavior = spatializationBehavior; this.spatializationBehavior = spatializationBehavior;
return this; return this;
@ -124,7 +142,7 @@ public final class AudioAttributes implements Bundleable {
/** The {@link C.SpatializationBehavior}. */ /** The {@link C.SpatializationBehavior}. */
public final @C.SpatializationBehavior int spatializationBehavior; public final @C.SpatializationBehavior int spatializationBehavior;
@Nullable private android.media.AudioAttributes audioAttributesV21; @Nullable private AudioAttributesV21 audioAttributesV21;
private AudioAttributes( private AudioAttributes(
@C.AudioContentType int contentType, @C.AudioContentType int contentType,
@ -140,25 +158,15 @@ public final class AudioAttributes implements Bundleable {
} }
/** /**
* Returns a {@link android.media.AudioAttributes} from this instance. * Returns a {@link AudioAttributesV21} from this instance.
* *
* <p>Field {@link AudioAttributes#allowedCapturePolicy} is ignored for API levels prior to 29. * <p>Some fields are ignored if the corresponding {@link android.media.AudioAttributes.Builder}
* setter is not available on the current API level.
*/ */
@RequiresApi(21) @RequiresApi(21)
public android.media.AudioAttributes getAudioAttributesV21() { public AudioAttributesV21 getAudioAttributesV21() {
if (audioAttributesV21 == null) { if (audioAttributesV21 == null) {
android.media.AudioAttributes.Builder builder = audioAttributesV21 = new AudioAttributesV21(this);
new android.media.AudioAttributes.Builder()
.setContentType(contentType)
.setFlags(flags)
.setUsage(usage);
if (Util.SDK_INT >= 29) {
Api29.setAllowedCapturePolicy(builder, allowedCapturePolicy);
}
if (Util.SDK_INT >= 32) {
Api32.setSpatializationBehavior(builder, spatializationBehavior);
}
audioAttributesV21 = builder.build();
} }
return audioAttributesV21; return audioAttributesV21;
} }
@ -250,8 +258,6 @@ public final class AudioAttributes implements Bundleable {
@RequiresApi(29) @RequiresApi(29)
private static final class Api29 { private static final class Api29 {
private Api29() {}
@DoNotInline @DoNotInline
public static void setAllowedCapturePolicy( public static void setAllowedCapturePolicy(
android.media.AudioAttributes.Builder builder, android.media.AudioAttributes.Builder builder,
@ -262,20 +268,11 @@ public final class AudioAttributes implements Bundleable {
@RequiresApi(32) @RequiresApi(32)
private static final class Api32 { private static final class Api32 {
private Api32() {}
@DoNotInline @DoNotInline
public static void setSpatializationBehavior( public static void setSpatializationBehavior(
android.media.AudioAttributes.Builder builder, android.media.AudioAttributes.Builder builder,
@C.SpatializationBehavior int spatializationBehavior) { @C.SpatializationBehavior int spatializationBehavior) {
try { builder.setSpatializationBehavior(spatializationBehavior);
// TODO[b/190759307]: Remove reflection once compile SDK target is set to 32.
Method setSpatializationBehavior =
builder.getClass().getMethod("setSpatializationBehavior", Integer.TYPE);
setSpatializationBehavior.invoke(builder, spatializationBehavior);
} catch (Exception e) {
// Do nothing if reflection fails.
}
} }
} }
} }

View file

@ -91,7 +91,9 @@ public final class DrmInitData implements Comparator<SchemeData>, Parcelable {
/** Number of {@link SchemeData}s. */ /** Number of {@link SchemeData}s. */
public final int schemeDataCount; public final int schemeDataCount;
/** @param schemeDatas Scheme initialization data for possibly multiple DRM schemes. */ /**
* @param schemeDatas Scheme initialization data for possibly multiple DRM schemes.
*/
public DrmInitData(List<SchemeData> schemeDatas) { public DrmInitData(List<SchemeData> schemeDatas) {
this(null, false, schemeDatas.toArray(new SchemeData[0])); this(null, false, schemeDatas.toArray(new SchemeData[0]));
} }
@ -104,7 +106,9 @@ public final class DrmInitData implements Comparator<SchemeData>, Parcelable {
this(schemeType, false, schemeDatas.toArray(new SchemeData[0])); this(schemeType, false, schemeDatas.toArray(new SchemeData[0]));
} }
/** @param schemeDatas Scheme initialization data for possibly multiple DRM schemes. */ /**
* @param schemeDatas Scheme initialization data for possibly multiple DRM schemes.
*/
public DrmInitData(SchemeData... schemeDatas) { public DrmInitData(SchemeData... schemeDatas) {
this(null, schemeDatas); this(null, schemeDatas);
} }

View file

@ -62,12 +62,16 @@ public final class Metadata implements Parcelable {
private final Entry[] entries; private final Entry[] entries;
/** @param entries The metadata entries. */ /**
* @param entries The metadata entries.
*/
public Metadata(Entry... entries) { public Metadata(Entry... entries) {
this.entries = entries; this.entries = entries;
} }
/** @param entries The metadata entries. */ /**
* @param entries The metadata entries.
*/
public Metadata(List<? extends Entry> entries) { public Metadata(List<? extends Entry> entries) {
this.entries = entries.toArray(new Entry[0]); this.entries = entries.toArray(new Entry[0]);
} }

View file

@ -42,7 +42,9 @@ public final class StreamKey implements Comparable<StreamKey>, Parcelable {
/** The stream index. */ /** The stream index. */
public final int streamIndex; public final int streamIndex;
/** @deprecated Use {@link #streamIndex}. */ /**
* @deprecated Use {@link #streamIndex}.
*/
@Deprecated public final int trackIndex; @Deprecated public final int trackIndex;
/** /**

View file

@ -25,8 +25,10 @@ import androidx.annotation.Nullable;
import com.google.android.exoplayer2.Bundleable; import com.google.android.exoplayer2.Bundleable;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.Tracks;
import com.google.android.exoplayer2.util.BundleableUtil; import com.google.android.exoplayer2.util.BundleableUtil;
import com.google.android.exoplayer2.util.Log; import com.google.android.exoplayer2.util.Log;
import com.google.android.exoplayer2.util.MimeTypes;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import java.lang.annotation.Documented; import java.lang.annotation.Documented;
@ -36,7 +38,25 @@ import java.lang.annotation.Target;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
/** Defines an immutable group of tracks identified by their format identity. */ /**
* An immutable group of tracks available within a media stream. All tracks in a group present the
* same content, but their formats may differ.
*
* <p>As an example of how tracks can be grouped, consider an adaptive playback where a main video
* feed is provided in five resolutions, and an alternative video feed (e.g., a different camera
* angle in a sports match) is provided in two resolutions. In this case there will be two video
* track groups, one corresponding to the main video feed containing five tracks, and a second for
* the alternative video feed containing two tracks.
*
* <p>Note that audio tracks whose languages differ are not grouped, because content in different
* languages is not considered to be the same. Conversely, audio tracks in the same language that
* only differ in properties such as bitrate, sampling rate, channel count and so on can be grouped.
* This also applies to text tracks.
*
* <p>Note also that this class only contains information derived from the media itself. Unlike
* {@link Tracks.Group}, it does not include runtime information such as the extent to which
* playback of each track is supported by the device, or which tracks are currently selected.
*/
public final class TrackGroup implements Bundleable { public final class TrackGroup implements Bundleable {
private static final String TAG = "TrackGroup"; private static final String TAG = "TrackGroup";
@ -45,6 +65,8 @@ public final class TrackGroup implements Bundleable {
public final int length; public final int length;
/** An identifier for the track group. */ /** An identifier for the track group. */
public final String id; public final String id;
/** The type of tracks in the group. */
public final @C.TrackType int type;
private final Format[] formats; private final Format[] formats;
@ -71,6 +93,11 @@ public final class TrackGroup implements Bundleable {
this.id = id; this.id = id;
this.formats = formats; this.formats = formats;
this.length = formats.length; this.length = formats.length;
@C.TrackType int type = MimeTypes.getTrackType(formats[0].sampleMimeType);
if (type == C.TRACK_TYPE_UNKNOWN) {
type = MimeTypes.getTrackType(formats[0].containerMimeType);
}
this.type = type;
verifyCorrectness(); verifyCorrectness();
} }
@ -133,7 +160,7 @@ public final class TrackGroup implements Bundleable {
return false; return false;
} }
TrackGroup other = (TrackGroup) obj; TrackGroup other = (TrackGroup) obj;
return length == other.length && id.equals(other.id) && Arrays.equals(formats, other.formats); return id.equals(other.id) && Arrays.equals(formats, other.formats);
} }
// Bundleable implementation. // Bundleable implementation.
@ -159,11 +186,12 @@ public final class TrackGroup implements Bundleable {
/** Object that can restore {@code TrackGroup} from a {@link Bundle}. */ /** Object that can restore {@code TrackGroup} from a {@link Bundle}. */
public static final Creator<TrackGroup> CREATOR = public static final Creator<TrackGroup> CREATOR =
bundle -> { bundle -> {
@Nullable
List<Bundle> formatBundles = bundle.getParcelableArrayList(keyForField(FIELD_FORMATS));
List<Format> formats = List<Format> formats =
BundleableUtil.fromBundleNullableList( formatBundles == null
Format.CREATOR, ? ImmutableList.of()
bundle.getParcelableArrayList(keyForField(FIELD_FORMATS)), : BundleableUtil.fromBundleList(Format.CREATOR, formatBundles);
ImmutableList.of());
String id = bundle.getString(keyForField(FIELD_ID), /* defaultValue= */ ""); String id = bundle.getString(keyForField(FIELD_ID), /* defaultValue= */ "");
return new TrackGroup(id, formats.toArray(new Format[0])); return new TrackGroup(id, formats.toArray(new Format[0]));
}; };

View file

@ -828,6 +828,36 @@ public final class AdPlaybackState implements Bundleable {
adsId, adGroups, adResumePositionUs, contentDurationUs, removedAdGroupCount); adsId, adGroups, adResumePositionUs, contentDurationUs, removedAdGroupCount);
} }
/**
* Returns a copy of the ad playback state with the given ads ID.
*
* @param adsId The new ads ID.
* @param adPlaybackState The ad playback state to copy.
* @return The new ad playback state.
*/
public static AdPlaybackState fromAdPlaybackState(Object adsId, AdPlaybackState adPlaybackState) {
AdGroup[] adGroups =
new AdGroup[adPlaybackState.adGroupCount - adPlaybackState.removedAdGroupCount];
for (int i = 0; i < adGroups.length; i++) {
AdGroup adGroup = adPlaybackState.adGroups[i];
adGroups[i] =
new AdGroup(
adGroup.timeUs,
adGroup.count,
Arrays.copyOf(adGroup.states, adGroup.states.length),
Arrays.copyOf(adGroup.uris, adGroup.uris.length),
Arrays.copyOf(adGroup.durationsUs, adGroup.durationsUs.length),
adGroup.contentResumeOffsetUs,
adGroup.isServerSideInserted);
}
return new AdPlaybackState(
adsId,
adGroups,
adPlaybackState.adResumePositionUs,
adPlaybackState.contentDurationUs,
adPlaybackState.removedAdGroupCount);
}
@Override @Override
public boolean equals(@Nullable Object o) { public boolean equals(@Nullable Object o) {
if (this == o) { if (this == o) {

View file

@ -0,0 +1,102 @@
/*
* Copyright (C) 2022 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.text;
import static java.lang.annotation.ElementType.TYPE_USE;
import android.graphics.Bitmap;
import android.os.Bundle;
import androidx.annotation.IntDef;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.Bundleable;
import com.google.android.exoplayer2.util.BundleableUtil;
import com.google.common.collect.ImmutableList;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.ArrayList;
import java.util.List;
/** Class to represent the state of active {@link Cue Cues} at a particular time. */
public final class CueGroup implements Bundleable {
/** Empty {@link CueGroup}. */
public static final CueGroup EMPTY = new CueGroup(ImmutableList.of());
/**
* The cues in this group.
*
* <p>This list is in ascending order of priority. If any of the cue boxes overlap when displayed,
* the {@link Cue} nearer the end of the list should be shown on top.
*
* <p>This list may be empty if the group represents a state with no cues.
*/
public final ImmutableList<Cue> cues;
/** Creates a CueGroup. */
public CueGroup(List<Cue> cues) {
this.cues = ImmutableList.copyOf(cues);
}
// Bundleable implementation.
@Documented
@Retention(RetentionPolicy.SOURCE)
@Target(TYPE_USE)
@IntDef({FIELD_CUES})
private @interface FieldNumber {}
private static final int FIELD_CUES = 0;
@Override
public Bundle toBundle() {
Bundle bundle = new Bundle();
bundle.putParcelableArrayList(
keyForField(FIELD_CUES), BundleableUtil.toBundleArrayList(filterOutBitmapCues(cues)));
return bundle;
}
public static final Creator<CueGroup> CREATOR = CueGroup::fromBundle;
private static final CueGroup fromBundle(Bundle bundle) {
@Nullable ArrayList<Bundle> cueBundles = bundle.getParcelableArrayList(keyForField(FIELD_CUES));
List<Cue> cues =
cueBundles == null
? ImmutableList.of()
: BundleableUtil.fromBundleList(Cue.CREATOR, cueBundles);
return new CueGroup(cues);
}
private static String keyForField(@FieldNumber int field) {
return Integer.toString(field, Character.MAX_RADIX);
}
/**
* Filters out {@link Cue} objects containing {@link Bitmap}. It is used when transferring cues
* between processes to prevent transferring too much data.
*/
private static ImmutableList<Cue> filterOutBitmapCues(List<Cue> cues) {
ImmutableList.Builder<Cue> builder = ImmutableList.builder();
for (int i = 0; i < cues.size(); i++) {
if (cues.get(i).bitmap != null) {
continue;
}
builder.add(cues.get(i));
}
return builder.build();
}
}

View file

@ -0,0 +1,140 @@
/*
* Copyright (C) 2021 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.trackselection;
import static com.google.android.exoplayer2.util.Assertions.checkNotNull;
import static java.util.Collections.max;
import static java.util.Collections.min;
import android.os.Bundle;
import androidx.annotation.IntDef;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.Bundleable;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.source.TrackGroup;
import com.google.common.collect.ImmutableList;
import com.google.common.primitives.Ints;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.List;
/**
* A track selection override, consisting of a {@link TrackGroup} and the indices of the tracks
* within the group that should be selected.
*
* <p>A track selection override is applied during playback if the media being played contains a
* {@link TrackGroup} equal to the one in the override. If a {@link TrackSelectionParameters}
* contains only one override of a given track type that applies to the media, this override will be
* used to control the track selection for that type. If multiple overrides of a given track type
* apply then the player will apply only one of them.
*
* <p>If {@link #trackIndices} is empty then the override specifies that no tracks should be
* selected. Adding an empty override to a {@link TrackSelectionParameters} is similar to {@link
* TrackSelectionParameters.Builder#setTrackTypeDisabled disabling a track type}, except that an
* empty override will only be applied if the media being played contains a {@link TrackGroup} equal
* to the one in the override. Conversely, disabling a track type will prevent selection of tracks
* of that type for all media.
*/
public final class TrackSelectionOverride implements Bundleable {
/** The media {@link TrackGroup} whose {@link #trackIndices} are forced to be selected. */
public final TrackGroup mediaTrackGroup;
/** The indices of tracks in a {@link TrackGroup} to be selected. */
public final ImmutableList<Integer> trackIndices;
@Documented
@Retention(RetentionPolicy.SOURCE)
@IntDef({
FIELD_TRACK_GROUP,
FIELD_TRACKS,
})
private @interface FieldNumber {}
private static final int FIELD_TRACK_GROUP = 0;
private static final int FIELD_TRACKS = 1;
/**
* Constructs an instance to force {@code trackIndex} in {@code trackGroup} to be selected.
*
* @param mediaTrackGroup The media {@link TrackGroup} for which to override the track selection.
* @param trackIndex The index of the track in the {@link TrackGroup} to select.
*/
public TrackSelectionOverride(TrackGroup mediaTrackGroup, int trackIndex) {
this(mediaTrackGroup, ImmutableList.of(trackIndex));
}
/**
* Constructs an instance to force {@code trackIndices} in {@code trackGroup} to be selected.
*
* @param mediaTrackGroup The media {@link TrackGroup} for which to override the track selection.
* @param trackIndices The indices of the tracks in the {@link TrackGroup} to select.
*/
public TrackSelectionOverride(TrackGroup mediaTrackGroup, List<Integer> trackIndices) {
if (!trackIndices.isEmpty()) {
if (min(trackIndices) < 0 || max(trackIndices) >= mediaTrackGroup.length) {
throw new IndexOutOfBoundsException();
}
}
this.mediaTrackGroup = mediaTrackGroup;
this.trackIndices = ImmutableList.copyOf(trackIndices);
}
/** Returns the {@link C.TrackType} of the overridden track group. */
public @C.TrackType int getType() {
return mediaTrackGroup.type;
}
@Override
public boolean equals(@Nullable Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
TrackSelectionOverride that = (TrackSelectionOverride) obj;
return mediaTrackGroup.equals(that.mediaTrackGroup) && trackIndices.equals(that.trackIndices);
}
@Override
public int hashCode() {
return mediaTrackGroup.hashCode() + 31 * trackIndices.hashCode();
}
// Bundleable implementation
@Override
public Bundle toBundle() {
Bundle bundle = new Bundle();
bundle.putBundle(keyForField(FIELD_TRACK_GROUP), mediaTrackGroup.toBundle());
bundle.putIntArray(keyForField(FIELD_TRACKS), Ints.toArray(trackIndices));
return bundle;
}
/** Object that can restore {@code TrackSelectionOverride} from a {@link Bundle}. */
public static final Creator<TrackSelectionOverride> CREATOR =
bundle -> {
Bundle trackGroupBundle = checkNotNull(bundle.getBundle(keyForField(FIELD_TRACK_GROUP)));
TrackGroup mediaTrackGroup = TrackGroup.CREATOR.fromBundle(trackGroupBundle);
int[] tracks = checkNotNull(bundle.getIntArray(keyForField(FIELD_TRACKS)));
return new TrackSelectionOverride(mediaTrackGroup, Ints.asList(tracks));
};
private static String keyForField(@FieldNumber int field) {
return Integer.toString(field, Character.MAX_RADIX);
}
}

View file

@ -1,310 +0,0 @@
/*
* Copyright (C) 2021 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.trackselection;
import static com.google.android.exoplayer2.util.Assertions.checkNotNull;
import static com.google.android.exoplayer2.util.BundleableUtil.fromBundleNullableList;
import static com.google.android.exoplayer2.util.BundleableUtil.toBundleArrayList;
import static java.lang.annotation.ElementType.TYPE_USE;
import static java.util.Collections.max;
import static java.util.Collections.min;
import android.os.Bundle;
import androidx.annotation.IntDef;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.Bundleable;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.MediaItem;
import com.google.android.exoplayer2.source.TrackGroup;
import com.google.android.exoplayer2.util.MimeTypes;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.primitives.Ints;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
/**
* Forces the selection of the specified tracks in {@link TrackGroup TrackGroups}.
*
* <p>Each {@link TrackSelectionOverride override} only affects the selection of tracks of that
* {@link C.TrackType type}. For example overriding the selection of an {@link C#TRACK_TYPE_AUDIO
* audio} {@link TrackGroup} will not affect the selection of {@link C#TRACK_TYPE_VIDEO video} or
* {@link C#TRACK_TYPE_TEXT text} tracks.
*
* <p>If multiple {@link TrackGroup TrackGroups} of the same {@link C.TrackType} are overridden,
* which tracks will be selected depend on the player capabilities. For example, by default {@code
* ExoPlayer} doesn't support selecting more than one {@link TrackGroup} per {@link C.TrackType}.
*
* <p>Overrides of {@link TrackGroup} that are not currently available are ignored. For example,
* when the player transitions to the next {@link MediaItem} in a playlist, any overrides of the
* previous {@link MediaItem} are ignored.
*
* @see TrackSelectionParameters#trackSelectionOverrides
*/
public final class TrackSelectionOverrides implements Bundleable {
/** Builder for {@link TrackSelectionOverrides}. */
public static final class Builder {
// Cannot use ImmutableMap.Builder as it doesn't support removing entries.
private final HashMap<TrackGroup, TrackSelectionOverride> overrides;
/** Creates an builder with no {@link TrackSelectionOverride}. */
public Builder() {
overrides = new HashMap<>();
}
private Builder(Map<TrackGroup, TrackSelectionOverride> overrides) {
this.overrides = new HashMap<>(overrides);
}
/** Adds an override for the provided {@link TrackGroup}. */
public Builder addOverride(TrackSelectionOverride override) {
overrides.put(override.trackGroup, override);
return this;
}
/** Removes the override associated with the provided {@link TrackGroup} if present. */
public Builder clearOverride(TrackGroup trackGroup) {
overrides.remove(trackGroup);
return this;
}
/** Set the override for the type of the provided {@link TrackGroup}. */
public Builder setOverrideForType(TrackSelectionOverride override) {
clearOverridesOfType(override.getTrackType());
overrides.put(override.trackGroup, override);
return this;
}
/**
* Remove any override associated with {@link TrackGroup TrackGroups} of type {@code trackType}.
*/
public Builder clearOverridesOfType(@C.TrackType int trackType) {
for (Iterator<TrackSelectionOverride> it = overrides.values().iterator(); it.hasNext(); ) {
TrackSelectionOverride trackSelectionOverride = it.next();
if (trackSelectionOverride.getTrackType() == trackType) {
it.remove();
}
}
return this;
}
/** Returns a new {@link TrackSelectionOverrides} instance with the current builder values. */
public TrackSelectionOverrides build() {
return new TrackSelectionOverrides(overrides);
}
}
/**
* Forces the selection of {@link #trackIndices} for a {@link TrackGroup}.
*
* <p>If multiple tracks in {@link #trackGroup} are overridden, as many as possible will be
* selected depending on the player capabilities.
*
* <p>If {@link #trackIndices} is empty, no tracks from {@link #trackGroup} will be played. This
* is similar to {@link TrackSelectionParameters#disabledTrackTypes}, except it will only affect
* the playback of the associated {@link TrackGroup}. For example, if the only {@link
* C#TRACK_TYPE_VIDEO} {@link TrackGroup} is associated with no tracks, no video will play until
* the next video starts.
*/
public static final class TrackSelectionOverride implements Bundleable {
/** The {@link TrackGroup} whose {@link #trackIndices} are forced to be selected. */
public final TrackGroup trackGroup;
/** The indices of tracks in a {@link TrackGroup} to be selected. */
public final ImmutableList<Integer> trackIndices;
/** Constructs an instance to force all tracks in {@code trackGroup} to be selected. */
public TrackSelectionOverride(TrackGroup trackGroup) {
this.trackGroup = trackGroup;
ImmutableList.Builder<Integer> builder = new ImmutableList.Builder<>();
for (int i = 0; i < trackGroup.length; i++) {
builder.add(i);
}
this.trackIndices = builder.build();
}
/**
* Constructs an instance to force {@code trackIndices} in {@code trackGroup} to be selected.
*
* @param trackGroup The {@link TrackGroup} for which to override the track selection.
* @param trackIndices The indices of the tracks in the {@link TrackGroup} to select.
*/
public TrackSelectionOverride(TrackGroup trackGroup, List<Integer> trackIndices) {
if (!trackIndices.isEmpty()) {
if (min(trackIndices) < 0 || max(trackIndices) >= trackGroup.length) {
throw new IndexOutOfBoundsException();
}
}
this.trackGroup = trackGroup;
this.trackIndices = ImmutableList.copyOf(trackIndices);
}
@Override
public boolean equals(@Nullable Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
TrackSelectionOverride that = (TrackSelectionOverride) obj;
return trackGroup.equals(that.trackGroup) && trackIndices.equals(that.trackIndices);
}
@Override
public int hashCode() {
return trackGroup.hashCode() + 31 * trackIndices.hashCode();
}
/** Returns the {@link C.TrackType} of the overriden track group. */
public @C.TrackType int getTrackType() {
return MimeTypes.getTrackType(trackGroup.getFormat(0).sampleMimeType);
}
// Bundleable implementation
@Documented
@Retention(RetentionPolicy.SOURCE)
@Target(TYPE_USE)
@IntDef({
FIELD_TRACK_GROUP,
FIELD_TRACKS,
})
private @interface FieldNumber {}
private static final int FIELD_TRACK_GROUP = 0;
private static final int FIELD_TRACKS = 1;
@Override
public Bundle toBundle() {
Bundle bundle = new Bundle();
bundle.putBundle(keyForField(FIELD_TRACK_GROUP), trackGroup.toBundle());
bundle.putIntArray(keyForField(FIELD_TRACKS), Ints.toArray(trackIndices));
return bundle;
}
/** Object that can restore {@code TrackSelectionOverride} from a {@link Bundle}. */
public static final Creator<TrackSelectionOverride> CREATOR =
bundle -> {
@Nullable Bundle trackGroupBundle = bundle.getBundle(keyForField(FIELD_TRACK_GROUP));
checkNotNull(trackGroupBundle); // Mandatory as there are no reasonable defaults.
TrackGroup trackGroup = TrackGroup.CREATOR.fromBundle(trackGroupBundle);
@Nullable int[] tracks = bundle.getIntArray(keyForField(FIELD_TRACKS));
if (tracks == null) {
return new TrackSelectionOverride(trackGroup);
}
return new TrackSelectionOverride(trackGroup, Ints.asList(tracks));
};
private static String keyForField(@FieldNumber int field) {
return Integer.toString(field, Character.MAX_RADIX);
}
}
/** Empty {@code TrackSelectionOverrides}, where no track selection is overridden. */
public static final TrackSelectionOverrides EMPTY =
new TrackSelectionOverrides(ImmutableMap.of());
private final ImmutableMap<TrackGroup, TrackSelectionOverride> overrides;
private TrackSelectionOverrides(Map<TrackGroup, TrackSelectionOverride> overrides) {
this.overrides = ImmutableMap.copyOf(overrides);
}
/** Returns a {@link Builder} initialized with the values of this instance. */
public Builder buildUpon() {
return new Builder(overrides);
}
/** Returns a list of the {@link TrackSelectionOverride overrides}. */
public ImmutableList<TrackSelectionOverride> asList() {
return ImmutableList.copyOf(overrides.values());
}
/**
* Returns the {@link TrackSelectionOverride} of the provided {@link TrackGroup} or {@code null}
* if there is none.
*/
@Nullable
public TrackSelectionOverride getOverride(TrackGroup trackGroup) {
return overrides.get(trackGroup);
}
@Override
public boolean equals(@Nullable Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
TrackSelectionOverrides that = (TrackSelectionOverrides) obj;
return overrides.equals(that.overrides);
}
@Override
public int hashCode() {
return overrides.hashCode();
}
// Bundleable implementation
@Documented
@Retention(RetentionPolicy.SOURCE)
@Target(TYPE_USE)
@IntDef({
FIELD_OVERRIDES,
})
private @interface FieldNumber {}
private static final int FIELD_OVERRIDES = 0;
@Override
public Bundle toBundle() {
Bundle bundle = new Bundle();
bundle.putParcelableArrayList(
keyForField(FIELD_OVERRIDES), toBundleArrayList(overrides.values()));
return bundle;
}
/** Object that can restore {@code TrackSelectionOverrides} from a {@link Bundle}. */
public static final Creator<TrackSelectionOverrides> CREATOR =
bundle -> {
List<TrackSelectionOverride> trackSelectionOverrides =
fromBundleNullableList(
TrackSelectionOverride.CREATOR,
bundle.getParcelableArrayList(keyForField(FIELD_OVERRIDES)),
ImmutableList.of());
ImmutableMap.Builder<TrackGroup, TrackSelectionOverride> builder =
new ImmutableMap.Builder<>();
for (int i = 0; i < trackSelectionOverrides.size(); i++) {
TrackSelectionOverride trackSelectionOverride = trackSelectionOverrides.get(i);
builder.put(trackSelectionOverride.trackGroup, trackSelectionOverride);
}
return new TrackSelectionOverrides(builder.buildOrThrow());
};
private static String keyForField(@FieldNumber int field) {
return Integer.toString(field, Character.MAX_RADIX);
}
}

View file

@ -16,38 +16,41 @@
package com.google.android.exoplayer2.trackselection; package com.google.android.exoplayer2.trackselection;
import static com.google.android.exoplayer2.util.Assertions.checkNotNull; import static com.google.android.exoplayer2.util.Assertions.checkNotNull;
import static com.google.android.exoplayer2.util.BundleableUtil.fromNullableBundle; import static com.google.android.exoplayer2.util.BundleableUtil.toBundleArrayList;
import static com.google.common.base.MoreObjects.firstNonNull; import static com.google.common.base.MoreObjects.firstNonNull;
import static java.lang.annotation.ElementType.TYPE_USE;
import android.content.Context; import android.content.Context;
import android.graphics.Point; import android.graphics.Point;
import android.os.Bundle; import android.os.Bundle;
import android.os.Looper; import android.os.Looper;
import android.view.accessibility.CaptioningManager; import android.view.accessibility.CaptioningManager;
import androidx.annotation.IntDef;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi; import androidx.annotation.RequiresApi;
import com.google.android.exoplayer2.Bundleable; import com.google.android.exoplayer2.Bundleable;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.source.TrackGroup;
import com.google.android.exoplayer2.util.BundleableUtil;
import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.util.Util;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSet;
import com.google.common.primitives.Ints; import com.google.common.primitives.Ints;
import java.lang.annotation.Documented; import java.util.HashMap;
import java.lang.annotation.Retention; import java.util.HashSet;
import java.lang.annotation.RetentionPolicy; import java.util.Iterator;
import java.lang.annotation.Target; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Set; import java.util.Set;
import org.checkerframework.checker.initialization.qual.UnknownInitialization; import org.checkerframework.checker.initialization.qual.UnknownInitialization;
import org.checkerframework.checker.nullness.qual.EnsuresNonNull; import org.checkerframework.checker.nullness.qual.EnsuresNonNull;
/** /**
* Constraint parameters for track selection. * Parameters for controlling track selection.
* *
* <p>For example the following code modifies the parameters to restrict video track selections to * <p>Parameters can be queried and set on a {@link Player}. For example the following code modifies
* SD, and to select a German audio track if there is one: * the parameters to restrict video track selections to SD, and to select a German audio track if
* there is one:
* *
* <pre>{@code * <pre>{@code
* // Build on the current parameters. * // Build on the current parameters.
@ -92,12 +95,13 @@ public class TrackSelectionParameters implements Bundleable {
// Text // Text
private ImmutableList<String> preferredTextLanguages; private ImmutableList<String> preferredTextLanguages;
private @C.RoleFlags int preferredTextRoleFlags; private @C.RoleFlags int preferredTextRoleFlags;
private @C.SelectionFlags int ignoredTextSelectionFlags;
private boolean selectUndeterminedTextLanguage; private boolean selectUndeterminedTextLanguage;
// General // General
private boolean forceLowestBitrate; private boolean forceLowestBitrate;
private boolean forceHighestSupportedBitrate; private boolean forceHighestSupportedBitrate;
private TrackSelectionOverrides trackSelectionOverrides; private HashMap<TrackGroup, TrackSelectionOverride> overrides;
private ImmutableSet<@C.TrackType Integer> disabledTrackTypes; private HashSet<@C.TrackType Integer> disabledTrackTypes;
/** /**
* @deprecated {@link Context} constraints will not be set using this constructor. Use {@link * @deprecated {@link Context} constraints will not be set using this constructor. Use {@link
@ -124,12 +128,13 @@ public class TrackSelectionParameters implements Bundleable {
// Text // Text
preferredTextLanguages = ImmutableList.of(); preferredTextLanguages = ImmutableList.of();
preferredTextRoleFlags = 0; preferredTextRoleFlags = 0;
ignoredTextSelectionFlags = 0;
selectUndeterminedTextLanguage = false; selectUndeterminedTextLanguage = false;
// General // General
forceLowestBitrate = false; forceLowestBitrate = false;
forceHighestSupportedBitrate = false; forceHighestSupportedBitrate = false;
trackSelectionOverrides = TrackSelectionOverrides.EMPTY; overrides = new HashMap<>();
disabledTrackTypes = ImmutableSet.of(); disabledTrackTypes = new HashSet<>();
} }
/** /**
@ -222,6 +227,10 @@ public class TrackSelectionParameters implements Bundleable {
bundle.getInt( bundle.getInt(
keyForField(FIELD_PREFERRED_TEXT_ROLE_FLAGS), keyForField(FIELD_PREFERRED_TEXT_ROLE_FLAGS),
DEFAULT_WITHOUT_CONTEXT.preferredTextRoleFlags); DEFAULT_WITHOUT_CONTEXT.preferredTextRoleFlags);
ignoredTextSelectionFlags =
bundle.getInt(
keyForField(FIELD_IGNORED_TEXT_SELECTION_FLAGS),
DEFAULT_WITHOUT_CONTEXT.ignoredTextSelectionFlags);
selectUndeterminedTextLanguage = selectUndeterminedTextLanguage =
bundle.getBoolean( bundle.getBoolean(
keyForField(FIELD_SELECT_UNDETERMINED_TEXT_LANGUAGE), keyForField(FIELD_SELECT_UNDETERMINED_TEXT_LANGUAGE),
@ -234,16 +243,24 @@ public class TrackSelectionParameters implements Bundleable {
bundle.getBoolean( bundle.getBoolean(
keyForField(FIELD_FORCE_HIGHEST_SUPPORTED_BITRATE), keyForField(FIELD_FORCE_HIGHEST_SUPPORTED_BITRATE),
DEFAULT_WITHOUT_CONTEXT.forceHighestSupportedBitrate); DEFAULT_WITHOUT_CONTEXT.forceHighestSupportedBitrate);
trackSelectionOverrides = @Nullable
fromNullableBundle( List<Bundle> overrideBundleList =
TrackSelectionOverrides.CREATOR, bundle.getParcelableArrayList(keyForField(FIELD_SELECTION_OVERRIDES));
bundle.getBundle(keyForField(FIELD_SELECTION_OVERRIDE_KEYS)), List<TrackSelectionOverride> overrideList =
TrackSelectionOverrides.EMPTY); overrideBundleList == null
disabledTrackTypes = ? ImmutableList.of()
ImmutableSet.copyOf( : BundleableUtil.fromBundleList(TrackSelectionOverride.CREATOR, overrideBundleList);
Ints.asList( overrides = new HashMap<>();
firstNonNull( for (int i = 0; i < overrideList.size(); i++) {
bundle.getIntArray(keyForField(FIELD_DISABLED_TRACK_TYPE)), new int[0]))); TrackSelectionOverride override = overrideList.get(i);
overrides.put(override.mediaTrackGroup, override);
}
int[] disabledTrackTypeArray =
firstNonNull(bundle.getIntArray(keyForField(FIELD_DISABLED_TRACK_TYPE)), new int[0]);
disabledTrackTypes = new HashSet<>();
for (@C.TrackType int disabledTrackType : disabledTrackTypeArray) {
disabledTrackTypes.add(disabledTrackType);
}
} }
/** Overrides the value of the builder with the value of {@link TrackSelectionParameters}. */ /** Overrides the value of the builder with the value of {@link TrackSelectionParameters}. */
@ -252,7 +269,7 @@ public class TrackSelectionParameters implements Bundleable {
"preferredAudioLanguages", "preferredAudioLanguages",
"preferredAudioMimeTypes", "preferredAudioMimeTypes",
"preferredTextLanguages", "preferredTextLanguages",
"trackSelectionOverrides", "overrides",
"disabledTrackTypes", "disabledTrackTypes",
}) })
private void init(@UnknownInitialization Builder this, TrackSelectionParameters parameters) { private void init(@UnknownInitialization Builder this, TrackSelectionParameters parameters) {
@ -279,12 +296,13 @@ public class TrackSelectionParameters implements Bundleable {
// Text // Text
preferredTextLanguages = parameters.preferredTextLanguages; preferredTextLanguages = parameters.preferredTextLanguages;
preferredTextRoleFlags = parameters.preferredTextRoleFlags; preferredTextRoleFlags = parameters.preferredTextRoleFlags;
ignoredTextSelectionFlags = parameters.ignoredTextSelectionFlags;
selectUndeterminedTextLanguage = parameters.selectUndeterminedTextLanguage; selectUndeterminedTextLanguage = parameters.selectUndeterminedTextLanguage;
// General // General
forceLowestBitrate = parameters.forceLowestBitrate; forceLowestBitrate = parameters.forceLowestBitrate;
forceHighestSupportedBitrate = parameters.forceHighestSupportedBitrate; forceHighestSupportedBitrate = parameters.forceHighestSupportedBitrate;
trackSelectionOverrides = parameters.trackSelectionOverrides; disabledTrackTypes = new HashSet<>(parameters.disabledTrackTypes);
disabledTrackTypes = parameters.disabledTrackTypes; overrides = new HashMap<>(parameters.overrides);
} }
/** Overrides the value of the builder with the value of {@link TrackSelectionParameters}. */ /** Overrides the value of the builder with the value of {@link TrackSelectionParameters}. */
@ -601,6 +619,18 @@ public class TrackSelectionParameters implements Bundleable {
return this; return this;
} }
/**
* Sets a bitmask of selection flags that are ignored for text track selections.
*
* @param ignoredTextSelectionFlags A bitmask of {@link C.SelectionFlags} that are ignored for
* text track selections.
* @return This builder.
*/
public Builder setIgnoredTextSelectionFlags(@C.SelectionFlags int ignoredTextSelectionFlags) {
this.ignoredTextSelectionFlags = ignoredTextSelectionFlags;
return this;
}
/** /**
* Sets whether a text track with undetermined language should be selected if no track with * Sets whether a text track with undetermined language should be selected if no track with
* {@link #setPreferredTextLanguages(String...) a preferred language} is available, or if the * {@link #setPreferredTextLanguages(String...) a preferred language} is available, or if the
@ -643,26 +673,72 @@ public class TrackSelectionParameters implements Bundleable {
return this; return this;
} }
/** /** Adds an override, replacing any override for the same {@link TrackGroup}. */
* Sets the selection overrides. public Builder addOverride(TrackSelectionOverride override) {
* overrides.put(override.mediaTrackGroup, override);
* @param trackSelectionOverrides The track selection overrides. return this;
* @return This builder. }
*/
public Builder setTrackSelectionOverrides(TrackSelectionOverrides trackSelectionOverrides) { /** Sets an override, replacing all existing overrides with the same track type. */
this.trackSelectionOverrides = trackSelectionOverrides; public Builder setOverrideForType(TrackSelectionOverride override) {
clearOverridesOfType(override.getType());
overrides.put(override.mediaTrackGroup, override);
return this;
}
/** Removes the override for the provided media {@link TrackGroup}, if there is one. */
public Builder clearOverride(TrackGroup mediaTrackGroup) {
overrides.remove(mediaTrackGroup);
return this;
}
/** Removes all overrides of the provided track type. */
public Builder clearOverridesOfType(@C.TrackType int trackType) {
Iterator<TrackSelectionOverride> it = overrides.values().iterator();
while (it.hasNext()) {
TrackSelectionOverride override = it.next();
if (override.getType() == trackType) {
it.remove();
}
}
return this;
}
/** Removes all overrides. */
public Builder clearOverrides() {
overrides.clear();
return this; return this;
} }
/** /**
* Sets the disabled track types, preventing all tracks of those types from being selected for * Sets the disabled track types, preventing all tracks of those types from being selected for
* playback. * playback. Any previously disabled track types are cleared.
* *
* @param disabledTrackTypes The track types to disable. * @param disabledTrackTypes The track types to disable.
* @return This builder. * @return This builder.
* @deprecated Use {@link #setTrackTypeDisabled(int, boolean)}.
*/ */
@Deprecated
public Builder setDisabledTrackTypes(Set<@C.TrackType Integer> disabledTrackTypes) { public Builder setDisabledTrackTypes(Set<@C.TrackType Integer> disabledTrackTypes) {
this.disabledTrackTypes = ImmutableSet.copyOf(disabledTrackTypes); this.disabledTrackTypes.clear();
this.disabledTrackTypes.addAll(disabledTrackTypes);
return this;
}
/**
* Sets whether a track type is disabled. If disabled, no tracks of the specified type will be
* selected for playback.
*
* @param trackType The track type.
* @param disabled Whether the track type should be disabled.
* @return This builder.
*/
public Builder setTrackTypeDisabled(@C.TrackType int trackType, boolean disabled) {
if (disabled) {
disabledTrackTypes.add(trackType);
} else {
disabledTrackTypes.remove(trackType);
}
return this; return this;
} }
@ -837,6 +913,11 @@ public class TrackSelectionParameters implements Bundleable {
* is enabled. * is enabled.
*/ */
public final @C.RoleFlags int preferredTextRoleFlags; public final @C.RoleFlags int preferredTextRoleFlags;
/**
* Bitmask of selection flags that are ignored for text track selections. See {@link
* C.SelectionFlags}. The default value is {@code 0} (i.e., no flags are ignored).
*/
public final @C.SelectionFlags int ignoredTextSelectionFlags;
/** /**
* Whether a text track with undetermined language should be selected if no track with {@link * Whether a text track with undetermined language should be selected if no track with {@link
* #preferredTextLanguages} is available, or if {@link #preferredTextLanguages} is unset. The * #preferredTextLanguages} is available, or if {@link #preferredTextLanguages} is unset. The
@ -855,8 +936,9 @@ public class TrackSelectionParameters implements Bundleable {
*/ */
public final boolean forceHighestSupportedBitrate; public final boolean forceHighestSupportedBitrate;
/** Overrides to force tracks to be selected. */ /** Overrides to force selection of specific tracks. */
public final TrackSelectionOverrides trackSelectionOverrides; public final ImmutableMap<TrackGroup, TrackSelectionOverride> overrides;
/** /**
* The track types that are disabled. No track of a disabled type will be selected, thus no track * The track types that are disabled. No track of a disabled type will be selected, thus no track
* type contained in the set will be played. The default value is that no track type is disabled * type contained in the set will be played. The default value is that no track type is disabled
@ -888,12 +970,13 @@ public class TrackSelectionParameters implements Bundleable {
// Text // Text
this.preferredTextLanguages = builder.preferredTextLanguages; this.preferredTextLanguages = builder.preferredTextLanguages;
this.preferredTextRoleFlags = builder.preferredTextRoleFlags; this.preferredTextRoleFlags = builder.preferredTextRoleFlags;
this.ignoredTextSelectionFlags = builder.ignoredTextSelectionFlags;
this.selectUndeterminedTextLanguage = builder.selectUndeterminedTextLanguage; this.selectUndeterminedTextLanguage = builder.selectUndeterminedTextLanguage;
// General // General
this.forceLowestBitrate = builder.forceLowestBitrate; this.forceLowestBitrate = builder.forceLowestBitrate;
this.forceHighestSupportedBitrate = builder.forceHighestSupportedBitrate; this.forceHighestSupportedBitrate = builder.forceHighestSupportedBitrate;
this.trackSelectionOverrides = builder.trackSelectionOverrides; this.overrides = ImmutableMap.copyOf(builder.overrides);
this.disabledTrackTypes = builder.disabledTrackTypes; this.disabledTrackTypes = ImmutableSet.copyOf(builder.disabledTrackTypes);
} }
/** Creates a new {@link Builder}, copying the initial values from this instance. */ /** Creates a new {@link Builder}, copying the initial values from this instance. */
@ -931,13 +1014,15 @@ public class TrackSelectionParameters implements Bundleable {
&& maxAudioChannelCount == other.maxAudioChannelCount && maxAudioChannelCount == other.maxAudioChannelCount
&& maxAudioBitrate == other.maxAudioBitrate && maxAudioBitrate == other.maxAudioBitrate
&& preferredAudioMimeTypes.equals(other.preferredAudioMimeTypes) && preferredAudioMimeTypes.equals(other.preferredAudioMimeTypes)
// Text
&& preferredTextLanguages.equals(other.preferredTextLanguages) && preferredTextLanguages.equals(other.preferredTextLanguages)
&& preferredTextRoleFlags == other.preferredTextRoleFlags && preferredTextRoleFlags == other.preferredTextRoleFlags
&& ignoredTextSelectionFlags == other.ignoredTextSelectionFlags
&& selectUndeterminedTextLanguage == other.selectUndeterminedTextLanguage && selectUndeterminedTextLanguage == other.selectUndeterminedTextLanguage
// General // General
&& forceLowestBitrate == other.forceLowestBitrate && forceLowestBitrate == other.forceLowestBitrate
&& forceHighestSupportedBitrate == other.forceHighestSupportedBitrate && forceHighestSupportedBitrate == other.forceHighestSupportedBitrate
&& trackSelectionOverrides.equals(other.trackSelectionOverrides) && overrides.equals(other.overrides)
&& disabledTrackTypes.equals(other.disabledTrackTypes); && disabledTrackTypes.equals(other.disabledTrackTypes);
} }
@ -967,50 +1052,18 @@ public class TrackSelectionParameters implements Bundleable {
// Text // Text
result = 31 * result + preferredTextLanguages.hashCode(); result = 31 * result + preferredTextLanguages.hashCode();
result = 31 * result + preferredTextRoleFlags; result = 31 * result + preferredTextRoleFlags;
result = 31 * result + ignoredTextSelectionFlags;
result = 31 * result + (selectUndeterminedTextLanguage ? 1 : 0); result = 31 * result + (selectUndeterminedTextLanguage ? 1 : 0);
// General // General
result = 31 * result + (forceLowestBitrate ? 1 : 0); result = 31 * result + (forceLowestBitrate ? 1 : 0);
result = 31 * result + (forceHighestSupportedBitrate ? 1 : 0); result = 31 * result + (forceHighestSupportedBitrate ? 1 : 0);
result = 31 * result + trackSelectionOverrides.hashCode(); result = 31 * result + overrides.hashCode();
result = 31 * result + disabledTrackTypes.hashCode(); result = 31 * result + disabledTrackTypes.hashCode();
return result; return result;
} }
// Bundleable implementation // Bundleable implementation
@Documented
@Retention(RetentionPolicy.SOURCE)
@Target(TYPE_USE)
@IntDef({
FIELD_PREFERRED_AUDIO_LANGUAGES,
FIELD_PREFERRED_AUDIO_ROLE_FLAGS,
FIELD_PREFERRED_TEXT_LANGUAGES,
FIELD_PREFERRED_TEXT_ROLE_FLAGS,
FIELD_SELECT_UNDETERMINED_TEXT_LANGUAGE,
FIELD_MAX_VIDEO_WIDTH,
FIELD_MAX_VIDEO_HEIGHT,
FIELD_MAX_VIDEO_FRAMERATE,
FIELD_MAX_VIDEO_BITRATE,
FIELD_MIN_VIDEO_WIDTH,
FIELD_MIN_VIDEO_HEIGHT,
FIELD_MIN_VIDEO_FRAMERATE,
FIELD_MIN_VIDEO_BITRATE,
FIELD_VIEWPORT_WIDTH,
FIELD_VIEWPORT_HEIGHT,
FIELD_VIEWPORT_ORIENTATION_MAY_CHANGE,
FIELD_PREFERRED_VIDEO_MIMETYPES,
FIELD_MAX_AUDIO_CHANNEL_COUNT,
FIELD_MAX_AUDIO_BITRATE,
FIELD_PREFERRED_AUDIO_MIME_TYPES,
FIELD_FORCE_LOWEST_BITRATE,
FIELD_FORCE_HIGHEST_SUPPORTED_BITRATE,
FIELD_SELECTION_OVERRIDE_KEYS,
FIELD_SELECTION_OVERRIDE_VALUES,
FIELD_DISABLED_TRACK_TYPE,
FIELD_PREFERRED_VIDEO_ROLE_FLAGS
})
private @interface FieldNumber {}
private static final int FIELD_PREFERRED_AUDIO_LANGUAGES = 1; private static final int FIELD_PREFERRED_AUDIO_LANGUAGES = 1;
private static final int FIELD_PREFERRED_AUDIO_ROLE_FLAGS = 2; private static final int FIELD_PREFERRED_AUDIO_ROLE_FLAGS = 2;
private static final int FIELD_PREFERRED_TEXT_LANGUAGES = 3; private static final int FIELD_PREFERRED_TEXT_LANGUAGES = 3;
@ -1033,10 +1086,19 @@ public class TrackSelectionParameters implements Bundleable {
private static final int FIELD_PREFERRED_AUDIO_MIME_TYPES = 20; private static final int FIELD_PREFERRED_AUDIO_MIME_TYPES = 20;
private static final int FIELD_FORCE_LOWEST_BITRATE = 21; private static final int FIELD_FORCE_LOWEST_BITRATE = 21;
private static final int FIELD_FORCE_HIGHEST_SUPPORTED_BITRATE = 22; private static final int FIELD_FORCE_HIGHEST_SUPPORTED_BITRATE = 22;
private static final int FIELD_SELECTION_OVERRIDE_KEYS = 23; private static final int FIELD_SELECTION_OVERRIDES = 23;
private static final int FIELD_SELECTION_OVERRIDE_VALUES = 24; private static final int FIELD_DISABLED_TRACK_TYPE = 24;
private static final int FIELD_DISABLED_TRACK_TYPE = 25; private static final int FIELD_PREFERRED_VIDEO_ROLE_FLAGS = 25;
private static final int FIELD_PREFERRED_VIDEO_ROLE_FLAGS = 26; private static final int FIELD_IGNORED_TEXT_SELECTION_FLAGS = 26;
/**
* Defines a minimum field ID value for subclasses to use when implementing {@link #toBundle()}
* and {@link Bundleable.Creator}.
*
* <p>Subclasses should obtain keys for their {@link Bundle} representation by applying a
* non-negative offset on this constant and passing the result to {@link #keyForField(int)}.
*/
protected static final int FIELD_CUSTOM_ID_BASE = 1000;
@Override @Override
public Bundle toBundle() { public Bundle toBundle() {
@ -1073,24 +1135,40 @@ public class TrackSelectionParameters implements Bundleable {
bundle.putStringArray( bundle.putStringArray(
keyForField(FIELD_PREFERRED_TEXT_LANGUAGES), preferredTextLanguages.toArray(new String[0])); keyForField(FIELD_PREFERRED_TEXT_LANGUAGES), preferredTextLanguages.toArray(new String[0]));
bundle.putInt(keyForField(FIELD_PREFERRED_TEXT_ROLE_FLAGS), preferredTextRoleFlags); bundle.putInt(keyForField(FIELD_PREFERRED_TEXT_ROLE_FLAGS), preferredTextRoleFlags);
bundle.putInt(keyForField(FIELD_IGNORED_TEXT_SELECTION_FLAGS), ignoredTextSelectionFlags);
bundle.putBoolean( bundle.putBoolean(
keyForField(FIELD_SELECT_UNDETERMINED_TEXT_LANGUAGE), selectUndeterminedTextLanguage); keyForField(FIELD_SELECT_UNDETERMINED_TEXT_LANGUAGE), selectUndeterminedTextLanguage);
// General // General
bundle.putBoolean(keyForField(FIELD_FORCE_LOWEST_BITRATE), forceLowestBitrate); bundle.putBoolean(keyForField(FIELD_FORCE_LOWEST_BITRATE), forceLowestBitrate);
bundle.putBoolean( bundle.putBoolean(
keyForField(FIELD_FORCE_HIGHEST_SUPPORTED_BITRATE), forceHighestSupportedBitrate); keyForField(FIELD_FORCE_HIGHEST_SUPPORTED_BITRATE), forceHighestSupportedBitrate);
bundle.putBundle( bundle.putParcelableArrayList(
keyForField(FIELD_SELECTION_OVERRIDE_KEYS), trackSelectionOverrides.toBundle()); keyForField(FIELD_SELECTION_OVERRIDES), toBundleArrayList(overrides.values()));
bundle.putIntArray(keyForField(FIELD_DISABLED_TRACK_TYPE), Ints.toArray(disabledTrackTypes)); bundle.putIntArray(keyForField(FIELD_DISABLED_TRACK_TYPE), Ints.toArray(disabledTrackTypes));
return bundle; return bundle;
} }
/** Object that can restore {@code TrackSelectionParameters} from a {@link Bundle}. */ /** Construct an instance from a {@link Bundle} produced by {@link #toBundle()}. */
public static final Creator<TrackSelectionParameters> CREATOR = public static TrackSelectionParameters fromBundle(Bundle bundle) {
bundle -> new Builder(bundle).build(); return new Builder(bundle).build();
}
private static String keyForField(@FieldNumber int field) { /**
* @deprecated Use {@link #fromBundle(Bundle)} instead.
*/
@Deprecated
public static final Creator<TrackSelectionParameters> CREATOR =
TrackSelectionParameters::fromBundle;
/**
* Converts the given field number to a string which can be used as a field key when implementing
* {@link #toBundle()} and {@link Bundleable.Creator}.
*
* <p>Subclasses should use {@code field} values greater than or equal to {@link
* #FIELD_CUSTOM_ID_BASE}.
*/
protected static String keyForField(int field) {
return Integer.toString(field, Character.MAX_RADIX); return Integer.toString(field, Character.MAX_RADIX);
} }
} }

View file

@ -96,13 +96,17 @@ public final class AdOverlayInfo {
/** An optional, detailed reason that the overlay view is needed. */ /** An optional, detailed reason that the overlay view is needed. */
@Nullable public final String reasonDetail; @Nullable public final String reasonDetail;
/** @deprecated Use {@link Builder} instead. */ /**
* @deprecated Use {@link Builder} instead.
*/
@Deprecated @Deprecated
public AdOverlayInfo(View view, @Purpose int purpose) { public AdOverlayInfo(View view, @Purpose int purpose) {
this(view, purpose, /* detailedReason= */ null); this(view, purpose, /* detailedReason= */ null);
} }
/** @deprecated Use {@link Builder} instead. */ /**
* @deprecated Use {@link Builder} instead.
*/
@Deprecated @Deprecated
public AdOverlayInfo(View view, @Purpose int purpose, @Nullable String detailedReason) { public AdOverlayInfo(View view, @Purpose int purpose, @Nullable String detailedReason) {
this.view = view; this.view = view;

View file

@ -30,34 +30,6 @@ import java.util.List;
/** Utilities for {@link Bundleable}. */ /** Utilities for {@link Bundleable}. */
public final class BundleableUtil { public final class BundleableUtil {
/**
* Converts a {@link Bundleable} to a {@link Bundle}. It's a convenience wrapper of {@link
* Bundleable#toBundle} that can take nullable values.
*/
@Nullable
public static Bundle toNullableBundle(@Nullable Bundleable bundleable) {
return bundleable == null ? null : bundleable.toBundle();
}
/**
* Converts a {@link Bundle} to a {@link Bundleable}. It's a convenience wrapper of {@link
* Bundleable.Creator#fromBundle} that can take nullable values.
*/
@Nullable
public static <T extends Bundleable> T fromNullableBundle(
Bundleable.Creator<T> creator, @Nullable Bundle bundle) {
return bundle == null ? null : creator.fromBundle(bundle);
}
/**
* Converts a {@link Bundle} to a {@link Bundleable}. It's a convenience wrapper of {@link
* Bundleable.Creator#fromBundle} that provides default value to ensure non-null.
*/
public static <T extends Bundleable> T fromNullableBundle(
Bundleable.Creator<T> creator, @Nullable Bundle bundle, T defaultValue) {
return bundle == null ? defaultValue : creator.fromBundle(bundle);
}
/** Converts a list of {@link Bundleable} to a list {@link Bundle}. */ /** Converts a list of {@link Bundleable} to a list {@link Bundle}. */
public static <T extends Bundleable> ImmutableList<Bundle> toBundleList(List<T> bundleableList) { public static <T extends Bundleable> ImmutableList<Bundle> toBundleList(List<T> bundleableList) {
ImmutableList.Builder<Bundle> builder = ImmutableList.builder(); ImmutableList.Builder<Bundle> builder = ImmutableList.builder();
@ -80,34 +52,6 @@ public final class BundleableUtil {
return builder.build(); return builder.build();
} }
/**
* Converts a list of {@link Bundle} to a list of {@link Bundleable}. Returns {@code defaultValue}
* if {@code bundleList} is null.
*/
public static <T extends Bundleable> List<T> fromBundleNullableList(
Bundleable.Creator<T> creator, @Nullable List<Bundle> bundleList, List<T> defaultValue) {
return (bundleList == null) ? defaultValue : fromBundleList(creator, bundleList);
}
/**
* Converts a {@link SparseArray} of {@link Bundle} to a {@link SparseArray} of {@link
* Bundleable}. Returns {@code defaultValue} if {@code bundleSparseArray} is null.
*/
public static <T extends Bundleable> SparseArray<T> fromBundleNullableSparseArray(
Bundleable.Creator<T> creator,
@Nullable SparseArray<Bundle> bundleSparseArray,
SparseArray<T> defaultValue) {
if (bundleSparseArray == null) {
return defaultValue;
}
// Can't use ImmutableList as it doesn't support null elements.
SparseArray<T> result = new SparseArray<>(bundleSparseArray.size());
for (int i = 0; i < bundleSparseArray.size(); i++) {
result.put(bundleSparseArray.keyAt(i), creator.fromBundle(bundleSparseArray.valueAt(i)));
}
return result;
}
/** /**
* Converts a collection of {@link Bundleable} to an {@link ArrayList} of {@link Bundle} so that * Converts a collection of {@link Bundleable} to an {@link ArrayList} of {@link Bundle} so that
* the returned list can be put to {@link Bundle} using {@link Bundle#putParcelableArrayList} * the returned list can be put to {@link Bundle} using {@link Bundle#putParcelableArrayList}
@ -122,6 +66,19 @@ public final class BundleableUtil {
return arrayList; return arrayList;
} }
/**
* Converts a {@link SparseArray} of {@link Bundle} to a {@link SparseArray} of {@link
* Bundleable}.
*/
public static <T extends Bundleable> SparseArray<T> fromBundleSparseArray(
Bundleable.Creator<T> creator, SparseArray<Bundle> bundleSparseArray) {
SparseArray<T> result = new SparseArray<>(bundleSparseArray.size());
for (int i = 0; i < bundleSparseArray.size(); i++) {
result.put(bundleSparseArray.keyAt(i), creator.fromBundle(bundleSparseArray.valueAt(i)));
}
return result;
}
/** /**
* Converts a {@link SparseArray} of {@link Bundleable} to an {@link SparseArray} of {@link * Converts a {@link SparseArray} of {@link Bundleable} to an {@link SparseArray} of {@link
* Bundle} so that the returned {@link SparseArray} can be put to {@link Bundle} using {@link * Bundle} so that the returned {@link SparseArray} can be put to {@link Bundle} using {@link

View file

@ -35,10 +35,14 @@ public interface Clock {
*/ */
long currentTimeMillis(); long currentTimeMillis();
/** @see android.os.SystemClock#elapsedRealtime() */ /**
* @see android.os.SystemClock#elapsedRealtime()
*/
long elapsedRealtime(); long elapsedRealtime();
/** @see android.os.SystemClock#uptimeMillis() */ /**
* @see android.os.SystemClock#uptimeMillis()
*/
long uptimeMillis(); long uptimeMillis();
/** /**

View file

@ -15,6 +15,8 @@
*/ */
package com.google.android.exoplayer2.util; package com.google.android.exoplayer2.util;
import static com.google.android.exoplayer2.util.Assertions.checkArgument;
import android.util.Pair; import android.util.Pair;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
@ -29,6 +31,12 @@ public final class CodecSpecificDataUtil {
private static final String[] HEVC_GENERAL_PROFILE_SPACE_STRINGS = private static final String[] HEVC_GENERAL_PROFILE_SPACE_STRINGS =
new String[] {"", "A", "B", "C"}; new String[] {"", "A", "B", "C"};
// MP4V-ES
private static final int VISUAL_OBJECT_LAYER = 1;
private static final int VISUAL_OBJECT_LAYER_START = 0x20;
private static final int EXTENDED_PAR = 0x0F;
private static final int RECTANGULAR = 0x00;
/** /**
* Parses an ALAC AudioSpecificConfig (i.e. an <a * Parses an ALAC AudioSpecificConfig (i.e. an <a
* href="https://github.com/macosforge/alac/blob/master/ALACMagicCookieDescription.txt">ALACSpecificConfig</a>). * href="https://github.com/macosforge/alac/blob/master/ALACMagicCookieDescription.txt">ALACSpecificConfig</a>).
@ -70,6 +78,87 @@ public final class CodecSpecificDataUtil {
&& initializationData.get(0)[0] == 1; && initializationData.get(0)[0] == 1;
} }
/**
* Parses an MPEG-4 Visual configuration information, as defined in ISO/IEC14496-2.
*
* @param videoSpecificConfig A byte array containing the MPEG-4 Visual configuration information
* to parse.
* @return A pair of the video's width and height.
*/
public static Pair<Integer, Integer> getVideoResolutionFromMpeg4VideoConfig(
byte[] videoSpecificConfig) {
int offset = 0;
boolean foundVOL = false;
ParsableByteArray scratchBytes = new ParsableByteArray(videoSpecificConfig);
while (offset + 3 < videoSpecificConfig.length) {
if (scratchBytes.readUnsignedInt24() != VISUAL_OBJECT_LAYER
|| (videoSpecificConfig[offset + 3] & 0xF0) != VISUAL_OBJECT_LAYER_START) {
scratchBytes.setPosition(scratchBytes.getPosition() - 2);
offset++;
continue;
}
foundVOL = true;
break;
}
checkArgument(foundVOL, "Invalid input: VOL not found.");
ParsableBitArray scratchBits = new ParsableBitArray(videoSpecificConfig);
// Skip the start codecs from the bitstream
scratchBits.skipBits((offset + 4) * 8);
scratchBits.skipBits(1); // random_accessible_vol
scratchBits.skipBits(8); // video_object_type_indication
if (scratchBits.readBit()) { // object_layer_identifier
scratchBits.skipBits(4); // video_object_layer_verid
scratchBits.skipBits(3); // video_object_layer_priority
}
int aspectRatioInfo = scratchBits.readBits(4);
if (aspectRatioInfo == EXTENDED_PAR) {
scratchBits.skipBits(8); // par_width
scratchBits.skipBits(8); // par_height
}
if (scratchBits.readBit()) { // vol_control_parameters
scratchBits.skipBits(2); // chroma_format
scratchBits.skipBits(1); // low_delay
if (scratchBits.readBit()) { // vbv_parameters
scratchBits.skipBits(79);
}
}
int videoObjectLayerShape = scratchBits.readBits(2);
checkArgument(
videoObjectLayerShape == RECTANGULAR,
"Only supports rectangular video object layer shape.");
checkArgument(scratchBits.readBit()); // marker_bit
int vopTimeIncrementResolution = scratchBits.readBits(16);
checkArgument(scratchBits.readBit()); // marker_bit
if (scratchBits.readBit()) { // fixed_vop_rate
checkArgument(vopTimeIncrementResolution > 0);
vopTimeIncrementResolution--;
int numBitsToSkip = 0;
while (vopTimeIncrementResolution > 0) {
numBitsToSkip++;
vopTimeIncrementResolution >>= 1;
}
scratchBits.skipBits(numBitsToSkip); // fixed_vop_time_increment
}
checkArgument(scratchBits.readBit()); // marker_bit
int videoObjectLayerWidth = scratchBits.readBits(13);
checkArgument(scratchBits.readBit()); // marker_bit
int videoObjectLayerHeight = scratchBits.readBits(13);
checkArgument(scratchBits.readBit()); // marker_bit
scratchBits.skipBits(1); // interlaced
return Pair.create(videoObjectLayerWidth, videoObjectLayerHeight);
}
/** /**
* Builds an RFC 6381 AVC codec string using the provided parameters. * Builds an RFC 6381 AVC codec string using the provided parameters.
* *

Some files were not shown because too many files have changed in this diff Show more