mirror of
https://github.com/samsonjs/media.git
synced 2026-04-13 12:35:48 +00:00
commit
867b107835
499 changed files with 27336 additions and 8577 deletions
|
|
@ -17,7 +17,7 @@ buildscript {
|
|||
mavenCentral()
|
||||
}
|
||||
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'
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,5 +29,10 @@ android {
|
|||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
|
||||
testOptions.unitTests.includeAndroidResources = true
|
||||
testOptions {
|
||||
unitTests.all {
|
||||
jvmArgs "-Xmx2g"
|
||||
}
|
||||
unitTests.includeAndroidResources true
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,14 +20,14 @@ project.ext {
|
|||
// Upgrading this requires [Internal ref: b/193254928] to be fixed, or some
|
||||
// additional robolectric config.
|
||||
targetSdkVersion = 30
|
||||
compileSdkVersion = 31
|
||||
compileSdkVersion = 32
|
||||
dexmakerVersion = '2.28.1'
|
||||
junitVersion = '4.13.2'
|
||||
// Use the same Guava version as the Android repo:
|
||||
// https://cs.android.com/android/platform/superproject/+/master:external/guava/METADATA
|
||||
guavaVersion = '31.0.1-android'
|
||||
mockitoVersion = '3.12.4'
|
||||
robolectricVersion = '4.6.1'
|
||||
robolectricVersion = '4.8.1'
|
||||
// Keep this in sync with Google's internal Checker Framework version.
|
||||
checkerframeworkVersion = '3.13.0'
|
||||
checkerframeworkCompatVersion = '2.5.5'
|
||||
|
|
|
|||
|
|
@ -230,8 +230,8 @@ public class MainActivity extends AppCompatActivity
|
|||
@Override
|
||||
public boolean onMove(
|
||||
RecyclerView list, RecyclerView.ViewHolder origin, RecyclerView.ViewHolder target) {
|
||||
int fromPosition = origin.getAdapterPosition();
|
||||
int toPosition = target.getAdapterPosition();
|
||||
int fromPosition = origin.getBindingAdapterPosition();
|
||||
int toPosition = target.getBindingAdapterPosition();
|
||||
if (draggingFromPosition == C.INDEX_UNSET) {
|
||||
// A drag has started, but changes to the media queue will be reflected in clearView().
|
||||
draggingFromPosition = fromPosition;
|
||||
|
|
@ -243,7 +243,7 @@ public class MainActivity extends AppCompatActivity
|
|||
|
||||
@Override
|
||||
public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
|
||||
int position = viewHolder.getAdapterPosition();
|
||||
int position = viewHolder.getBindingAdapterPosition();
|
||||
QueueItemViewHolder queueItemHolder = (QueueItemViewHolder) viewHolder;
|
||||
if (playerManager.removeItem(queueItemHolder.item)) {
|
||||
mediaQueueListAdapter.notifyItemRemoved(position);
|
||||
|
|
@ -282,7 +282,7 @@ public class MainActivity extends AppCompatActivity
|
|||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
playerManager.selectQueueItem(getAdapterPosition());
|
||||
playerManager.selectQueueItem(getBindingAdapterPosition());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ import com.google.android.exoplayer2.Player;
|
|||
import com.google.android.exoplayer2.Player.DiscontinuityReason;
|
||||
import com.google.android.exoplayer2.Player.TimelineChangeReason;
|
||||
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.SessionAvailabilityListener;
|
||||
import com.google.android.exoplayer2.ui.StyledPlayerControlView;
|
||||
|
|
@ -57,7 +57,7 @@ import java.util.ArrayList;
|
|||
private final ArrayList<MediaItem> mediaQueue;
|
||||
private final Listener listener;
|
||||
|
||||
private TracksInfo lastSeenTrackGroupInfo;
|
||||
private Tracks lastSeenTracks;
|
||||
private int currentItemIndex;
|
||||
private Player currentPlayer;
|
||||
|
||||
|
|
@ -219,19 +219,19 @@ import java.util.ArrayList;
|
|||
}
|
||||
|
||||
@Override
|
||||
public void onTracksInfoChanged(TracksInfo tracksInfo) {
|
||||
if (currentPlayer != localPlayer || tracksInfo == lastSeenTrackGroupInfo) {
|
||||
public void onTracksChanged(Tracks tracks) {
|
||||
if (currentPlayer != localPlayer || tracks == lastSeenTracks) {
|
||||
return;
|
||||
}
|
||||
if (!tracksInfo.isTypeSupportedOrEmpty(
|
||||
C.TRACK_TYPE_VIDEO, /* allowExceedsCapabilities= */ true)) {
|
||||
if (tracks.containsType(C.TRACK_TYPE_VIDEO)
|
||||
&& !tracks.isTypeSupported(C.TRACK_TYPE_VIDEO, /* allowExceedsCapabilities= */ true)) {
|
||||
listener.onUnsupportedTrack(C.TRACK_TYPE_VIDEO);
|
||||
}
|
||||
if (!tracksInfo.isTypeSupportedOrEmpty(
|
||||
C.TRACK_TYPE_AUDIO, /* allowExceedsCapabilities= */ true)) {
|
||||
if (tracks.containsType(C.TRACK_TYPE_AUDIO)
|
||||
&& !tracks.isTypeSupported(C.TRACK_TYPE_AUDIO, /* allowExceedsCapabilities= */ true)) {
|
||||
listener.onUnsupportedTrack(C.TRACK_TYPE_AUDIO);
|
||||
}
|
||||
lastSeenTrackGroupInfo = tracksInfo;
|
||||
lastSeenTracks = tracks;
|
||||
}
|
||||
|
||||
// CastPlayer.SessionAvailabilityListener implementation.
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ import android.graphics.drawable.BitmapDrawable;
|
|||
import android.opengl.GLES20;
|
||||
import android.opengl.GLUtils;
|
||||
import com.google.android.exoplayer2.C;
|
||||
import com.google.android.exoplayer2.util.GlProgram;
|
||||
import com.google.android.exoplayer2.util.GlUtil;
|
||||
import java.io.IOException;
|
||||
import java.util.Locale;
|
||||
|
|
@ -50,7 +51,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||
private final Bitmap logoBitmap;
|
||||
private final Canvas overlayCanvas;
|
||||
|
||||
private GlUtil.@MonotonicNonNull Program program;
|
||||
private @MonotonicNonNull GlProgram program;
|
||||
|
||||
private float bitmapScaleX;
|
||||
private float bitmapScaleY;
|
||||
|
|
@ -78,7 +79,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||
public void initialize() {
|
||||
try {
|
||||
program =
|
||||
new GlUtil.Program(
|
||||
new GlProgram(
|
||||
context,
|
||||
/* vertexShaderFilePath= */ "bitmap_overlay_video_processor_vertex.glsl",
|
||||
/* fragmentShaderFilePath= */ "bitmap_overlay_video_processor_fragment.glsl");
|
||||
|
|
@ -86,9 +87,13 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||
throw new IllegalStateException(e);
|
||||
}
|
||||
program.setBufferAttribute(
|
||||
"aFramePosition", GlUtil.getNormalizedCoordinateBounds(), GlUtil.RECTANGLE_VERTICES_COUNT);
|
||||
"aFramePosition",
|
||||
GlUtil.getNormalizedCoordinateBounds(),
|
||||
GlUtil.HOMOGENEOUS_COORDINATE_VECTOR_SIZE);
|
||||
program.setBufferAttribute(
|
||||
"aTexCoords", GlUtil.getTextureCoordinateBounds(), GlUtil.RECTANGLE_VERTICES_COUNT);
|
||||
"aTexCoords",
|
||||
GlUtil.getTextureCoordinateBounds(),
|
||||
GlUtil.HOMOGENEOUS_COORDINATE_VECTOR_SIZE);
|
||||
GLES20.glGenTextures(1, textures, 0);
|
||||
GLES20.glBindTexture(GL10.GL_TEXTURE_2D, textures[0]);
|
||||
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();
|
||||
|
||||
// Run the shader program.
|
||||
GlUtil.Program program = checkNotNull(this.program);
|
||||
program.setSamplerTexIdUniform("uTexSampler0", frameTexture, /* unit= */ 0);
|
||||
program.setSamplerTexIdUniform("uTexSampler1", textures[0], /* unit= */ 1);
|
||||
GlProgram program = checkNotNull(this.program);
|
||||
program.setSamplerTexIdUniform("uTexSampler0", frameTexture, /* texUnitIndex= */ 0);
|
||||
program.setSamplerTexIdUniform("uTexSampler1", textures[0], /* texUnitIndex= */ 1);
|
||||
program.setFloatUniform("uScaleX", bitmapScaleX);
|
||||
program.setFloatUniform("uScaleY", bitmapScaleY);
|
||||
program.setFloatsUniform("uTexTransform", transformMatrix);
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ import android.content.Context;
|
|||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.text.TextUtils;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.Toast;
|
||||
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.DefaultDataSource;
|
||||
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.EventLogger;
|
||||
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 drmLicenseUrl = Assertions.checkNotNull(intent.getStringExtra(DRM_LICENSE_URL_EXTRA));
|
||||
UUID drmSchemeUuid = Assertions.checkNotNull(Util.getDrmUuid(drmScheme));
|
||||
HttpDataSource.Factory licenseDataSourceFactory = new DefaultHttpDataSource.Factory();
|
||||
DataSource.Factory licenseDataSourceFactory = new DefaultHttpDataSource.Factory();
|
||||
HttpMediaDrmCallback drmCallback =
|
||||
new HttpMediaDrmCallback(drmLicenseUrl, licenseDataSourceFactory);
|
||||
drmSessionManager =
|
||||
|
|
@ -157,13 +157,18 @@ public final class MainActivity extends Activity {
|
|||
|
||||
DataSource.Factory dataSourceFactory = new DefaultDataSource.Factory(this);
|
||||
MediaSource mediaSource;
|
||||
@C.ContentType int type = Util.inferContentType(uri, intent.getStringExtra(EXTENSION_EXTRA));
|
||||
if (type == C.TYPE_DASH) {
|
||||
@Nullable String fileExtension = intent.getStringExtra(EXTENSION_EXTRA);
|
||||
@C.ContentType
|
||||
int type =
|
||||
TextUtils.isEmpty(fileExtension)
|
||||
? Util.inferContentType(uri)
|
||||
: Util.inferContentTypeForExtension(fileExtension);
|
||||
if (type == C.CONTENT_TYPE_DASH) {
|
||||
mediaSource =
|
||||
new DashMediaSource.Factory(dataSourceFactory)
|
||||
.setDrmSessionManagerProvider(unusedMediaItem -> drmSessionManager)
|
||||
.createMediaSource(MediaItem.fromUri(uri));
|
||||
} else if (type == C.TYPE_OTHER) {
|
||||
} else if (type == C.CONTENT_TYPE_OTHER) {
|
||||
mediaSource =
|
||||
new ProgressiveMediaSource.Factory(dataSourceFactory)
|
||||
.setDrmSessionManagerProvider(unusedMediaItem -> drmSessionManager)
|
||||
|
|
@ -181,7 +186,7 @@ public final class MainActivity extends Activity {
|
|||
Assertions.checkNotNull(this.videoProcessingGLSurfaceView);
|
||||
videoProcessingGLSurfaceView.setPlayer(player);
|
||||
Assertions.checkNotNull(playerView).setPlayer(player);
|
||||
player.addAnalyticsListener(new EventLogger(/* trackSelector= */ null));
|
||||
player.addAnalyticsListener(new EventLogger());
|
||||
this.player = player;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -27,7 +27,6 @@ import com.google.android.exoplayer2.ui.DownloadNotificationHelper;
|
|||
import com.google.android.exoplayer2.upstream.DataSource;
|
||||
import com.google.android.exoplayer2.upstream.DefaultDataSource;
|
||||
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.CacheDataSource;
|
||||
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 DataSource.@MonotonicNonNull Factory dataSourceFactory;
|
||||
private static HttpDataSource.@MonotonicNonNull Factory httpDataSourceFactory;
|
||||
private static DataSource.@MonotonicNonNull Factory httpDataSourceFactory;
|
||||
private static @MonotonicNonNull DatabaseProvider databaseProvider;
|
||||
private static @MonotonicNonNull File downloadDirectory;
|
||||
private static @MonotonicNonNull Cache downloadCache;
|
||||
|
|
@ -85,7 +84,7 @@ public final class DemoUtil {
|
|||
.setExtensionRendererMode(extensionRendererMode);
|
||||
}
|
||||
|
||||
public static synchronized HttpDataSource.Factory getHttpDataSourceFactory(Context context) {
|
||||
public static synchronized DataSource.Factory getHttpDataSourceFactory(Context context) {
|
||||
if (httpDataSourceFactory == null) {
|
||||
if (USE_CRONET_FOR_NETWORKING) {
|
||||
context = context.getApplicationContext();
|
||||
|
|
|
|||
|
|
@ -15,8 +15,7 @@
|
|||
*/
|
||||
package com.google.android.exoplayer2.demo;
|
||||
|
||||
import static com.google.android.exoplayer2.util.Assertions.checkNotNull;
|
||||
import static com.google.android.exoplayer2.util.Assertions.checkStateNotNull;
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
|
|
@ -29,6 +28,7 @@ import androidx.fragment.app.FragmentManager;
|
|||
import com.google.android.exoplayer2.Format;
|
||||
import com.google.android.exoplayer2.MediaItem;
|
||||
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.DrmSession;
|
||||
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.source.TrackGroup;
|
||||
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.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.Util;
|
||||
import java.io.IOException;
|
||||
|
|
@ -65,31 +65,26 @@ public class DownloadTracker {
|
|||
private static final String TAG = "DownloadTracker";
|
||||
|
||||
private final Context context;
|
||||
private final HttpDataSource.Factory httpDataSourceFactory;
|
||||
private final DataSource.Factory dataSourceFactory;
|
||||
private final CopyOnWriteArraySet<Listener> listeners;
|
||||
private final HashMap<Uri, Download> downloads;
|
||||
private final DownloadIndex downloadIndex;
|
||||
private final DefaultTrackSelector.Parameters trackSelectorParameters;
|
||||
|
||||
@Nullable private StartDownloadDialogHelper startDownloadDialogHelper;
|
||||
|
||||
public DownloadTracker(
|
||||
Context context,
|
||||
HttpDataSource.Factory httpDataSourceFactory,
|
||||
DownloadManager downloadManager) {
|
||||
Context context, DataSource.Factory dataSourceFactory, DownloadManager downloadManager) {
|
||||
this.context = context.getApplicationContext();
|
||||
this.httpDataSourceFactory = httpDataSourceFactory;
|
||||
this.dataSourceFactory = dataSourceFactory;
|
||||
listeners = new CopyOnWriteArraySet<>();
|
||||
downloads = new HashMap<>();
|
||||
downloadIndex = downloadManager.getDownloadIndex();
|
||||
trackSelectorParameters = DownloadHelper.getDefaultTrackSelectorParameters(context);
|
||||
downloadManager.addListener(new DownloadManagerListener());
|
||||
loadDownloads();
|
||||
}
|
||||
|
||||
public void addListener(Listener listener) {
|
||||
checkNotNull(listener);
|
||||
listeners.add(listener);
|
||||
listeners.add(checkNotNull(listener));
|
||||
}
|
||||
|
||||
public void removeListener(Listener listener) {
|
||||
|
|
@ -120,8 +115,7 @@ public class DownloadTracker {
|
|||
startDownloadDialogHelper =
|
||||
new StartDownloadDialogHelper(
|
||||
fragmentManager,
|
||||
DownloadHelper.forMediaItem(
|
||||
context, mediaItem, renderersFactory, httpDataSourceFactory),
|
||||
DownloadHelper.forMediaItem(context, mediaItem, renderersFactory, dataSourceFactory),
|
||||
mediaItem);
|
||||
}
|
||||
}
|
||||
|
|
@ -159,7 +153,7 @@ public class DownloadTracker {
|
|||
|
||||
private final class StartDownloadDialogHelper
|
||||
implements DownloadHelper.Callback,
|
||||
DialogInterface.OnClickListener,
|
||||
TrackSelectionDialog.TrackSelectionListener,
|
||||
DialogInterface.OnDismissListener {
|
||||
|
||||
private final FragmentManager fragmentManager;
|
||||
|
|
@ -167,7 +161,6 @@ public class DownloadTracker {
|
|||
private final MediaItem mediaItem;
|
||||
|
||||
private TrackSelectionDialog trackSelectionDialog;
|
||||
private MappedTrackInfo mappedTrackInfo;
|
||||
private WidevineOfflineLicenseFetchTask widevineOfflineLicenseFetchTask;
|
||||
@Nullable private byte[] keySetId;
|
||||
|
||||
|
|
@ -220,7 +213,7 @@ public class DownloadTracker {
|
|||
new WidevineOfflineLicenseFetchTask(
|
||||
format,
|
||||
mediaItem.localConfiguration.drmConfiguration,
|
||||
httpDataSourceFactory,
|
||||
dataSourceFactory,
|
||||
/* dialogHelper= */ this,
|
||||
helper);
|
||||
widevineOfflineLicenseFetchTask.execute();
|
||||
|
|
@ -237,21 +230,13 @@ public class DownloadTracker {
|
|||
Log.e(TAG, logMessage, e);
|
||||
}
|
||||
|
||||
// DialogInterface.OnClickListener implementation.
|
||||
// TrackSelectionListener implementation.
|
||||
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
public void onTracksSelected(TrackSelectionParameters trackSelectionParameters) {
|
||||
for (int periodIndex = 0; periodIndex < downloadHelper.getPeriodCount(); periodIndex++) {
|
||||
downloadHelper.clearTrackSelections(periodIndex);
|
||||
for (int i = 0; i < mappedTrackInfo.getRendererCount(); i++) {
|
||||
if (!trackSelectionDialog.getIsDisabled(/* rendererIndex= */ i)) {
|
||||
downloadHelper.addTrackSelectionForSingleRenderer(
|
||||
periodIndex,
|
||||
/* rendererIndex= */ i,
|
||||
trackSelectorParameters,
|
||||
trackSelectionDialog.getOverrides(/* rendererIndex= */ i));
|
||||
}
|
||||
}
|
||||
downloadHelper.addTrackSelection(periodIndex, trackSelectionParameters);
|
||||
}
|
||||
DownloadRequest downloadRequest = buildDownloadRequest();
|
||||
if (downloadRequest.streamKeys.isEmpty()) {
|
||||
|
|
@ -316,21 +301,21 @@ public class DownloadTracker {
|
|||
return;
|
||||
}
|
||||
|
||||
mappedTrackInfo = downloadHelper.getMappedTrackInfo(/* periodIndex= */ 0);
|
||||
if (!TrackSelectionDialog.willHaveContent(mappedTrackInfo)) {
|
||||
Tracks tracks = downloadHelper.getTracks(/* periodIndex= */ 0);
|
||||
if (!TrackSelectionDialog.willHaveContent(tracks)) {
|
||||
Log.d(TAG, "No dialog content. Downloading entire stream.");
|
||||
startDownload();
|
||||
downloadHelper.release();
|
||||
return;
|
||||
}
|
||||
trackSelectionDialog =
|
||||
TrackSelectionDialog.createForMappedTrackInfoAndParameters(
|
||||
TrackSelectionDialog.createForTracksAndParameters(
|
||||
/* titleId= */ R.string.exo_download_description,
|
||||
mappedTrackInfo,
|
||||
trackSelectorParameters,
|
||||
tracks,
|
||||
DownloadHelper.getDefaultTrackSelectorParameters(context),
|
||||
/* allowAdaptiveSelections= */ false,
|
||||
/* allowMultipleOverrides= */ true,
|
||||
/* onClickListener= */ this,
|
||||
/* onTracksSelectedListener= */ this,
|
||||
/* onDismissListener= */ this);
|
||||
trackSelectionDialog.show(fragmentManager, /* tag= */ null);
|
||||
}
|
||||
|
|
@ -371,7 +356,7 @@ public class DownloadTracker {
|
|||
|
||||
private final Format format;
|
||||
private final MediaItem.DrmConfiguration drmConfiguration;
|
||||
private final HttpDataSource.Factory httpDataSourceFactory;
|
||||
private final DataSource.Factory dataSourceFactory;
|
||||
private final StartDownloadDialogHelper dialogHelper;
|
||||
private final DownloadHelper downloadHelper;
|
||||
|
||||
|
|
@ -381,12 +366,12 @@ public class DownloadTracker {
|
|||
public WidevineOfflineLicenseFetchTask(
|
||||
Format format,
|
||||
MediaItem.DrmConfiguration drmConfiguration,
|
||||
HttpDataSource.Factory httpDataSourceFactory,
|
||||
DataSource.Factory dataSourceFactory,
|
||||
StartDownloadDialogHelper dialogHelper,
|
||||
DownloadHelper downloadHelper) {
|
||||
this.format = format;
|
||||
this.drmConfiguration = drmConfiguration;
|
||||
this.httpDataSourceFactory = httpDataSourceFactory;
|
||||
this.dataSourceFactory = dataSourceFactory;
|
||||
this.dialogHelper = dialogHelper;
|
||||
this.downloadHelper = downloadHelper;
|
||||
}
|
||||
|
|
@ -397,7 +382,7 @@ public class DownloadTracker {
|
|||
OfflineLicenseHelper.newWidevineInstance(
|
||||
drmConfiguration.licenseUri.toString(),
|
||||
drmConfiguration.forceDefaultLicenseUri,
|
||||
httpDataSourceFactory,
|
||||
dataSourceFactory,
|
||||
drmConfiguration.licenseRequestHeaders,
|
||||
new DrmSessionEventListener.EventDispatcher());
|
||||
try {
|
||||
|
|
@ -415,7 +400,7 @@ public class DownloadTracker {
|
|||
if (drmSessionException != null) {
|
||||
dialogHelper.onOfflineLicenseFetchedError(drmSessionException);
|
||||
} else {
|
||||
dialogHelper.onOfflineLicenseFetched(downloadHelper, checkStateNotNull(keySetId));
|
||||
dialogHelper.onOfflineLicenseFetched(downloadHelper, checkNotNull(keySetId));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,8 +15,9 @@
|
|||
*/
|
||||
package com.google.android.exoplayer2.demo;
|
||||
|
||||
import static com.google.android.exoplayer2.util.Assertions.checkNotNull;
|
||||
import static com.google.android.exoplayer2.util.Assertions.checkState;
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
import static com.google.common.base.Preconditions.checkState;
|
||||
|
||||
import android.content.Intent;
|
||||
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.SubtitleConfiguration;
|
||||
import com.google.android.exoplayer2.MediaMetadata;
|
||||
import com.google.android.exoplayer2.util.Assertions;
|
||||
import com.google.android.exoplayer2.util.Util;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import java.util.ArrayList;
|
||||
|
|
@ -87,7 +87,7 @@ public class IntentUtil {
|
|||
|
||||
/** Populates the intent with the given list of {@link MediaItem media items}. */
|
||||
public static void addToIntent(List<MediaItem> mediaItems, Intent intent) {
|
||||
Assertions.checkArgument(!mediaItems.isEmpty());
|
||||
checkArgument(!mediaItems.isEmpty());
|
||||
if (mediaItems.size() == 1) {
|
||||
MediaItem mediaItem = mediaItems.get(0);
|
||||
MediaItem.LocalConfiguration localConfiguration = checkNotNull(mediaItem.localConfiguration);
|
||||
|
|
@ -178,7 +178,7 @@ public class IntentUtil {
|
|||
headers.put(keyRequestPropertiesArray[i], keyRequestPropertiesArray[i + 1]);
|
||||
}
|
||||
}
|
||||
@Nullable UUID drmUuid = Util.getDrmUuid(Util.castNonNull(drmSchemeExtra));
|
||||
@Nullable UUID drmUuid = Util.getDrmUuid(drmSchemeExtra);
|
||||
if (drmUuid != null) {
|
||||
builder.setDrmConfiguration(
|
||||
new MediaItem.DrmConfiguration.Builder(drmUuid)
|
||||
|
|
@ -189,7 +189,7 @@ public class IntentUtil {
|
|||
intent.getBooleanExtra(
|
||||
DRM_FORCE_DEFAULT_LICENSE_URI_EXTRA + extrasKeySuffix, false))
|
||||
.setLicenseRequestHeaders(headers)
|
||||
.forceSessionsForAudioAndVideoTracks(
|
||||
.setForceSessionsForAudioAndVideoTracks(
|
||||
intent.getBooleanExtra(DRM_SESSION_FOR_CLEAR_CONTENT + extrasKeySuffix, false))
|
||||
.build());
|
||||
}
|
||||
|
|
@ -242,7 +242,7 @@ public class IntentUtil {
|
|||
drmConfiguration.forcedSessionTrackTypes;
|
||||
if (!forcedDrmSessionTrackTypes.isEmpty()) {
|
||||
// Only video and audio together are supported.
|
||||
Assertions.checkState(
|
||||
checkState(
|
||||
forcedDrmSessionTrackTypes.size() == 2
|
||||
&& forcedDrmSessionTrackTypes.contains(C.TRACK_TYPE_VIDEO)
|
||||
&& forcedDrmSessionTrackTypes.contains(C.TRACK_TYPE_AUDIO));
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ package com.google.android.exoplayer2.demo;
|
|||
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.util.Pair;
|
||||
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.Player;
|
||||
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.drm.DefaultDrmSessionManagerProvider;
|
||||
import com.google.android.exoplayer2.drm.FrameworkMediaDrm;
|
||||
import com.google.android.exoplayer2.ext.ima.ImaAdsLoader;
|
||||
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.MediaSource;
|
||||
import com.google.android.exoplayer2.source.ads.AdsLoader;
|
||||
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
|
||||
import com.google.android.exoplayer2.ui.StyledPlayerControlView;
|
||||
import com.google.android.exoplayer2.trackselection.TrackSelectionParameters;
|
||||
import com.google.android.exoplayer2.ui.StyledPlayerView;
|
||||
import com.google.android.exoplayer2.upstream.DataSource;
|
||||
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}. */
|
||||
public class PlayerActivity extends AppCompatActivity
|
||||
implements OnClickListener, StyledPlayerControlView.VisibilityListener {
|
||||
implements OnClickListener, StyledPlayerView.ControllerVisibilityListener {
|
||||
|
||||
// Saved instance state keys.
|
||||
|
||||
|
|
@ -79,10 +80,9 @@ public class PlayerActivity extends AppCompatActivity
|
|||
private Button selectTracksButton;
|
||||
private DataSource.Factory dataSourceFactory;
|
||||
private List<MediaItem> mediaItems;
|
||||
private DefaultTrackSelector trackSelector;
|
||||
private DefaultTrackSelector.Parameters trackSelectionParameters;
|
||||
private TrackSelectionParameters trackSelectionParameters;
|
||||
private DebugTextViewHelper debugViewHelper;
|
||||
private TracksInfo lastSeenTracksInfo;
|
||||
private Tracks lastSeenTracks;
|
||||
private boolean startAutoPlay;
|
||||
private int startItemIndex;
|
||||
private long startPosition;
|
||||
|
|
@ -90,7 +90,12 @@ public class PlayerActivity extends AppCompatActivity
|
|||
// For ad playback only.
|
||||
|
||||
@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;
|
||||
|
||||
private ImaServerSideAdInsertionMediaSource.AdsLoader.@MonotonicNonNull State
|
||||
serverSideAdsLoaderState;
|
||||
|
||||
|
|
@ -113,22 +118,15 @@ public class PlayerActivity extends AppCompatActivity
|
|||
playerView.requestFocus();
|
||||
|
||||
if (savedInstanceState != null) {
|
||||
// Restore as DefaultTrackSelector.Parameters in case ExoPlayer specific parameters were set.
|
||||
trackSelectionParameters =
|
||||
DefaultTrackSelector.Parameters.CREATOR.fromBundle(
|
||||
TrackSelectionParameters.fromBundle(
|
||||
savedInstanceState.getBundle(KEY_TRACK_SELECTION_PARAMETERS));
|
||||
startAutoPlay = savedInstanceState.getBoolean(KEY_AUTO_PLAY);
|
||||
startItemIndex = savedInstanceState.getInt(KEY_ITEM_INDEX);
|
||||
startPosition = savedInstanceState.getLong(KEY_POSITION);
|
||||
Bundle adsLoaderStateBundle = savedInstanceState.getBundle(KEY_SERVER_SIDE_ADS_LOADER_STATE);
|
||||
if (adsLoaderStateBundle != null) {
|
||||
serverSideAdsLoaderState =
|
||||
ImaServerSideAdInsertionMediaSource.AdsLoader.State.CREATOR.fromBundle(
|
||||
adsLoaderStateBundle);
|
||||
}
|
||||
restoreServerSideAdsLoaderState(savedInstanceState);
|
||||
} else {
|
||||
trackSelectionParameters =
|
||||
new DefaultTrackSelector.ParametersBuilder(/* context= */ this).build();
|
||||
trackSelectionParameters = new TrackSelectionParameters.Builder(/* context= */ this).build();
|
||||
clearStartPosition();
|
||||
}
|
||||
}
|
||||
|
|
@ -145,7 +143,7 @@ public class PlayerActivity extends AppCompatActivity
|
|||
@Override
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
if (Util.SDK_INT > 23) {
|
||||
if (Build.VERSION.SDK_INT > 23) {
|
||||
initializePlayer();
|
||||
if (playerView != null) {
|
||||
playerView.onResume();
|
||||
|
|
@ -156,7 +154,7 @@ public class PlayerActivity extends AppCompatActivity
|
|||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
if (Util.SDK_INT <= 23 || player == null) {
|
||||
if (Build.VERSION.SDK_INT <= 23 || player == null) {
|
||||
initializePlayer();
|
||||
if (playerView != null) {
|
||||
playerView.onResume();
|
||||
|
|
@ -167,7 +165,7 @@ public class PlayerActivity extends AppCompatActivity
|
|||
@Override
|
||||
public void onPause() {
|
||||
super.onPause();
|
||||
if (Util.SDK_INT <= 23) {
|
||||
if (Build.VERSION.SDK_INT <= 23) {
|
||||
if (playerView != null) {
|
||||
playerView.onPause();
|
||||
}
|
||||
|
|
@ -178,7 +176,7 @@ public class PlayerActivity extends AppCompatActivity
|
|||
@Override
|
||||
public void onStop() {
|
||||
super.onStop();
|
||||
if (Util.SDK_INT > 23) {
|
||||
if (Build.VERSION.SDK_INT > 23) {
|
||||
if (playerView != null) {
|
||||
playerView.onPause();
|
||||
}
|
||||
|
|
@ -218,9 +216,7 @@ public class PlayerActivity extends AppCompatActivity
|
|||
outState.putBoolean(KEY_AUTO_PLAY, startAutoPlay);
|
||||
outState.putInt(KEY_ITEM_INDEX, startItemIndex);
|
||||
outState.putLong(KEY_POSITION, startPosition);
|
||||
if (serverSideAdsLoaderState != null) {
|
||||
outState.putBundle(KEY_SERVER_SIDE_ADS_LOADER_STATE, serverSideAdsLoaderState.toBundle());
|
||||
}
|
||||
saveServerSideAdsLoaderState(outState);
|
||||
}
|
||||
|
||||
// Activity input
|
||||
|
|
@ -237,20 +233,20 @@ public class PlayerActivity extends AppCompatActivity
|
|||
public void onClick(View view) {
|
||||
if (view == selectTracksButton
|
||||
&& !isShowingTrackSelectionDialog
|
||||
&& TrackSelectionDialog.willHaveContent(trackSelector)) {
|
||||
&& TrackSelectionDialog.willHaveContent(player)) {
|
||||
isShowingTrackSelectionDialog = true;
|
||||
TrackSelectionDialog trackSelectionDialog =
|
||||
TrackSelectionDialog.createForTrackSelector(
|
||||
trackSelector,
|
||||
TrackSelectionDialog.createForPlayer(
|
||||
player,
|
||||
/* onDismissListener= */ dismissedDialog -> isShowingTrackSelectionDialog = false);
|
||||
trackSelectionDialog.show(getSupportFragmentManager(), /* tag= */ null);
|
||||
}
|
||||
}
|
||||
|
||||
// StyledPlayerControlView.VisibilityListener implementation
|
||||
// StyledPlayerView.ControllerVisibilityListener implementation
|
||||
|
||||
@Override
|
||||
public void onVisibilityChange(int visibility) {
|
||||
public void onVisibilityChanged(int visibility) {
|
||||
debugRootView.setVisibility(visibility);
|
||||
}
|
||||
|
||||
|
|
@ -260,7 +256,9 @@ public class PlayerActivity extends AppCompatActivity
|
|||
setContentView(R.layout.player_activity);
|
||||
}
|
||||
|
||||
/** @return Whether initialization was successful. */
|
||||
/**
|
||||
* @return Whether initialization was successful.
|
||||
*/
|
||||
protected boolean initializePlayer() {
|
||||
if (player == null) {
|
||||
Intent intent = getIntent();
|
||||
|
|
@ -270,26 +268,20 @@ public class PlayerActivity extends AppCompatActivity
|
|||
return false;
|
||||
}
|
||||
|
||||
boolean preferExtensionDecoders =
|
||||
intent.getBooleanExtra(IntentUtil.PREFER_EXTENSION_DECODERS_EXTRA, false);
|
||||
RenderersFactory renderersFactory =
|
||||
DemoUtil.buildRenderersFactory(/* context= */ this, preferExtensionDecoders);
|
||||
|
||||
trackSelector = new DefaultTrackSelector(/* context= */ this);
|
||||
lastSeenTracksInfo = TracksInfo.EMPTY;
|
||||
player =
|
||||
lastSeenTracks = Tracks.EMPTY;
|
||||
ExoPlayer.Builder playerBuilder =
|
||||
new ExoPlayer.Builder(/* context= */ this)
|
||||
.setRenderersFactory(renderersFactory)
|
||||
.setMediaSourceFactory(createMediaSourceFactory())
|
||||
.setTrackSelector(trackSelector)
|
||||
.build();
|
||||
.setMediaSourceFactory(createMediaSourceFactory());
|
||||
setRenderersFactory(
|
||||
playerBuilder, intent.getBooleanExtra(IntentUtil.PREFER_EXTENSION_DECODERS_EXTRA, false));
|
||||
player = playerBuilder.build();
|
||||
player.setTrackSelectionParameters(trackSelectionParameters);
|
||||
player.addListener(new PlayerEventListener());
|
||||
player.addAnalyticsListener(new EventLogger(trackSelector));
|
||||
player.addAnalyticsListener(new EventLogger());
|
||||
player.setAudioAttributes(AudioAttributes.DEFAULT, /* handleAudioFocus= */ true);
|
||||
player.setPlayWhenReady(startAutoPlay);
|
||||
playerView.setPlayer(player);
|
||||
serverSideAdsLoader.setPlayer(player);
|
||||
configurePlayerWithServerSideAdsLoader();
|
||||
debugViewHelper = new DebugTextViewHelper(player, debugTextView);
|
||||
debugViewHelper.start();
|
||||
}
|
||||
|
|
@ -304,6 +296,10 @@ public class PlayerActivity extends AppCompatActivity
|
|||
}
|
||||
|
||||
private MediaSource.Factory createMediaSourceFactory() {
|
||||
DefaultDrmSessionManagerProvider drmSessionManagerProvider =
|
||||
new DefaultDrmSessionManagerProvider();
|
||||
drmSessionManagerProvider.setDrmHttpDataSourceFactory(
|
||||
DemoUtil.getHttpDataSourceFactory(/* context= */ this));
|
||||
ImaServerSideAdInsertionMediaSource.AdsLoader.Builder serverSideAdLoaderBuilder =
|
||||
new ImaServerSideAdInsertionMediaSource.AdsLoader.Builder(/* context= */ this, playerView);
|
||||
if (serverSideAdsLoaderState != null) {
|
||||
|
|
@ -312,13 +308,28 @@ public class PlayerActivity extends AppCompatActivity
|
|||
serverSideAdsLoader = serverSideAdLoaderBuilder.build();
|
||||
ImaServerSideAdInsertionMediaSource.Factory imaServerSideAdInsertionMediaSourceFactory =
|
||||
new ImaServerSideAdInsertionMediaSource.Factory(
|
||||
serverSideAdsLoader, new DefaultMediaSourceFactory(dataSourceFactory));
|
||||
return new DefaultMediaSourceFactory(dataSourceFactory)
|
||||
.setAdsLoaderProvider(this::getClientSideAdsLoader)
|
||||
.setAdViewProvider(playerView)
|
||||
serverSideAdsLoader,
|
||||
new DefaultMediaSourceFactory(/* context= */ this)
|
||||
.setDataSourceFactory(dataSourceFactory));
|
||||
return new DefaultMediaSourceFactory(/* context= */ this)
|
||||
.setDataSourceFactory(dataSourceFactory)
|
||||
.setDrmSessionManagerProvider(drmSessionManagerProvider)
|
||||
.setLocalAdInsertionComponents(
|
||||
this::getClientSideAdsLoader, /* adViewProvider= */ playerView)
|
||||
.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) {
|
||||
String action = intent.getAction();
|
||||
boolean actionIsListView = IntentUtil.ACTION_VIEW_LIST.equals(action);
|
||||
|
|
@ -345,7 +356,7 @@ public class PlayerActivity extends AppCompatActivity
|
|||
|
||||
MediaItem.DrmConfiguration drmConfiguration = mediaItem.localConfiguration.drmConfiguration;
|
||||
if (drmConfiguration != null) {
|
||||
if (Util.SDK_INT < 18) {
|
||||
if (Build.VERSION.SDK_INT < 18) {
|
||||
showToast(R.string.error_drm_unsupported_before_api_18);
|
||||
finish();
|
||||
return Collections.emptyList();
|
||||
|
|
@ -372,8 +383,7 @@ public class PlayerActivity extends AppCompatActivity
|
|||
if (player != null) {
|
||||
updateTrackSelectorParameters();
|
||||
updateStartPosition();
|
||||
serverSideAdsLoaderState = serverSideAdsLoader.release();
|
||||
serverSideAdsLoader = null;
|
||||
releaseServerSideAdsLoader();
|
||||
debugViewHelper.stop();
|
||||
debugViewHelper = null;
|
||||
player.release();
|
||||
|
|
@ -388,6 +398,11 @@ public class PlayerActivity extends AppCompatActivity
|
|||
}
|
||||
}
|
||||
|
||||
private void releaseServerSideAdsLoader() {
|
||||
serverSideAdsLoaderState = serverSideAdsLoader.release();
|
||||
serverSideAdsLoader = null;
|
||||
}
|
||||
|
||||
private void releaseClientSideAdsLoader() {
|
||||
if (clientSideAdsLoader != null) {
|
||||
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() {
|
||||
if (player != null) {
|
||||
// Until the demo app is fully migrated to TrackSelectionParameters, rely on ExoPlayer to use
|
||||
// DefaultTrackSelector by default.
|
||||
trackSelectionParameters =
|
||||
(DefaultTrackSelector.Parameters) player.getTrackSelectionParameters();
|
||||
trackSelectionParameters = player.getTrackSelectionParameters();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -422,8 +449,7 @@ public class PlayerActivity extends AppCompatActivity
|
|||
// User controls
|
||||
|
||||
private void updateButtonVisibility() {
|
||||
selectTracksButton.setEnabled(
|
||||
player != null && TrackSelectionDialog.willHaveContent(trackSelector));
|
||||
selectTracksButton.setEnabled(player != null && TrackSelectionDialog.willHaveContent(player));
|
||||
}
|
||||
|
||||
private void showControls() {
|
||||
|
|
@ -461,20 +487,20 @@ public class PlayerActivity extends AppCompatActivity
|
|||
|
||||
@Override
|
||||
@SuppressWarnings("ReferenceEquality")
|
||||
public void onTracksInfoChanged(TracksInfo tracksInfo) {
|
||||
public void onTracksChanged(Tracks tracks) {
|
||||
updateButtonVisibility();
|
||||
if (tracksInfo == lastSeenTracksInfo) {
|
||||
if (tracks == lastSeenTracks) {
|
||||
return;
|
||||
}
|
||||
if (!tracksInfo.isTypeSupportedOrEmpty(
|
||||
C.TRACK_TYPE_VIDEO, /* allowExceedsCapabilities= */ true)) {
|
||||
if (tracks.containsType(C.TRACK_TYPE_VIDEO)
|
||||
&& !tracks.isTypeSupported(C.TRACK_TYPE_VIDEO, /* allowExceedsCapabilities= */ true)) {
|
||||
showToast(R.string.error_unsupported_video);
|
||||
}
|
||||
if (!tracksInfo.isTypeSupportedOrEmpty(
|
||||
C.TRACK_TYPE_AUDIO, /* allowExceedsCapabilities= */ true)) {
|
||||
if (tracks.containsType(C.TRACK_TYPE_AUDIO)
|
||||
&& !tracks.isTypeSupported(C.TRACK_TYPE_AUDIO, /* allowExceedsCapabilities= */ true)) {
|
||||
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) {
|
||||
List<MediaItem> mediaItems = new ArrayList<>();
|
||||
for (MediaItem item : IntentUtil.createMediaItemsFromIntent(intent)) {
|
||||
@Nullable
|
||||
DownloadRequest downloadRequest =
|
||||
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);
|
||||
}
|
||||
mediaItems.add(
|
||||
maybeSetDownloadProperties(
|
||||
item, downloadTracker.getDownloadRequest(item.localConfiguration.uri)));
|
||||
}
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,9 +15,9 @@
|
|||
*/
|
||||
package com.google.android.exoplayer2.demo;
|
||||
|
||||
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.checkState;
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
import static com.google.common.base.Preconditions.checkState;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
|
|
@ -27,6 +27,7 @@ import android.content.res.AssetManager;
|
|||
import android.net.Uri;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Bundle;
|
||||
import android.text.TextUtils;
|
||||
import android.util.JsonReader;
|
||||
import android.view.Menu;
|
||||
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.util.Log;
|
||||
import com.google.android.exoplayer2.util.Util;
|
||||
import com.google.common.base.Objects;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import java.io.IOException;
|
||||
|
|
@ -116,8 +118,11 @@ public class SampleChooserActivity extends AppCompatActivity
|
|||
useExtensionRenderers = DemoUtil.useExtensionRenderers();
|
||||
downloadTracker = DemoUtil.getDownloadTracker(/* context= */ this);
|
||||
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
|
||||
// action. Starting it in the background throws an exception if the app is in the background too
|
||||
// (e.g. if device screen is locked).
|
||||
|
|
@ -436,7 +441,10 @@ public class SampleChooserActivity extends AppCompatActivity
|
|||
} else {
|
||||
@Nullable
|
||||
String adaptiveMimeType =
|
||||
Util.getAdaptiveMimeTypeForContentType(Util.inferContentType(uri, extension));
|
||||
Util.getAdaptiveMimeTypeForContentType(
|
||||
TextUtils.isEmpty(extension)
|
||||
? Util.inferContentType(uri)
|
||||
: Util.inferContentTypeForExtension(extension));
|
||||
mediaItem
|
||||
.setUri(uri)
|
||||
.setMediaMetadata(new MediaMetadata.Builder().setTitle(title).build())
|
||||
|
|
@ -447,7 +455,7 @@ public class SampleChooserActivity extends AppCompatActivity
|
|||
new MediaItem.DrmConfiguration.Builder(drmUuid)
|
||||
.setLicenseUri(drmLicenseUri)
|
||||
.setLicenseRequestHeaders(drmLicenseRequestHeaders)
|
||||
.forceSessionsForAudioAndVideoTracks(drmSessionForClearContent)
|
||||
.setForceSessionsForAudioAndVideoTracks(drmSessionForClearContent)
|
||||
.setMultiSession(drmMultiSession)
|
||||
.setForceDefaultLicenseUri(drmForceDefaultLicenseUri)
|
||||
.build());
|
||||
|
|
@ -481,7 +489,7 @@ public class SampleChooserActivity extends AppCompatActivity
|
|||
|
||||
private PlaylistGroup getGroup(String groupName, List<PlaylistGroup> groups) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,20 +32,40 @@ import androidx.fragment.app.FragmentManager;
|
|||
import androidx.fragment.app.FragmentPagerAdapter;
|
||||
import androidx.viewpager.widget.ViewPager;
|
||||
import com.google.android.exoplayer2.C;
|
||||
import com.google.android.exoplayer2.source.TrackGroupArray;
|
||||
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
|
||||
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector.SelectionOverride;
|
||||
import com.google.android.exoplayer2.trackselection.MappingTrackSelector.MappedTrackInfo;
|
||||
import com.google.android.exoplayer2.Player;
|
||||
import com.google.android.exoplayer2.Tracks;
|
||||
import com.google.android.exoplayer2.source.TrackGroup;
|
||||
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.util.Assertions;
|
||||
import com.google.android.material.tabs.TabLayout;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/** Dialog to select tracks. */
|
||||
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 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
|
||||
* specified {@link DefaultTrackSelector} in its current state.
|
||||
* specified {@link Player}.
|
||||
*/
|
||||
public static boolean willHaveContent(DefaultTrackSelector trackSelector) {
|
||||
MappedTrackInfo mappedTrackInfo = trackSelector.getCurrentMappedTrackInfo();
|
||||
return mappedTrackInfo != null && willHaveContent(mappedTrackInfo);
|
||||
public static boolean willHaveContent(Player player) {
|
||||
return willHaveContent(player.getCurrentTracks());
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
for (int i = 0; i < mappedTrackInfo.getRendererCount(); i++) {
|
||||
if (showTabForRenderer(mappedTrackInfo, i)) {
|
||||
public static boolean willHaveContent(Tracks tracks) {
|
||||
for (Tracks.Group trackGroup : tracks.getGroups()) {
|
||||
if (SUPPORTED_TRACK_TYPES.contains(trackGroup.getType())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
@ -76,78 +95,67 @@ public final class TrackSelectionDialog extends DialogFragment {
|
|||
}
|
||||
|
||||
/**
|
||||
* Creates a dialog for a given {@link DefaultTrackSelector}, whose parameters will be
|
||||
* automatically updated when tracks are selected.
|
||||
* Creates a dialog for a given {@link Player}, whose parameters will be automatically updated
|
||||
* 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
|
||||
* dismissed.
|
||||
*/
|
||||
public static TrackSelectionDialog createForTrackSelector(
|
||||
DefaultTrackSelector trackSelector, DialogInterface.OnDismissListener onDismissListener) {
|
||||
MappedTrackInfo mappedTrackInfo =
|
||||
Assertions.checkNotNull(trackSelector.getCurrentMappedTrackInfo());
|
||||
TrackSelectionDialog trackSelectionDialog = new TrackSelectionDialog();
|
||||
DefaultTrackSelector.Parameters parameters = trackSelector.getParameters();
|
||||
trackSelectionDialog.init(
|
||||
/* titleId= */ R.string.track_selection_title,
|
||||
mappedTrackInfo,
|
||||
/* initialParameters = */ parameters,
|
||||
public static TrackSelectionDialog createForPlayer(
|
||||
Player player, DialogInterface.OnDismissListener onDismissListener) {
|
||||
return createForTracksAndParameters(
|
||||
R.string.track_selection_title,
|
||||
player.getCurrentTracks(),
|
||||
player.getTrackSelectionParameters(),
|
||||
/* allowAdaptiveSelections= */ true,
|
||||
/* allowMultipleOverrides= */ false,
|
||||
/* onClickListener= */ (dialog, which) -> {
|
||||
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);
|
||||
},
|
||||
player::setTrackSelectionParameters,
|
||||
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 mappedTrackInfo The {@link MappedTrackInfo} to display.
|
||||
* @param initialParameters The {@link DefaultTrackSelector.Parameters} describing the initial
|
||||
* track selection.
|
||||
* @param tracks The {@link Tracks} describing the tracks to display.
|
||||
* @param trackSelectionParameters The initial {@link TrackSelectionParameters}.
|
||||
* @param allowAdaptiveSelections Whether adaptive selections (consisting of more than one track)
|
||||
* can be made.
|
||||
* @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
|
||||
* dismissed.
|
||||
*/
|
||||
public static TrackSelectionDialog createForMappedTrackInfoAndParameters(
|
||||
public static TrackSelectionDialog createForTracksAndParameters(
|
||||
int titleId,
|
||||
MappedTrackInfo mappedTrackInfo,
|
||||
DefaultTrackSelector.Parameters initialParameters,
|
||||
Tracks tracks,
|
||||
TrackSelectionParameters trackSelectionParameters,
|
||||
boolean allowAdaptiveSelections,
|
||||
boolean allowMultipleOverrides,
|
||||
DialogInterface.OnClickListener onClickListener,
|
||||
TrackSelectionListener trackSelectionListener,
|
||||
DialogInterface.OnDismissListener onDismissListener) {
|
||||
TrackSelectionDialog trackSelectionDialog = new TrackSelectionDialog();
|
||||
trackSelectionDialog.init(
|
||||
tracks,
|
||||
trackSelectionParameters,
|
||||
titleId,
|
||||
mappedTrackInfo,
|
||||
initialParameters,
|
||||
allowAdaptiveSelections,
|
||||
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);
|
||||
return trackSelectionDialog;
|
||||
}
|
||||
|
|
@ -160,9 +168,9 @@ public final class TrackSelectionDialog extends DialogFragment {
|
|||
}
|
||||
|
||||
private void init(
|
||||
Tracks tracks,
|
||||
TrackSelectionParameters trackSelectionParameters,
|
||||
int titleId,
|
||||
MappedTrackInfo mappedTrackInfo,
|
||||
DefaultTrackSelector.Parameters initialParameters,
|
||||
boolean allowAdaptiveSelections,
|
||||
boolean allowMultipleOverrides,
|
||||
DialogInterface.OnClickListener onClickListener,
|
||||
|
|
@ -170,45 +178,49 @@ public final class TrackSelectionDialog extends DialogFragment {
|
|||
this.titleId = titleId;
|
||||
this.onClickListener = onClickListener;
|
||||
this.onDismissListener = onDismissListener;
|
||||
for (int i = 0; i < mappedTrackInfo.getRendererCount(); i++) {
|
||||
if (showTabForRenderer(mappedTrackInfo, i)) {
|
||||
int trackType = mappedTrackInfo.getRendererType(/* rendererIndex= */ i);
|
||||
TrackGroupArray trackGroupArray = mappedTrackInfo.getTrackGroups(i);
|
||||
|
||||
for (int i = 0; i < SUPPORTED_TRACK_TYPES.size(); i++) {
|
||||
@C.TrackType int trackType = SUPPORTED_TRACK_TYPES.get(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();
|
||||
tabFragment.init(
|
||||
mappedTrackInfo,
|
||||
/* rendererIndex= */ i,
|
||||
initialParameters.getRendererDisabled(/* rendererIndex= */ i),
|
||||
initialParameters.getSelectionOverride(/* rendererIndex= */ i, trackGroupArray),
|
||||
trackGroups,
|
||||
trackSelectionParameters.disabledTrackTypes.contains(trackType),
|
||||
trackSelectionParameters.overrides,
|
||||
allowAdaptiveSelections,
|
||||
allowMultipleOverrides);
|
||||
tabFragments.put(i, tabFragment);
|
||||
tabFragments.put(trackType, tabFragment);
|
||||
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.
|
||||
* @return Whether the renderer is disabled.
|
||||
* @param trackType The track type.
|
||||
* @return Whether the disabled option is selected for the track type.
|
||||
*/
|
||||
public boolean getIsDisabled(int rendererIndex) {
|
||||
TrackSelectionViewFragment rendererView = tabFragments.get(rendererIndex);
|
||||
return rendererView != null && rendererView.isDisabled;
|
||||
public boolean getIsDisabled(int trackType) {
|
||||
TrackSelectionViewFragment trackView = tabFragments.get(trackType);
|
||||
return trackView != null && trackView.isDisabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of selected track selection overrides for the specified renderer. There will
|
||||
* be at most one override for each track group.
|
||||
* Returns the selected track overrides for the specified track type.
|
||||
*
|
||||
* @param rendererIndex Renderer index.
|
||||
* @return The list of track selection overrides for this renderer.
|
||||
* @param trackType The track type.
|
||||
* @return The track overrides for the track type.
|
||||
*/
|
||||
public List<SelectionOverride> getOverrides(int rendererIndex) {
|
||||
TrackSelectionViewFragment rendererView = tabFragments.get(rendererIndex);
|
||||
return rendererView == null ? Collections.emptyList() : rendererView.overrides;
|
||||
public Map<TrackGroup, TrackSelectionOverride> getOverrides(int trackType) {
|
||||
TrackSelectionViewFragment trackView = tabFragments.get(trackType);
|
||||
return trackView == null ? Collections.emptyMap() : trackView.overrides;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -248,27 +260,7 @@ public final class TrackSelectionDialog extends DialogFragment {
|
|||
return dialogView;
|
||||
}
|
||||
|
||||
private static boolean showTabForRenderer(MappedTrackInfo mappedTrackInfo, int rendererIndex) {
|
||||
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) {
|
||||
private static String getTrackTypeString(Resources resources, @C.TrackType int trackType) {
|
||||
switch (trackType) {
|
||||
case C.TRACK_TYPE_VIDEO:
|
||||
return resources.getString(R.string.exo_track_selection_title_video);
|
||||
|
|
@ -289,12 +281,12 @@ public final class TrackSelectionDialog extends DialogFragment {
|
|||
|
||||
@Override
|
||||
public Fragment getItem(int position) {
|
||||
return tabFragments.valueAt(position);
|
||||
return tabFragments.get(tabTrackTypes.get(position));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCount() {
|
||||
return tabFragments.size();
|
||||
return tabTrackTypes.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -307,13 +299,12 @@ public final class TrackSelectionDialog extends DialogFragment {
|
|||
public static final class TrackSelectionViewFragment extends Fragment
|
||||
implements TrackSelectionView.TrackSelectionListener {
|
||||
|
||||
private MappedTrackInfo mappedTrackInfo;
|
||||
private int rendererIndex;
|
||||
private List<Tracks.Group> trackGroups;
|
||||
private boolean allowAdaptiveSelections;
|
||||
private boolean allowMultipleOverrides;
|
||||
|
||||
/* package */ boolean isDisabled;
|
||||
/* package */ List<SelectionOverride> overrides;
|
||||
/* package */ Map<TrackGroup, TrackSelectionOverride> overrides;
|
||||
|
||||
public TrackSelectionViewFragment() {
|
||||
// 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(
|
||||
MappedTrackInfo mappedTrackInfo,
|
||||
int rendererIndex,
|
||||
boolean initialIsDisabled,
|
||||
@Nullable SelectionOverride initialOverride,
|
||||
List<Tracks.Group> trackGroups,
|
||||
boolean isDisabled,
|
||||
Map<TrackGroup, TrackSelectionOverride> overrides,
|
||||
boolean allowAdaptiveSelections,
|
||||
boolean allowMultipleOverrides) {
|
||||
this.mappedTrackInfo = mappedTrackInfo;
|
||||
this.rendererIndex = rendererIndex;
|
||||
this.isDisabled = initialIsDisabled;
|
||||
this.overrides =
|
||||
initialOverride == null
|
||||
? Collections.emptyList()
|
||||
: Collections.singletonList(initialOverride);
|
||||
this.trackGroups = trackGroups;
|
||||
this.isDisabled = isDisabled;
|
||||
this.allowAdaptiveSelections = allowAdaptiveSelections;
|
||||
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
|
||||
|
|
@ -351,8 +341,7 @@ public final class TrackSelectionDialog extends DialogFragment {
|
|||
trackSelectionView.setAllowMultipleOverrides(allowMultipleOverrides);
|
||||
trackSelectionView.setAllowAdaptiveSelections(allowAdaptiveSelections);
|
||||
trackSelectionView.init(
|
||||
mappedTrackInfo,
|
||||
rendererIndex,
|
||||
trackGroups,
|
||||
isDisabled,
|
||||
overrides,
|
||||
/* trackFormatComparator= */ null,
|
||||
|
|
@ -361,7 +350,8 @@ public final class TrackSelectionDialog extends DialogFragment {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void onTrackSelectionChanged(boolean isDisabled, List<SelectionOverride> overrides) {
|
||||
public void onTrackSelectionChanged(
|
||||
boolean isDisabled, Map<TrackGroup, TrackSelectionOverride> overrides) {
|
||||
this.isDisabled = isDisabled;
|
||||
this.overrides = overrides;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ import android.app.Activity;
|
|||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.text.TextUtils;
|
||||
import android.view.Surface;
|
||||
import android.view.SurfaceControl;
|
||||
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.DefaultDataSource;
|
||||
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.Util;
|
||||
import java.util.UUID;
|
||||
|
|
@ -189,7 +189,7 @@ public final class MainActivity extends Activity {
|
|||
String drmScheme = Assertions.checkNotNull(intent.getStringExtra(DRM_SCHEME_EXTRA));
|
||||
String drmLicenseUrl = Assertions.checkNotNull(intent.getStringExtra(DRM_LICENSE_URL_EXTRA));
|
||||
UUID drmSchemeUuid = Assertions.checkNotNull(Util.getDrmUuid(drmScheme));
|
||||
HttpDataSource.Factory licenseDataSourceFactory = new DefaultHttpDataSource.Factory();
|
||||
DataSource.Factory licenseDataSourceFactory = new DefaultHttpDataSource.Factory();
|
||||
HttpMediaDrmCallback drmCallback =
|
||||
new HttpMediaDrmCallback(drmLicenseUrl, licenseDataSourceFactory);
|
||||
drmSessionManager =
|
||||
|
|
@ -202,13 +202,18 @@ public final class MainActivity extends Activity {
|
|||
|
||||
DataSource.Factory dataSourceFactory = new DefaultDataSource.Factory(this);
|
||||
MediaSource mediaSource;
|
||||
@C.ContentType int type = Util.inferContentType(uri, intent.getStringExtra(EXTENSION_EXTRA));
|
||||
if (type == C.TYPE_DASH) {
|
||||
@Nullable String fileExtension = intent.getStringExtra(EXTENSION_EXTRA);
|
||||
@C.ContentType
|
||||
int type =
|
||||
TextUtils.isEmpty(fileExtension)
|
||||
? Util.inferContentType(uri)
|
||||
: Util.inferContentTypeForExtension(fileExtension);
|
||||
if (type == C.CONTENT_TYPE_DASH) {
|
||||
mediaSource =
|
||||
new DashMediaSource.Factory(dataSourceFactory)
|
||||
.setDrmSessionManagerProvider(unusedMediaItem -> drmSessionManager)
|
||||
.createMediaSource(MediaItem.fromUri(uri));
|
||||
} else if (type == C.TYPE_OTHER) {
|
||||
} else if (type == C.CONTENT_TYPE_OTHER) {
|
||||
mediaSource =
|
||||
new ProgressiveMediaSource.Factory(dataSourceFactory)
|
||||
.setDrmSessionManagerProvider(unusedMediaItem -> drmSessionManager)
|
||||
|
|
|
|||
22
demos/transformer/BUILD.bazel
Normal file
22
demos/transformer/BUILD.bazel
Normal 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",
|
||||
)
|
||||
|
|
@ -6,4 +6,61 @@ example by removing audio or video.
|
|||
See the [demos README](../README.md) for instructions on how to build and run
|
||||
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
|
||||
[MediaPipe]: https://google.github.io/mediapipe/
|
||||
[build an AAR]: https://google.github.io/mediapipe/getting_started/android_archive_library.html
|
||||
|
|
|
|||
|
|
@ -45,6 +45,27 @@ android {
|
|||
// This demo app isn't indexed and doesn't have translations.
|
||||
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 {
|
||||
|
|
@ -56,6 +77,14 @@ dependencies {
|
|||
implementation 'androidx.multidex:multidex:' + androidxMultidexVersion
|
||||
implementation 'com.google.android.material:material:' + androidxMaterialVersion
|
||||
implementation project(modulePrefix + 'library-core')
|
||||
implementation project(modulePrefix + 'library-dash')
|
||||
implementation project(modulePrefix + 'library-transformer')
|
||||
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'
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
#version 300 es
|
||||
#version 100
|
||||
// Copyright 2022 The Android Open Source Project
|
||||
//
|
||||
// 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.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
in vec4 aFramePosition;
|
||||
in vec4 aTexCoords;
|
||||
uniform mat4 uTexTransform;
|
||||
uniform mat4 uTransformationMatrix;
|
||||
out vec2 vTexCoords;
|
||||
|
||||
// ES 2 vertex shader that leaves the coordinates unchanged.
|
||||
|
||||
attribute vec4 aFramePosition;
|
||||
varying vec2 vTexSamplingCoord;
|
||||
void main() {
|
||||
gl_Position = uTransformationMatrix * aFramePosition;
|
||||
vTexCoords = (uTexTransform * aTexCoords).xy;
|
||||
gl_Position = aFramePosition;
|
||||
vTexSamplingCoord = vec2(aFramePosition.x * 0.5 + 0.5, aFramePosition.y * 0.5 + 0.5);
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -32,7 +32,11 @@ import android.widget.TextView;
|
|||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import com.google.android.exoplayer2.C;
|
||||
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.List;
|
||||
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 VIDEO_MIME_TYPE = "video_mime_type";
|
||||
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_Y = "scale_y";
|
||||
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 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 = {
|
||||
"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/BigBuckBunny_320x180.mp4",
|
||||
"https://html5demos.com/assets/dizzy.mp4",
|
||||
"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
|
||||
"MP4 with H264 video and AAC audio",
|
||||
"MP4 with H265 video and AAC audio",
|
||||
"Long MP4 with H264 video and AAC audio",
|
||||
"WebM with VP8 video and Vorbis audio",
|
||||
"720p H264 video and AAC audio",
|
||||
"1080p H265 video and AAC audio",
|
||||
"360p H264 video and AAC 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 float HALF_DIAGONAL = 1f / (float) Math.sqrt(2);
|
||||
|
||||
private @MonotonicNonNull Button chooseFileButton;
|
||||
private @MonotonicNonNull TextView chosenFileTextView;
|
||||
private @MonotonicNonNull Button selectFileButton;
|
||||
private @MonotonicNonNull TextView selectedFileTextView;
|
||||
private @MonotonicNonNull CheckBox removeAudioCheckbox;
|
||||
private @MonotonicNonNull CheckBox removeVideoCheckbox;
|
||||
private @MonotonicNonNull CheckBox flattenForSlowMotionCheckbox;
|
||||
private @MonotonicNonNull Spinner audioMimeSpinner;
|
||||
private @MonotonicNonNull Spinner videoMimeSpinner;
|
||||
private @MonotonicNonNull Spinner resolutionHeightSpinner;
|
||||
private @MonotonicNonNull Spinner translateSpinner;
|
||||
private @MonotonicNonNull Spinner scaleSpinner;
|
||||
private @MonotonicNonNull Spinner rotateSpinner;
|
||||
private @MonotonicNonNull CheckBox trimCheckBox;
|
||||
private @MonotonicNonNull CheckBox enableFallbackCheckBox;
|
||||
private @MonotonicNonNull CheckBox enableRequestSdrToneMappingCheckBox;
|
||||
private @MonotonicNonNull CheckBox enableHdrEditingCheckBox;
|
||||
private @MonotonicNonNull Button selectDemoEffectsButton;
|
||||
private boolean @MonotonicNonNull [] demoEffectsSelections;
|
||||
private int inputUriPosition;
|
||||
private long trimStartMs;
|
||||
private long trimEndMs;
|
||||
private float periodicVignetteCenterX;
|
||||
private float periodicVignetteCenterY;
|
||||
private float periodicVignetteInnerRadius;
|
||||
private float periodicVignetteOuterRadius;
|
||||
|
||||
@Override
|
||||
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
|
|
@ -90,11 +139,11 @@ public final class ConfigurationActivity extends AppCompatActivity {
|
|||
|
||||
findViewById(R.id.transform_button).setOnClickListener(this::startTransformation);
|
||||
|
||||
chooseFileButton = findViewById(R.id.choose_file_button);
|
||||
chooseFileButton.setOnClickListener(this::chooseFile);
|
||||
selectFileButton = findViewById(R.id.select_file_button);
|
||||
selectFileButton.setOnClickListener(this::selectFile);
|
||||
|
||||
chosenFileTextView = findViewById(R.id.chosen_file_text_view);
|
||||
chosenFileTextView.setText(URI_DESCRIPTIONS[inputUriPosition]);
|
||||
selectedFileTextView = findViewById(R.id.selected_file_text_view);
|
||||
selectedFileTextView.setText(URI_DESCRIPTIONS[inputUriPosition]);
|
||||
|
||||
removeAudioCheckbox = findViewById(R.id.remove_audio_checkbox);
|
||||
removeAudioCheckbox.setOnClickListener(this::onRemoveAudio);
|
||||
|
|
@ -118,11 +167,10 @@ public final class ConfigurationActivity extends AppCompatActivity {
|
|||
videoMimeSpinner = findViewById(R.id.video_mime_spinner);
|
||||
videoMimeSpinner.setAdapter(videoMimeAdapter);
|
||||
videoMimeAdapter.addAll(
|
||||
SAME_AS_INPUT_OPTION,
|
||||
MimeTypes.VIDEO_H263,
|
||||
MimeTypes.VIDEO_H264,
|
||||
MimeTypes.VIDEO_H265,
|
||||
MimeTypes.VIDEO_MP4V);
|
||||
SAME_AS_INPUT_OPTION, MimeTypes.VIDEO_H263, MimeTypes.VIDEO_H264, MimeTypes.VIDEO_MP4V);
|
||||
if (Util.SDK_INT >= 24) {
|
||||
videoMimeAdapter.add(MimeTypes.VIDEO_H265);
|
||||
}
|
||||
|
||||
ArrayAdapter<String> resolutionHeightAdapter =
|
||||
new ArrayAdapter<>(/* context= */ this, R.layout.spinner_item);
|
||||
|
|
@ -132,14 +180,6 @@ public final class ConfigurationActivity extends AppCompatActivity {
|
|||
resolutionHeightAdapter.addAll(
|
||||
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 =
|
||||
new ArrayAdapter<>(/* context= */ this, R.layout.spinner_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);
|
||||
rotateSpinner = findViewById(R.id.rotate_spinner);
|
||||
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);
|
||||
|
||||
demoEffectsSelections = new boolean[DEMO_EFFECTS.length];
|
||||
selectDemoEffectsButton = findViewById(R.id.select_demo_effects_button);
|
||||
selectDemoEffectsButton.setOnClickListener(this::selectDemoEffects);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -162,8 +215,8 @@ public final class ConfigurationActivity extends AppCompatActivity {
|
|||
super.onResume();
|
||||
@Nullable Uri intentUri = getIntent().getData();
|
||||
if (intentUri != null) {
|
||||
checkNotNull(chooseFileButton).setEnabled(false);
|
||||
checkNotNull(chosenFileTextView).setText(intentUri.toString());
|
||||
checkNotNull(selectFileButton).setEnabled(false);
|
||||
checkNotNull(selectedFileTextView).setText(intentUri.toString());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -180,13 +233,16 @@ public final class ConfigurationActivity extends AppCompatActivity {
|
|||
"audioMimeSpinner",
|
||||
"videoMimeSpinner",
|
||||
"resolutionHeightSpinner",
|
||||
"translateSpinner",
|
||||
"scaleSpinner",
|
||||
"rotateSpinner",
|
||||
"enableHdrEditingCheckBox"
|
||||
"trimCheckBox",
|
||||
"enableFallbackCheckBox",
|
||||
"enableRequestSdrToneMappingCheckBox",
|
||||
"enableHdrEditingCheckBox",
|
||||
"demoEffectsSelections"
|
||||
})
|
||||
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.putBoolean(SHOULD_REMOVE_AUDIO, removeAudioCheckbox.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)) {
|
||||
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());
|
||||
if (!SAME_AS_INPUT_OPTION.equals(selectedScale)) {
|
||||
List<String> scaleXY = Arrays.asList(selectedScale.split(", "));
|
||||
|
|
@ -221,7 +270,19 @@ public final class ConfigurationActivity extends AppCompatActivity {
|
|||
if (!SAME_AS_INPUT_OPTION.equals(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.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);
|
||||
|
||||
@Nullable Uri intentUri = getIntent().getData();
|
||||
|
|
@ -231,19 +292,82 @@ public final class ConfigurationActivity extends AppCompatActivity {
|
|||
startActivity(transformerIntent);
|
||||
}
|
||||
|
||||
private void chooseFile(View view) {
|
||||
private void selectFile(View view) {
|
||||
new AlertDialog.Builder(/* context= */ this)
|
||||
.setTitle(R.string.choose_file_title)
|
||||
.setTitle(R.string.select_file_title)
|
||||
.setSingleChoiceItems(URI_DESCRIPTIONS, inputUriPosition, this::selectFileInDialog)
|
||||
.setPositiveButton(android.R.string.ok, /* listener= */ null)
|
||||
.create()
|
||||
.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) {
|
||||
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({
|
||||
|
|
@ -251,10 +375,11 @@ public final class ConfigurationActivity extends AppCompatActivity {
|
|||
"audioMimeSpinner",
|
||||
"videoMimeSpinner",
|
||||
"resolutionHeightSpinner",
|
||||
"translateSpinner",
|
||||
"scaleSpinner",
|
||||
"rotateSpinner",
|
||||
"enableHdrEditingCheckBox"
|
||||
"enableRequestSdrToneMappingCheckBox",
|
||||
"enableHdrEditingCheckBox",
|
||||
"selectDemoEffectsButton"
|
||||
})
|
||||
private void onRemoveAudio(View view) {
|
||||
if (((CheckBox) view).isChecked()) {
|
||||
|
|
@ -270,10 +395,11 @@ public final class ConfigurationActivity extends AppCompatActivity {
|
|||
"audioMimeSpinner",
|
||||
"videoMimeSpinner",
|
||||
"resolutionHeightSpinner",
|
||||
"translateSpinner",
|
||||
"scaleSpinner",
|
||||
"rotateSpinner",
|
||||
"enableHdrEditingCheckBox"
|
||||
"enableRequestSdrToneMappingCheckBox",
|
||||
"enableHdrEditingCheckBox",
|
||||
"selectDemoEffectsButton"
|
||||
})
|
||||
private void onRemoveVideo(View view) {
|
||||
if (((CheckBox) view).isChecked()) {
|
||||
|
|
@ -288,26 +414,34 @@ public final class ConfigurationActivity extends AppCompatActivity {
|
|||
"audioMimeSpinner",
|
||||
"videoMimeSpinner",
|
||||
"resolutionHeightSpinner",
|
||||
"translateSpinner",
|
||||
"scaleSpinner",
|
||||
"rotateSpinner",
|
||||
"enableHdrEditingCheckBox"
|
||||
"enableRequestSdrToneMappingCheckBox",
|
||||
"enableHdrEditingCheckBox",
|
||||
"selectDemoEffectsButton"
|
||||
})
|
||||
private void enableTrackSpecificOptions(boolean isAudioEnabled, boolean isVideoEnabled) {
|
||||
audioMimeSpinner.setEnabled(isAudioEnabled);
|
||||
videoMimeSpinner.setEnabled(isVideoEnabled);
|
||||
resolutionHeightSpinner.setEnabled(isVideoEnabled);
|
||||
translateSpinner.setEnabled(isVideoEnabled);
|
||||
scaleSpinner.setEnabled(isVideoEnabled);
|
||||
rotateSpinner.setEnabled(isVideoEnabled);
|
||||
enableRequestSdrToneMappingCheckBox.setEnabled(
|
||||
isRequestSdrToneMappingSupported() && isVideoEnabled);
|
||||
enableHdrEditingCheckBox.setEnabled(isVideoEnabled);
|
||||
selectDemoEffectsButton.setEnabled(isVideoEnabled);
|
||||
|
||||
findViewById(R.id.audio_mime_text_view).setEnabled(isAudioEnabled);
|
||||
findViewById(R.id.video_mime_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.rotate).setEnabled(isVideoEnabled);
|
||||
findViewById(R.id.request_sdr_tone_mapping)
|
||||
.setEnabled(isRequestSdrToneMappingSupported() && isVideoEnabled);
|
||||
findViewById(R.id.hdr_editing).setEnabled(isVideoEnabled);
|
||||
}
|
||||
|
||||
private static boolean isRequestSdrToneMappingSupported() {
|
||||
return Util.SDK_INT >= 31;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -19,9 +19,9 @@ import static android.Manifest.permission.READ_EXTERNAL_STORAGE;
|
|||
import static com.google.android.exoplayer2.util.Assertions.checkNotNull;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.graphics.Matrix;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
|
|
@ -32,13 +32,19 @@ import android.view.ViewGroup;
|
|||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.StringRes;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import com.google.android.exoplayer2.C;
|
||||
import com.google.android.exoplayer2.ExoPlayer;
|
||||
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.SingleFrameGlTextureProcessor;
|
||||
import com.google.android.exoplayer2.transformer.TransformationException;
|
||||
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.ui.AspectRatioFrameLayout;
|
||||
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.common.base.Stopwatch;
|
||||
import com.google.common.base.Ticker;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
|
|
@ -145,9 +153,10 @@ public final class TransformerActivity extends AppCompatActivity {
|
|||
externalCacheFile = createExternalCacheFile("transformer-output.mp4");
|
||||
String filePath = externalCacheFile.getAbsolutePath();
|
||||
@Nullable Bundle bundle = intent.getExtras();
|
||||
MediaItem mediaItem = createMediaItem(bundle, uri);
|
||||
Transformer transformer = createTransformer(bundle, filePath);
|
||||
transformationStopwatch.start();
|
||||
transformer.startTransformation(MediaItem.fromUri(uri), filePath);
|
||||
transformer.startTransformation(mediaItem, filePath);
|
||||
this.transformer = transformer;
|
||||
} catch (IOException 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.
|
||||
private File createExternalCacheFile(String fileName) throws IOException {
|
||||
File file = new File(getExternalCacheDir(), fileName);
|
||||
|
|
@ -214,22 +241,91 @@ public final class TransformerActivity extends AppCompatActivity {
|
|||
if (resolutionHeight != C.LENGTH_UNSET) {
|
||||
requestBuilder.setResolution(resolutionHeight);
|
||||
}
|
||||
Matrix transformationMatrix = getTransformationMatrix(bundle);
|
||||
if (!transformationMatrix.isIdentity()) {
|
||||
requestBuilder.setTransformationMatrix(transformationMatrix);
|
||||
}
|
||||
|
||||
float scaleX = bundle.getFloat(ConfigurationActivity.SCALE_X, /* defaultValue= */ 1);
|
||||
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(
|
||||
bundle.getBoolean(ConfigurationActivity.ENABLE_HDR_EDITING));
|
||||
transformerBuilder
|
||||
.setTransformationRequest(requestBuilder.build())
|
||||
.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
|
||||
.addListener(
|
||||
new Transformer.Listener() {
|
||||
@Override
|
||||
public void onTransformationCompleted(MediaItem mediaItem) {
|
||||
public void onTransformationCompleted(
|
||||
MediaItem mediaItem, TransformationResult transformationResult) {
|
||||
TransformerActivity.this.onTransformationCompleted(filePath);
|
||||
}
|
||||
|
||||
|
|
@ -243,26 +339,6 @@ public final class TransformerActivity extends AppCompatActivity {
|
|||
.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({
|
||||
"informationTextView",
|
||||
"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 {
|
||||
|
||||
@Nullable
|
||||
|
|
|
|||
|
|
@ -34,18 +34,18 @@
|
|||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
<Button
|
||||
android:id="@+id/choose_file_button"
|
||||
android:id="@+id/select_file_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/choose_file_title"
|
||||
android:text="@string/select_file_title"
|
||||
app:layout_constraintTop_toBottomOf="@+id/configuration_text_view"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent" />
|
||||
<TextView
|
||||
android:id="@+id/chosen_file_text_view"
|
||||
android:id="@+id/selected_file_text_view"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
|
|
@ -57,14 +57,14 @@
|
|||
android:gravity="center"
|
||||
app:layout_constraintEnd_toEndOf="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
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="0dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/chosen_file_text_view"
|
||||
app:layout_constraintBottom_toTopOf="@+id/transform_button">
|
||||
app:layout_constraintTop_toBottomOf="@+id/selected_file_text_view"
|
||||
app:layout_constraintBottom_toTopOf="@+id/select_demo_effects_button">
|
||||
<TableLayout
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
|
|
@ -137,17 +137,6 @@
|
|||
android:layout_gravity="right|center_vertical"
|
||||
android:gravity="right" />
|
||||
</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
|
||||
android:layout_weight="1"
|
||||
android:gravity="center_vertical" >
|
||||
|
|
@ -170,6 +159,36 @@
|
|||
android:layout_gravity="right|center_vertical"
|
||||
android:gravity="right" />
|
||||
</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
|
||||
android:layout_weight="1"
|
||||
android:gravity="center_vertical" >
|
||||
|
|
@ -182,6 +201,17 @@
|
|||
</TableRow>
|
||||
</TableLayout>
|
||||
</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
|
||||
android:id="@+id/transform_button"
|
||||
android:layout_width="wrap_content"
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
48
demos/transformer/src/main/res/layout/trim_options.xml
Normal file
48
demos/transformer/src/main/res/layout/trim_options.xml
Normal 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>
|
||||
|
|
@ -17,22 +17,31 @@
|
|||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="app_name" translatable="false">Transformer Demo</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_video" translatable="false">Remove video</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="video_mime" translatable="false">Output video MIME type</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="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="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_not_available" translatable="false">No debug preview available.</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_completed" translatable="false">Transformation completed in %d seconds.</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>
|
||||
|
|
|
|||
19
demos/transformer/src/withMediaPipe/AndroidManifest.xml
Normal file
19
demos/transformer/src/withMediaPipe/AndroidManifest.xml
Normal 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>
|
||||
|
|
@ -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"
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -51,8 +51,8 @@ build and inject a `DefaultMediaSourceFactory` configured with an
|
|||
~~~
|
||||
MediaSource.Factory mediaSourceFactory =
|
||||
new DefaultMediaSourceFactory(context)
|
||||
.setAdsLoaderProvider(adsLoaderProvider)
|
||||
.setAdViewProvider(playerView);
|
||||
.setLocalAdInsertionComponents(
|
||||
adsLoaderProvider, /* adViewProvider= */ playerView);
|
||||
ExoPlayer player = new ExoPlayer.Builder(context)
|
||||
.setMediaSourceFactory(mediaSourceFactory)
|
||||
.build();
|
||||
|
|
@ -220,7 +220,7 @@ server-side ad insertion `MediaSource` for URIs using the `ssai://` scheme:
|
|||
Player player =
|
||||
new ExoPlayer.Builder(context)
|
||||
.setMediaSourceFactory(
|
||||
new DefaultMediaSourceFactory(dataSourceFactory)
|
||||
new DefaultMediaSourceFactory(context)
|
||||
.setServerSideAdInsertionMediaSourceFactory(ssaiFactory))
|
||||
.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.
|
||||
DefaultMediaSourceFactory defaultMediaSourceFactory =
|
||||
new DefaultMediaSourceFactory(dataSourceFactory);
|
||||
new DefaultMediaSourceFactory(context);
|
||||
// AdsLoader that can be reused for multiple playbacks.
|
||||
ImaServerSideAdInsertionMediaSource.AdsLoader adsLoader =
|
||||
new ImaServerSideAdInsertionMediaSource.AdsLoader.Builder(context, adViewProvider)
|
||||
|
|
@ -267,7 +267,10 @@ with `ImaServerSideAdInsertionUriBuilder`:
|
|||
|
||||
```
|
||||
Uri ssaiUri =
|
||||
new ImaServerSideAdInsertionUriBuilder().setAssetKey(assetKey).build();
|
||||
new ImaServerSideAdInsertionUriBuilder()
|
||||
.setAssetKey(assetKey)
|
||||
.setFormat(C.TYPE_HLS)
|
||||
.build();
|
||||
player.setMediaItem(MediaItem.fromUri(ssaiUri));
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -43,32 +43,7 @@ described below.
|
|||
|
||||
### Configuring the network stack ###
|
||||
|
||||
ExoPlayer supports Android's default network stack, as well as Cronet and
|
||||
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.
|
||||
We have a page about [customizing the network stack used by ExoPlayer].
|
||||
|
||||
### Caching data loaded from the network ###
|
||||
|
||||
|
|
@ -84,7 +59,8 @@ DataSource.Factory cacheDataSourceFactory =
|
|||
|
||||
ExoPlayer player = new ExoPlayer.Builder(context)
|
||||
.setMediaSourceFactory(
|
||||
new DefaultMediaSourceFactory(cacheDataSourceFactory))
|
||||
new DefaultMediaSourceFactory(context)
|
||||
.setDataSourceFactory(cacheDataSourceFactory))
|
||||
.build();
|
||||
~~~
|
||||
{: .language-java}
|
||||
|
|
@ -108,7 +84,9 @@ DataSource.Factory dataSourceFactory = () -> {
|
|||
};
|
||||
|
||||
ExoPlayer player = new ExoPlayer.Builder(context)
|
||||
.setMediaSourceFactory(new DefaultMediaSourceFactory(dataSourceFactory))
|
||||
.setMediaSourceFactory(
|
||||
new DefaultMediaSourceFactory(context)
|
||||
.setDataSourceFactory(dataSourceFactory))
|
||||
.build();
|
||||
~~~
|
||||
{: .language-java}
|
||||
|
|
@ -206,6 +184,56 @@ DefaultExtractorsFactory extractorsFactory =
|
|||
The `ExtractorsFactory` can then be injected via `DefaultMediaSourceFactory` as
|
||||
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 ##
|
||||
|
||||
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
|
||||
performed on the player.
|
||||
|
||||
[Cronet extension]: https://github.com/google/ExoPlayer/tree/release-v2/extensions/cronet
|
||||
[OkHttp extension]: https://github.com/google/ExoPlayer/tree/release-v2/extensions/okhttp
|
||||
[customizing the network stack used by ExoPlayer]: {{ site.baseurl }}/network-stacks.html
|
||||
[LoadErrorHandlingPolicy]: {{ site.exo_sdk }}/upstream/LoadErrorHandlingPolicy.html
|
||||
[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
|
||||
|
||||
|
|
|
|||
|
|
@ -9,13 +9,10 @@ issues. `EventLogger` implements `AnalyticsListener`, so registering an instance
|
|||
with an `ExoPlayer` is easy:
|
||||
|
||||
```
|
||||
player.addAnalyticsListener(new EventLogger(trackSelector));
|
||||
player.addAnalyticsListener(new EventLogger());
|
||||
```
|
||||
{: .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
|
||||
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
|
||||
|
|
@ -80,20 +77,16 @@ logging for an adaptive stream:
|
|||
|
||||
```
|
||||
EventLogger: tracks [eventTime=0.30, mediaPos=0.00, window=0, period=0,
|
||||
EventLogger: MediaCodecVideoRenderer [
|
||||
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: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: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: ]
|
||||
EventLogger: group [
|
||||
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: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: [ ] Track:4, id=136, mimeType=video/avc, bitrate=2400538, codecs=avc1.4d401f, res=1280x720, fps=30.0, supported=NO_EXCEEDS_CAPABILITIES
|
||||
EventLogger: ]
|
||||
EventLogger: MediaCodecAudioRenderer [
|
||||
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: [X] Track:1, id=140, mimeType=audio/mp4a-latm, bitrate=127868, codecs=mp4a.40.2, channels=2, sample_rate=44100, supported=YES
|
||||
EventLogger: ]
|
||||
EventLogger: group [
|
||||
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: ]
|
||||
EventLogger: ]
|
||||
```
|
||||
|
|
|
|||
|
|
@ -319,7 +319,8 @@ DataSource.Factory cacheDataSourceFactory =
|
|||
|
||||
ExoPlayer player = new ExoPlayer.Builder(context)
|
||||
.setMediaSourceFactory(
|
||||
new DefaultMediaSourceFactory(cacheDataSourceFactory))
|
||||
new DefaultMediaSourceFactory(context)
|
||||
.setDataSourceFactory(cacheDataSourceFactory))
|
||||
.build();
|
||||
~~~
|
||||
{: .language-java}
|
||||
|
|
|
|||
|
|
@ -94,11 +94,18 @@ DrmSessionManager customDrmSessionManager =
|
|||
new CustomDrmSessionManager(/* ... */);
|
||||
// Pass a drm session manager provider to the media source factory.
|
||||
MediaSource.Factory mediaSourceFactory =
|
||||
new DefaultMediaSourceFactory(dataSourceFactory)
|
||||
new DefaultMediaSourceFactory(context)
|
||||
.setDrmSessionManagerProvider(mediaItem -> customDrmSessionManager);
|
||||
~~~
|
||||
{: .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
|
||||
[`MediaDrm`]: {{ site.android_sdk }}/android/media/MediaDrm.html
|
||||
[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
|
||||
|
|
|
|||
|
|
@ -88,8 +88,6 @@ public void onPlayerError(PlaybackException error) {
|
|||
if (cause instanceof HttpDataSourceException) {
|
||||
// An HTTP error occurred.
|
||||
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
|
||||
// querying the cause.
|
||||
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.
|
||||
|
||||
```
|
||||
player.addAnalyticsListener(new EventLogger(trackSelector));
|
||||
player.addAnalyticsListener(new EventLogger());
|
||||
```
|
||||
{: .language-java }
|
||||
|
||||
Passing the `trackSelector` enables additional logging, but is optional and so
|
||||
`null` can be passed instead. See the [debug logging page][] for more details.
|
||||
See the [debug logging page][] for more details.
|
||||
|
||||
## Firing events at specified playback positions ##
|
||||
|
||||
|
|
|
|||
|
|
@ -16,13 +16,6 @@ and can only be played at one position. The documentation on this page is only
|
|||
relevant to adaptive live streams.
|
||||
{:.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 ##
|
||||
|
||||
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 ##
|
||||
|
||||
By default, ExoPlayer uses live playback parameters defined by the media. If you
|
||||
want to configure the live playback parameters yourself, you can set them on a
|
||||
per `MediaItem` basis by calling `MediaItem.Builder.setLiveConfiguration`. If
|
||||
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
|
||||
values will override parameters defined by the media.
|
||||
ExoPlayer uses some parameters to control the offset of the playback position
|
||||
from the live edge, and the range of playback speeds that can be used to
|
||||
adjust this offset.
|
||||
|
||||
ExoPlayer gets values for these parameters from three places, in descending
|
||||
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.
|
||||
|
|
@ -130,40 +127,31 @@ Available configuration values are:
|
|||
* `maxPlaybackSpeed`: The maximum playback speed the player can use to catch up
|
||||
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
|
||||
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
|
||||
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}
|
||||
|
||||
## 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:
|
||||
If speed adjustment is enabled, a `LivePlaybackSpeedControl` defines what
|
||||
adjustments are made. 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 =
|
||||
|
|
@ -195,6 +183,30 @@ Relevant customization parameters of `DefaultLivePlaybackSpeedControl` are:
|
|||
a lower value means the estimation will adjust faster at a higher risk of
|
||||
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
|
||||
[default UI components]: {{ site.baseurl }}/ui-components.html
|
||||
[pending feature request (#2213)]: https://github.com/google/ExoPlayer/issues/2213
|
||||
|
|
|
|||
|
|
@ -34,9 +34,10 @@ these requirements and injected during player construction:
|
|||
|
||||
~~~
|
||||
MediaSource.Factory mediaSourceFactory =
|
||||
new DefaultMediaSourceFactory(cacheDataSourceFactory)
|
||||
.setAdsLoaderProvider(adsLoaderProvider)
|
||||
.setAdViewProvider(playerView);
|
||||
new DefaultMediaSourceFactory(context)
|
||||
.setDataSourceFactory(cacheDataSourceFactory)
|
||||
.setLocalAdInsertionComponents(
|
||||
adsLoaderProvider, /* adViewProvider= */ playerView);
|
||||
ExoPlayer player = new ExoPlayer.Builder(context)
|
||||
.setMediaSourceFactory(mediaSourceFactory)
|
||||
.build();
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
~~~
|
||||
new DefaultDataSourceFactory(
|
||||
new DefaultDataSource.Factory(
|
||||
...
|
||||
/* baseDataSourceFactory= */ new PreferredHttpDataSource.Factory(...));
|
||||
~~~
|
||||
{: .language-java}
|
||||
|
||||
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.
|
||||
|
||||
The example below shows how to build an `ExoPlayer` that will use the Cronet
|
||||
|
|
@ -48,10 +48,12 @@ DefaultDataSource.Factory dataSourceFactory =
|
|||
context,
|
||||
/* baseDataSourceFactory= */ cronetDataSourceFactory);
|
||||
|
||||
// Inject the DefaultDataSourceFactory when creating the player.
|
||||
// Inject the DefaultDataSource.Factory when creating the player.
|
||||
ExoPlayer player =
|
||||
new ExoPlayer.Builder(context)
|
||||
.setMediaSourceFactory(new DefaultMediaSourceFactory(dataSourceFactory))
|
||||
.setMediaSourceFactory(
|
||||
new DefaultMediaSourceFactory(context)
|
||||
.setDataSourceFactory(dataSourceFactory))
|
||||
.build();
|
||||
~~~
|
||||
{: .language-java}
|
||||
|
|
|
|||
|
|
@ -2,50 +2,63 @@
|
|||
title: Track selection
|
||||
---
|
||||
|
||||
Track selection determines which of the available media tracks are played by the
|
||||
player. This process is configured by [`TrackSelectionParameters`][], which
|
||||
support many different options to specify constraints and overrides.
|
||||
When a media item contains multiple tracks, track selection is the process that
|
||||
determines which of them are chosen for playback. The track selection process is
|
||||
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
|
||||
selection. You can listen to `Player.Listener.onTracksInfoChanged` to get
|
||||
notified about changes, which may happen
|
||||
* When preparation completes
|
||||
* When the available or selected tracks change
|
||||
* When the playlist item changes
|
||||
You can listen to `Player.Listener.onTracksChanged` to be notified about changes
|
||||
to tracks, including:
|
||||
|
||||
* The available tracks becoming known when preparation of the media item being
|
||||
played completes. Note that the player needs to prepare a media item to know
|
||||
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() {
|
||||
@Override
|
||||
public void onTracksInfoChanged(TracksInfo tracksInfo) {
|
||||
// Update UI using current TracksInfo.
|
||||
public void onTracksChanged(Tracks tracks) {
|
||||
// Update UI using current tracks.
|
||||
}
|
||||
});
|
||||
~~~
|
||||
{: .language-java}
|
||||
|
||||
You can also retrieve the current `TracksInfo` by calling
|
||||
`player.getCurrentTracksInfo()`.
|
||||
You can also query the current tracks by calling `player.getCurrentTracks()`.
|
||||
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
|
||||
track type, format details, player support and selection status of each
|
||||
available track. Tracks are grouped together into one `TrackGroup` if they
|
||||
represent the same content that can be used interchangeably by the player (for
|
||||
example, all audio tracks of a single language, but with different bitrates).
|
||||
As an example of how tracks can be grouped, consider an adaptive playback where
|
||||
a main video feed is provided in five bitrates, and an alternative video feed
|
||||
(e.g., a different camera angle in a sports match) is provided in two bitrates.
|
||||
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.
|
||||
|
||||
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.
|
||||
@C.TrackType int trackType = groupInfo.getTrackType();
|
||||
boolean trackInGroupIsSelected = groupInfo.isSelected();
|
||||
boolean trackInGroupIsSupported = groupInfo.isSupported();
|
||||
TrackGroup group = groupInfo.getTrackGroup();
|
||||
for (int i = 0; i < group.length; i++) {
|
||||
@C.TrackType int trackType = trackGroup.getTrackType();
|
||||
boolean trackInGroupIsSelected = trackGroup.isSelected();
|
||||
boolean trackInGroupIsSupported = trackGroup.isSupported();
|
||||
for (int i = 0; i < trackGroup.length; i++) {
|
||||
// Individual track information.
|
||||
boolean isSupported = groupInfo.isTrackSupported(i);
|
||||
boolean isSelected = groupInfo.isTrackSelected(i);
|
||||
Format trackFormat = group.getFormat(i);
|
||||
boolean isSupported = trackGroup.isTrackSupported(i);
|
||||
boolean isSelected = trackGroup.isTrackSelected(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
|
||||
supported individually and the player is not necessarily able to play them at
|
||||
the same time.
|
||||
* A track is 'selected' if the track selector chose this track for playback
|
||||
using the current `TrackSelectionParameters`. If multiple tracks within one
|
||||
track group are selected, the player uses these tracks for adaptive playback
|
||||
(for example, multiple video tracks with different bitrates). Note that only
|
||||
one of these tracks will be played at any one time. If you want to be notified
|
||||
of in-playback changes to the adaptive video track you can listen to
|
||||
`Player.Listener.onVideoSizeChanged`.
|
||||
* A track is 'selected' if it has been chosen for playback given the current
|
||||
`TrackSelectionParameters`. If multiple tracks within one track group are
|
||||
selected, the player uses these tracks for adaptive playback (for example,
|
||||
multiple video tracks with different bitrates). Note that only one of these
|
||||
tracks will be played at any one time.
|
||||
|
||||
## Modifying track selection parameters
|
||||
|
||||
The selection process can be configured by setting `TrackSelectionParameters` on
|
||||
the `Player` with `Player.setTrackSelectionParameters`. These updates can be
|
||||
done before and during playback. In most cases, it's advisable to obtain the
|
||||
current parameters and only modify the required aspects with the
|
||||
`TrackSelectionParameters.Builder`. The builder class also allows chaining to
|
||||
specify multiple options with one command:
|
||||
The track selection process can be configured using
|
||||
`Player.setTrackSelectionParameters`. This can be done both before and during
|
||||
playback. The example below demonstrates how to obtain the current
|
||||
`TrackSelectionParameters` from the player, modify them, and update the `Player`
|
||||
with the modified result:
|
||||
|
||||
~~~
|
||||
player.setTrackSelectionParameters(
|
||||
|
|
@ -86,84 +96,84 @@ player.setTrackSelectionParameters(
|
|||
### Constraint based track selection
|
||||
|
||||
Most options in `TrackSelectionParameters` allow you to specify constraints,
|
||||
which are independent of the tracks that are actually available. Typical
|
||||
constraints are:
|
||||
which are independent of the tracks that are actually available. Available
|
||||
constraints include:
|
||||
|
||||
* Maximum or minimum video width, height, frame rate, or bitrate.
|
||||
* Maximum audio channel count or bitrate.
|
||||
* Preferred MIME types for video or audio.
|
||||
* Preferred audio languages or role flags.
|
||||
* Preferred text languages or role flags.
|
||||
* Maximum and minimum video width, height, frame rate, and bitrate.
|
||||
* Maximum audio channel count and bitrate.
|
||||
* Preferred MIME types for video and audio.
|
||||
* Preferred audio languages and role flags.
|
||||
* Preferred text languages and role flags.
|
||||
|
||||
Note that ExoPlayer already applies sensible defaults for most of these values,
|
||||
for example restricting video resolution to the display size or preferring the
|
||||
audio language that matches the user's system Locale setting.
|
||||
ExoPlayer uses sensible defaults for these constraints, for example restricting
|
||||
video resolution to the display size and preferring the audio language that
|
||||
matches the user's system Locale setting.
|
||||
|
||||
There are several benefits to using constraint based track selection instead of
|
||||
specifying specific tracks directly:
|
||||
There are several benefits to using constraint based track selection rather than
|
||||
selecting specific tracks from those that are available:
|
||||
|
||||
* You can specify constraints before knowing what tracks the media provides.
|
||||
This allows to immediately select the appropriate tracks for faster startup
|
||||
time and also simplifies track selection code as you don't have to listen for
|
||||
changes in the available tracks.
|
||||
* Constraints can be applied consistently across all items in a playlist. For
|
||||
example, selecting an audio language based on user preference will
|
||||
automatically apply to the next playlist item too, whereas overriding a
|
||||
specific track will only apply to the current playlist item for which the
|
||||
track exists.
|
||||
* You can specify constraints before knowing what tracks a media item provides.
|
||||
This means that constraints can be specified before the player has prepared a
|
||||
media item, whereas selecting specific tracks requires application code to
|
||||
wait until the available tracks become known.
|
||||
* Constraints are applied for all media items in a playlist, even when those
|
||||
items have different available tracks. For example, a preferred audio language
|
||||
constraint will be automatically applied for all media items, even if the
|
||||
`Format` of the track in that language varies from one media item to the next.
|
||||
This is not the case when selecting specific tracks, as described below.
|
||||
|
||||
### Selecting specific tracks
|
||||
|
||||
It's possible to specify specific tracks in `TrackSelectionParameters` that
|
||||
should be selected for the current set of tracks. Note that a change in the
|
||||
available tracks, for example when changing items in a playlist, will also
|
||||
invalidate such a track override.
|
||||
|
||||
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.
|
||||
It's possible to select specific tracks using `TrackSelectionParameters`. First,
|
||||
the player's currently available tracks should be queried using
|
||||
`Player.getCurrentTracks`. Second, having identified which tracks to select,
|
||||
they can be set on `TrackSelectionParameters` using a `TrackSelectionOverride`.
|
||||
For example, to select the first track from a specific `audioTrackGroup`:
|
||||
|
||||
~~~
|
||||
player.setTrackSelectionParameters(
|
||||
player.getTrackSelectionParameters()
|
||||
.buildUpon()
|
||||
.setDisabledTrackTypes(ImmutableSet.of(C.TRACK_TYPE_VIDEO))
|
||||
.setOverrideForType(
|
||||
new TrackSelectionOverride(
|
||||
audioTrackGroup.getMediaTrackGroup(),
|
||||
/* trackIndex= */ 0))
|
||||
.build());
|
||||
~~~
|
||||
{: .language-java}
|
||||
|
||||
Alternatively, it's possible to prevent the selection of track groups for the
|
||||
current playlist item only by specifying empty overrides for these groups:
|
||||
A `TrackSelectionOverride` will only apply to media items that contain a
|
||||
`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.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}
|
||||
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@ app is notified of events via the listener passed to the `Transformer` builder.
|
|||
Transformer.Listener transformerListener =
|
||||
new Transformer.Listener() {
|
||||
@Override
|
||||
public void onTransformationCompleted(MediaItem inputMediaItem) {
|
||||
public void onTransformationCompleted(MediaItem inputMediaItem, TransformationResult transformationResult) {
|
||||
playOutput();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ redirect_from:
|
|||
* [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?][]
|
||||
* [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
|
||||
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 "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
|
||||
|
|
@ -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
|
||||
[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]: #video-playback-is-stuttering
|
||||
|
||||
[Supported formats]: {{ site.baseurl }}/supported-formats.html
|
||||
[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
|
||||
[`LibraryLoader`]: {{ site.exo_sdk }}/util/LibraryLoader.html
|
||||
[`EventLogger`]: {{ site.baseurl }}/debug-logging.html
|
||||
[enabling asynchronous buffer queueing]: {{ site.baseurl }}/customization.html#enabling-asynchronous-buffer-queueing
|
||||
|
|
|
|||
|
|
@ -37,19 +37,15 @@ import com.google.android.exoplayer2.PlaybackException;
|
|||
import com.google.android.exoplayer2.PlaybackParameters;
|
||||
import com.google.android.exoplayer2.Player;
|
||||
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.source.TrackGroup;
|
||||
import com.google.android.exoplayer2.source.TrackGroupArray;
|
||||
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.text.CueGroup;
|
||||
import com.google.android.exoplayer2.trackselection.TrackSelectionParameters;
|
||||
import com.google.android.exoplayer2.util.Assertions;
|
||||
import com.google.android.exoplayer2.util.Clock;
|
||||
import com.google.android.exoplayer2.util.ListenerSet;
|
||||
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.video.VideoSize;
|
||||
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.common.collect.ImmutableList;
|
||||
import java.util.List;
|
||||
import org.checkerframework.checker.nullness.compatqual.NullableType;
|
||||
import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
||||
|
||||
/**
|
||||
|
|
@ -105,7 +100,7 @@ public final class CastPlayer extends BasePlayer {
|
|||
COMMAND_GET_MEDIA_ITEMS_METADATA,
|
||||
COMMAND_SET_MEDIA_ITEMS_METADATA,
|
||||
COMMAND_CHANGE_MEDIA_ITEMS,
|
||||
COMMAND_GET_TRACK_INFOS)
|
||||
COMMAND_GET_TRACKS)
|
||||
.build();
|
||||
|
||||
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 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 TrackSelectionArray EMPTY_TRACK_SELECTION_ARRAY =
|
||||
new TrackSelectionArray(null, null, null);
|
||||
private static final long[] EMPTY_TRACK_ID_ARRAY = new long[0];
|
||||
|
||||
private final CastContext castContext;
|
||||
|
|
@ -144,9 +133,7 @@ public final class CastPlayer extends BasePlayer {
|
|||
private final StateHolder<PlaybackParameters> playbackParameters;
|
||||
@Nullable private RemoteMediaClient remoteMediaClient;
|
||||
private CastTimeline currentTimeline;
|
||||
private TrackGroupArray currentTrackGroups;
|
||||
private TrackSelectionArray currentTrackSelection;
|
||||
private TracksInfo currentTracksInfo;
|
||||
private Tracks currentTracks;
|
||||
private Commands availableCommands;
|
||||
private @Player.State int playbackState;
|
||||
private int currentWindowIndex;
|
||||
|
|
@ -222,9 +209,7 @@ public final class CastPlayer extends BasePlayer {
|
|||
playbackParameters = new StateHolder<>(PlaybackParameters.DEFAULT);
|
||||
playbackState = STATE_IDLE;
|
||||
currentTimeline = CastTimeline.EMPTY_CAST_TIMELINE;
|
||||
currentTrackGroups = TrackGroupArray.EMPTY;
|
||||
currentTrackSelection = EMPTY_TRACK_SELECTION_ARRAY;
|
||||
currentTracksInfo = TracksInfo.EMPTY;
|
||||
currentTracks = Tracks.EMPTY;
|
||||
availableCommands = new Commands.Builder().addAll(PERMANENT_AVAILABLE_COMMANDS).build();
|
||||
pendingSeekWindowIndex = C.INDEX_UNSET;
|
||||
pendingSeekPositionMs = C.TIME_UNSET;
|
||||
|
|
@ -471,6 +456,11 @@ public final class CastPlayer extends BasePlayer {
|
|||
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
|
||||
@Override
|
||||
public void stop(boolean reset) {
|
||||
|
|
@ -556,18 +546,8 @@ public final class CastPlayer extends BasePlayer {
|
|||
}
|
||||
|
||||
@Override
|
||||
public TrackGroupArray getCurrentTrackGroups() {
|
||||
return currentTrackGroups;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TrackSelectionArray getCurrentTrackSelections() {
|
||||
return currentTrackSelection;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TracksInfo getCurrentTracksInfo() {
|
||||
return currentTracksInfo;
|
||||
public Tracks getCurrentTracks() {
|
||||
return currentTracks;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -728,10 +708,10 @@ public final class CastPlayer extends BasePlayer {
|
|||
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
|
||||
public ImmutableList<Cue> getCurrentCues() {
|
||||
return ImmutableList.of();
|
||||
public CueGroup getCurrentCues() {
|
||||
return CueGroup.EMPTY;
|
||||
}
|
||||
|
||||
/** This method is not supported and always returns {@link DeviceInfo#UNKNOWN}. */
|
||||
|
|
@ -840,10 +820,7 @@ public final class CastPlayer extends BasePlayer {
|
|||
}
|
||||
if (updateTracksAndSelectionsAndNotifyIfChanged()) {
|
||||
listeners.queueEvent(
|
||||
Player.EVENT_TRACKS_CHANGED,
|
||||
listener -> listener.onTracksChanged(currentTrackGroups, currentTrackSelection));
|
||||
listeners.queueEvent(
|
||||
Player.EVENT_TRACKS_CHANGED, listener -> listener.onTracksInfoChanged(currentTracksInfo));
|
||||
Player.EVENT_TRACKS_CHANGED, listener -> listener.onTracksChanged(currentTracks));
|
||||
}
|
||||
updateAvailableCommandsAndNotifyIfChanged();
|
||||
listeners.flushEvents();
|
||||
|
|
@ -998,55 +975,33 @@ public final class CastPlayer extends BasePlayer {
|
|||
return false;
|
||||
}
|
||||
|
||||
MediaStatus mediaStatus = getMediaStatus();
|
||||
MediaInfo mediaInfo = mediaStatus != null ? mediaStatus.getMediaInfo() : null;
|
||||
@Nullable MediaStatus mediaStatus = getMediaStatus();
|
||||
@Nullable MediaInfo mediaInfo = mediaStatus != null ? mediaStatus.getMediaInfo() : null;
|
||||
@Nullable
|
||||
List<MediaTrack> castMediaTracks = mediaInfo != null ? mediaInfo.getMediaTracks() : null;
|
||||
if (castMediaTracks == null || castMediaTracks.isEmpty()) {
|
||||
boolean hasChanged = !currentTrackGroups.isEmpty();
|
||||
currentTrackGroups = TrackGroupArray.EMPTY;
|
||||
currentTrackSelection = EMPTY_TRACK_SELECTION_ARRAY;
|
||||
currentTracksInfo = TracksInfo.EMPTY;
|
||||
boolean hasChanged = !Tracks.EMPTY.equals(currentTracks);
|
||||
currentTracks = Tracks.EMPTY;
|
||||
return hasChanged;
|
||||
}
|
||||
long[] activeTrackIds = mediaStatus.getActiveTrackIds();
|
||||
@Nullable long[] activeTrackIds = mediaStatus.getActiveTrackIds();
|
||||
if (activeTrackIds == null) {
|
||||
activeTrackIds = EMPTY_TRACK_ID_ARRAY;
|
||||
}
|
||||
|
||||
TrackGroup[] trackGroups = new TrackGroup[castMediaTracks.size()];
|
||||
@NullableType TrackSelection[] trackSelections = new TrackSelection[RENDERER_COUNT];
|
||||
TracksInfo.TrackGroupInfo[] trackGroupInfos =
|
||||
new TracksInfo.TrackGroupInfo[castMediaTracks.size()];
|
||||
Tracks.Group[] trackGroups = new Tracks.Group[castMediaTracks.size()];
|
||||
for (int i = 0; i < castMediaTracks.size(); i++) {
|
||||
MediaTrack mediaTrack = castMediaTracks.get(i);
|
||||
trackGroups[i] =
|
||||
TrackGroup trackGroup =
|
||||
new TrackGroup(/* id= */ Integer.toString(i), CastUtils.mediaTrackToFormat(mediaTrack));
|
||||
|
||||
long id = mediaTrack.getId();
|
||||
@C.TrackType int trackType = MimeTypes.getTrackType(mediaTrack.getContentType());
|
||||
int rendererIndex = getRendererIndexForTrackType(trackType);
|
||||
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);
|
||||
@C.FormatSupport int[] trackSupport = new int[] {C.FORMAT_HANDLED};
|
||||
boolean[] trackSelected = new boolean[] {isTrackActive(mediaTrack.getId(), activeTrackIds)};
|
||||
trackGroups[i] =
|
||||
new Tracks.Group(trackGroup, /* adaptiveSupported= */ false, trackSupport, trackSelected);
|
||||
}
|
||||
TrackGroupArray newTrackGroups = new TrackGroupArray(trackGroups);
|
||||
TrackSelectionArray newTrackSelections = new TrackSelectionArray(trackSelections);
|
||||
TracksInfo newTracksInfo = new TracksInfo(ImmutableList.copyOf(trackGroupInfos));
|
||||
|
||||
if (!newTrackGroups.equals(currentTrackGroups)
|
||||
|| !newTrackSelections.equals(currentTrackSelection)
|
||||
|| !newTracksInfo.equals(currentTracksInfo)) {
|
||||
currentTrackSelection = newTrackSelections;
|
||||
currentTrackGroups = newTrackGroups;
|
||||
currentTracksInfo = newTracksInfo;
|
||||
Tracks newTracks = new Tracks(ImmutableList.copyOf(trackGroups));
|
||||
if (!newTracks.equals(currentTracks)) {
|
||||
currentTracks = newTracks;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
|
@ -1304,14 +1259,6 @@ public final class CastPlayer extends BasePlayer {
|
|||
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) {
|
||||
switch (repeatMode) {
|
||||
case REPEAT_MODE_ONE:
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
application code to use `CronetDataSource.Factory`. If your application also
|
||||
needs to play non-http(s) content such as local files, use:
|
||||
|
||||
```
|
||||
new DefaultDataSourceFactory(
|
||||
new DefaultDataSource.Factory(
|
||||
...
|
||||
/* baseDataSourceFactory= */ new CronetDataSource.Factory(...) );
|
||||
```
|
||||
|
|
|
|||
|
|
@ -343,7 +343,9 @@ public class CronetDataSource extends BaseDataSource implements HttpDataSource {
|
|||
*/
|
||||
public final int cronetConnectionStatus;
|
||||
|
||||
/** @deprecated Use {@link #OpenException(IOException, DataSpec, int, int)}. */
|
||||
/**
|
||||
* @deprecated Use {@link #OpenException(IOException, DataSpec, int, int)}.
|
||||
*/
|
||||
@Deprecated
|
||||
public OpenException(IOException cause, DataSpec dataSpec, int cronetConnectionStatus) {
|
||||
super(cause, dataSpec, PlaybackException.ERROR_CODE_IO_UNSPECIFIED, TYPE_OPEN);
|
||||
|
|
@ -359,7 +361,9 @@ public class CronetDataSource extends BaseDataSource implements HttpDataSource {
|
|||
this.cronetConnectionStatus = cronetConnectionStatus;
|
||||
}
|
||||
|
||||
/** @deprecated Use {@link #OpenException(String, DataSpec, int, int)}. */
|
||||
/**
|
||||
* @deprecated Use {@link #OpenException(String, DataSpec, int, int)}.
|
||||
*/
|
||||
@Deprecated
|
||||
public OpenException(String errorMessage, DataSpec dataSpec, int cronetConnectionStatus) {
|
||||
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
|
||||
* {@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 Use {@link CronetDataSource.Factory#setContentTypePredicate(Predicate)} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public void setContentTypePredicate(@Nullable Predicate<String> contentTypePredicate) {
|
||||
|
|
|
|||
|
|
@ -25,7 +25,9 @@ import com.google.android.exoplayer2.upstream.TransferListener;
|
|||
import java.util.concurrent.Executor;
|
||||
import org.chromium.net.CronetEngine;
|
||||
|
||||
/** @deprecated Use {@link CronetDataSource.Factory} instead. */
|
||||
/**
|
||||
* @deprecated Use {@link CronetDataSource.Factory} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public final class CronetDataSourceFactory extends BaseFactory {
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
if (project.file('src/main/jni/ffmpeg').exists()) {
|
||||
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 {
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ public final class FfmpegLibrary {
|
|||
/**
|
||||
* 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
|
||||
* instantiating a {@link FfmpegAudioRenderer} instance.
|
||||
* instantiating a {@link FfmpegAudioRenderer} or {@link FfmpegVideoRenderer} instance.
|
||||
*
|
||||
* @param libraries The names of the FFmpeg native libraries.
|
||||
*/
|
||||
|
|
@ -146,6 +146,10 @@ public final class FfmpegLibrary {
|
|||
return "pcm_mulaw";
|
||||
case MimeTypes.AUDIO_ALAW:
|
||||
return "pcm_alaw";
|
||||
case MimeTypes.VIDEO_H264:
|
||||
return "h264";
|
||||
case MimeTypes.VIDEO_H265:
|
||||
return "hevc";
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -14,7 +14,7 @@
|
|||
# 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.
|
||||
set(CMAKE_CXX_STANDARD 11)
|
||||
|
|
|
|||
|
|
@ -22,7 +22,8 @@ import org.junit.Test;
|
|||
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)
|
||||
public final class DefaultRenderersFactoryTest {
|
||||
|
|
@ -32,4 +33,10 @@ public final class DefaultRenderersFactoryTest {
|
|||
DefaultRenderersFactoryAsserts.assertExtensionRendererCreated(
|
||||
FfmpegAudioRenderer.class, C.TRACK_TYPE_AUDIO);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createRenderers_instantiatesFfmpegVideoRenderer() {
|
||||
DefaultRenderersFactoryAsserts.assertExtensionRendererCreated(
|
||||
FfmpegVideoRenderer.class, C.TRACK_TYPE_VIDEO);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -42,9 +42,7 @@ import com.google.android.exoplayer2.testutil.ExoHostedTest;
|
|||
import com.google.android.exoplayer2.testutil.HostActivity;
|
||||
import com.google.android.exoplayer2.testutil.TestUtil;
|
||||
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.DefaultDataSource;
|
||||
import com.google.android.exoplayer2.util.Assertions;
|
||||
import com.google.android.exoplayer2.util.Util;
|
||||
import java.util.ArrayList;
|
||||
|
|
@ -235,14 +233,13 @@ public final class ImaPlaybackTest {
|
|||
protected MediaSource buildSource(
|
||||
HostActivity host, DrmSessionManager drmSessionManager, FrameLayout overlayFrameLayout) {
|
||||
Context context = host.getApplicationContext();
|
||||
DataSource.Factory dataSourceFactory = new DefaultDataSource.Factory(context);
|
||||
MediaSource contentMediaSource =
|
||||
new DefaultMediaSourceFactory(context).createMediaSource(MediaItem.fromUri(contentUri));
|
||||
return new AdsMediaSource(
|
||||
contentMediaSource,
|
||||
adTagDataSpec,
|
||||
/* adsId= */ adTagDataSpec.uri,
|
||||
new DefaultMediaSourceFactory(dataSourceFactory),
|
||||
new DefaultMediaSourceFactory(context),
|
||||
Assertions.checkNotNull(imaAdsLoader),
|
||||
() -> overlayFrameLayout);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -84,12 +84,14 @@ import java.util.Map;
|
|||
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
|
||||
* the interval recommended by the IMA documentation.
|
||||
* Interval at which ad progress updates are provided to the IMA SDK, in milliseconds. 200 ms is
|
||||
* 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
|
||||
*/
|
||||
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. */
|
||||
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.
|
||||
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) {
|
||||
|
|
|
|||
|
|
@ -503,11 +503,11 @@ public final class ImaAdsLoader implements AdsLoader {
|
|||
List<String> supportedMimeTypes = new ArrayList<>();
|
||||
for (@C.ContentType int contentType : contentTypes) {
|
||||
// IMA does not support Smooth Streaming ad media.
|
||||
if (contentType == C.TYPE_DASH) {
|
||||
if (contentType == C.CONTENT_TYPE_DASH) {
|
||||
supportedMimeTypes.add(MimeTypes.APPLICATION_MPD);
|
||||
} else if (contentType == C.TYPE_HLS) {
|
||||
} else if (contentType == C.CONTENT_TYPE_HLS) {
|
||||
supportedMimeTypes.add(MimeTypes.APPLICATION_M3U8);
|
||||
} else if (contentType == C.TYPE_OTHER) {
|
||||
} else if (contentType == C.CONTENT_TYPE_OTHER) {
|
||||
supportedMimeTypes.addAll(
|
||||
Arrays.asList(
|
||||
MimeTypes.VIDEO_MP4,
|
||||
|
|
|
|||
|
|
@ -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.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.updateAdDurationAndPropagate;
|
||||
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.checkState;
|
||||
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.usToMs;
|
||||
import static java.lang.Math.min;
|
||||
|
|
@ -133,14 +134,14 @@ public final class ImaServerSideAdInsertionMediaSource extends CompositeMediaSou
|
|||
|
||||
@Override
|
||||
public MediaSource.Factory setLoadErrorHandlingPolicy(
|
||||
@Nullable LoadErrorHandlingPolicy loadErrorHandlingPolicy) {
|
||||
LoadErrorHandlingPolicy loadErrorHandlingPolicy) {
|
||||
contentMediaSourceFactory.setLoadErrorHandlingPolicy(loadErrorHandlingPolicy);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MediaSource.Factory setDrmSessionManagerProvider(
|
||||
@Nullable DrmSessionManagerProvider drmSessionManagerProvider) {
|
||||
DrmSessionManagerProvider drmSessionManagerProvider) {
|
||||
contentMediaSourceFactory.setDrmSessionManagerProvider(drmSessionManagerProvider);
|
||||
return this;
|
||||
}
|
||||
|
|
@ -325,23 +326,31 @@ public final class ImaServerSideAdInsertionMediaSource extends CompositeMediaSou
|
|||
@Override
|
||||
public Bundle toBundle() {
|
||||
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;
|
||||
}
|
||||
|
||||
/** Object that can restore {@link AdsLoader.State} from a {@link Bundle}. */
|
||||
public static final Bundleable.Creator<State> CREATOR = State::fromBundle;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static State fromBundle(Bundle bundle) {
|
||||
@Nullable
|
||||
Map<String, AdPlaybackState> adPlaybackStateMap =
|
||||
(Map<String, AdPlaybackState>)
|
||||
bundle.getSerializable(keyForField(FIELD_AD_PLAYBACK_STATES));
|
||||
return new State(
|
||||
adPlaybackStateMap != null
|
||||
? ImmutableMap.copyOf(adPlaybackStateMap)
|
||||
: ImmutableMap.of());
|
||||
ImmutableMap.Builder<String, AdPlaybackState> adPlaybackStateMap =
|
||||
new ImmutableMap.Builder<>();
|
||||
Bundle adPlaybackStateBundle =
|
||||
checkNotNull(bundle.getBundle(keyForField(FIELD_AD_PLAYBACK_STATES)));
|
||||
for (String key : adPlaybackStateBundle.keySet()) {
|
||||
AdPlaybackState adPlaybackState =
|
||||
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) {
|
||||
|
|
@ -596,7 +605,7 @@ public final class ImaServerSideAdInsertionMediaSource extends CompositeMediaSou
|
|||
}
|
||||
|
||||
@MainThread
|
||||
@EnsuresNonNull("contentTimeline")
|
||||
@EnsuresNonNull("this.contentTimeline")
|
||||
private void setContentTimeline(Timeline contentTimeline) {
|
||||
if (contentTimeline.equals(this.contentTimeline)) {
|
||||
return;
|
||||
|
|
@ -651,19 +660,29 @@ public final class ImaServerSideAdInsertionMediaSource extends CompositeMediaSou
|
|||
|
||||
private static AdPlaybackState setVodAdGroupPlaceholders(
|
||||
List<CuePoint> cuePoints, AdPlaybackState adPlaybackState) {
|
||||
// TODO(b/192231683) Use getEndTimeMs()/getStartTimeMs() after jar target was removed
|
||||
for (int i = 0; i < cuePoints.size(); i++) {
|
||||
CuePoint cuePoint = cuePoints.get(i);
|
||||
long fromPositionUs = msToUs(secToMsRounded(cuePoint.getStartTime()));
|
||||
adPlaybackState =
|
||||
addAdGroupToAdPlaybackState(
|
||||
adPlaybackState,
|
||||
/* fromPositionUs= */ secToUs(cuePoint.getStartTime()),
|
||||
/* fromPositionUs= */ fromPositionUs,
|
||||
/* contentResumeOffsetUs= */ 0,
|
||||
// TODO(b/192231683) Use getEndTimeMs()/getStartTimeMs() after jar target was removed
|
||||
/* adDurationsUs...= */ secToUs(cuePoint.getEndTime() - cuePoint.getStartTime()));
|
||||
/* adDurationsUs...= */ getAdDuration(
|
||||
/* startTimeSeconds= */ cuePoint.getStartTime(),
|
||||
/* endTimeSeconds= */ cuePoint.getEndTime()));
|
||||
}
|
||||
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) {
|
||||
AdPodInfo adPodInfo = ad.getAdPodInfo();
|
||||
// Handle post rolls that have a podIndex of -1.
|
||||
|
|
@ -675,9 +694,9 @@ public final class ImaServerSideAdInsertionMediaSource extends CompositeMediaSou
|
|||
adPlaybackState =
|
||||
expandAdGroupPlaceholder(
|
||||
adGroupIndex,
|
||||
/* adGroupDurationUs= */ secToUs(adPodInfo.getMaxDuration()),
|
||||
/* adGroupDurationUs= */ msToUs(secToMsRounded(adPodInfo.getMaxDuration())),
|
||||
adIndexInAdGroup,
|
||||
/* adDurationUs= */ secToUs(ad.getDuration()),
|
||||
/* adDurationUs= */ msToUs(secToMsRounded(ad.getDuration())),
|
||||
/* adsInAdGroupCount= */ adPodInfo.getTotalAds(),
|
||||
adPlaybackState);
|
||||
} else if (adIndexInAdGroup < adGroup.count - 1) {
|
||||
|
|
@ -685,7 +704,7 @@ public final class ImaServerSideAdInsertionMediaSource extends CompositeMediaSou
|
|||
updateAdDurationInAdGroup(
|
||||
adGroupIndex,
|
||||
adIndexInAdGroup,
|
||||
/* adDurationUs= */ secToUs(ad.getDuration()),
|
||||
/* adDurationUs= */ msToUs(secToMsRounded(ad.getDuration())),
|
||||
adPlaybackState);
|
||||
}
|
||||
return adPlaybackState;
|
||||
|
|
@ -694,7 +713,7 @@ public final class ImaServerSideAdInsertionMediaSource extends CompositeMediaSou
|
|||
private AdPlaybackState addLiveAdBreak(
|
||||
Ad ad, long currentPeriodPositionUs, AdPlaybackState adPlaybackState) {
|
||||
AdPodInfo adPodInfo = ad.getAdPodInfo();
|
||||
long adDurationUs = secToUs(ad.getDuration());
|
||||
long adDurationUs = secToUsRounded(ad.getDuration());
|
||||
int adIndexInAdGroup = adPodInfo.getAdPosition() - 1;
|
||||
// TODO(b/208398934) Support seeking backwards.
|
||||
if (adIndexInAdGroup == 0 || adPlaybackState.adGroupCount == 1) {
|
||||
|
|
@ -708,7 +727,7 @@ public final class ImaServerSideAdInsertionMediaSource extends CompositeMediaSou
|
|||
new long[adCount],
|
||||
adIndexInAdGroup,
|
||||
adDurationUs,
|
||||
secToUs(adPodInfo.getMaxDuration()));
|
||||
msToUs(secToMsRounded(adPodInfo.getMaxDuration())));
|
||||
adPlaybackState =
|
||||
addAdGroupToAdPlaybackState(
|
||||
adPlaybackState,
|
||||
|
|
|
|||
|
|
@ -72,7 +72,7 @@ public final class ImaServerSideAdInsertionUriBuilder {
|
|||
public ImaServerSideAdInsertionUriBuilder() {
|
||||
adTagParameters = ImmutableMap.of();
|
||||
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.
|
||||
*
|
||||
* @param format VOD or live stream type.
|
||||
* @param format {@link C#TYPE_DASH} or {@link C#TYPE_HLS}.
|
||||
* @return This instance, for convenience.
|
||||
*/
|
||||
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;
|
||||
return this;
|
||||
}
|
||||
|
|
@ -243,7 +243,7 @@ public final class ImaServerSideAdInsertionUriBuilder {
|
|||
|| (!TextUtils.isEmpty(assetKey)
|
||||
&& TextUtils.isEmpty(contentSourceId)
|
||||
&& TextUtils.isEmpty(videoId)));
|
||||
checkState(format != C.TYPE_OTHER);
|
||||
checkState(format != C.CONTENT_TYPE_OTHER);
|
||||
@Nullable String adsId = this.adsId;
|
||||
if (adsId == null) {
|
||||
adsId = assetKey != null ? assetKey : checkNotNull(videoId);
|
||||
|
|
@ -330,9 +330,9 @@ public final class ImaServerSideAdInsertionUriBuilder {
|
|||
.createVodStreamRequest(checkNotNull(contentSourceId), checkNotNull(videoId), apiKey);
|
||||
}
|
||||
int format = Integer.parseInt(uri.getQueryParameter(FORMAT));
|
||||
if (format == C.TYPE_DASH) {
|
||||
if (format == C.CONTENT_TYPE_DASH) {
|
||||
streamRequest.setFormat(StreamFormat.DASH);
|
||||
} else if (format == C.TYPE_HLS) {
|
||||
} else if (format == C.CONTENT_TYPE_HLS) {
|
||||
streamRequest.setFormat(StreamFormat.HLS);
|
||||
} else {
|
||||
throw new IllegalArgumentException("Unsupported stream format:" + format);
|
||||
|
|
|
|||
|
|
@ -54,7 +54,10 @@ import com.google.android.exoplayer2.upstream.DataSpec;
|
|||
import com.google.android.exoplayer2.util.Util;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.math.DoubleMath;
|
||||
import java.io.IOException;
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
|
|
@ -398,17 +401,13 @@ import java.util.Set;
|
|||
long elapsedAdGroupAdDurationUs = 0;
|
||||
for (int j = periodIndex; j < contentTimeline.getPeriodCount(); j++) {
|
||||
contentTimeline.getPeriod(j, period, /* setIds= */ true);
|
||||
// TODO(b/192231683) Remove subtracted US from ad group time when we can upgrade the SDK.
|
||||
// Subtract one microsecond to work around rounding errors with adGroup.timeUs.
|
||||
if (totalElapsedContentDurationUs < adGroup.timeUs - 1) {
|
||||
if (totalElapsedContentDurationUs < adGroup.timeUs) {
|
||||
// Period starts before the ad group, so it is a content period.
|
||||
adPlaybackStates.put(checkNotNull(period.uid), contentOnlyAdPlaybackState);
|
||||
totalElapsedContentDurationUs += period.durationUs;
|
||||
} else {
|
||||
long periodStartUs = totalElapsedContentDurationUs + elapsedAdGroupAdDurationUs;
|
||||
// TODO(b/192231683) Remove additional US when we can upgrade the SDK.
|
||||
// Add one microsecond to work around rounding errors with adGroup.timeUs.
|
||||
if (periodStartUs + period.durationUs <= adGroup.timeUs + adGroupDurationUs + 1) {
|
||||
if (periodStartUs + period.durationUs <= adGroup.timeUs + adGroupDurationUs) {
|
||||
// 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).
|
||||
adPlaybackStates.put(
|
||||
|
|
@ -490,16 +489,12 @@ import java.util.Set;
|
|||
long elapsedAdGroupAdDurationUs = 0;
|
||||
for (int j = periodIndex; j < contentTimeline.getPeriodCount(); j++) {
|
||||
contentTimeline.getPeriod(j, period, /* setIds= */ true);
|
||||
// TODO(b/192231683) Remove subtracted US from ad group time when we can upgrade the SDK.
|
||||
// Subtract one microsecond to work around rounding errors with adGroup.timeUs.
|
||||
if (totalElapsedContentDurationUs < adGroup.timeUs - 1) {
|
||||
if (totalElapsedContentDurationUs < adGroup.timeUs) {
|
||||
// Period starts before the ad group, so it is a content period.
|
||||
totalElapsedContentDurationUs += period.durationUs;
|
||||
} else {
|
||||
long periodStartUs = totalElapsedContentDurationUs + elapsedAdGroupAdDurationUs;
|
||||
// TODO(b/192231683) Remove additional US when we can upgrade the SDK.
|
||||
// Add one microsecond to work around rounding errors with adGroup.timeUs.
|
||||
if (periodStartUs + period.durationUs <= adGroup.timeUs + adGroupDurationUs + 1) {
|
||||
if (periodStartUs + period.durationUs <= adGroup.timeUs + adGroupDurationUs) {
|
||||
// The period ends before the end of the ad group, so it is an ad period.
|
||||
if (j == adPeriodIndex) {
|
||||
return new Pair<>(/* adGroupIndex= */ i, adIndexInAdGroup);
|
||||
|
|
@ -518,5 +513,31 @@ import java.util.Set;
|
|||
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() {}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ import com.google.android.exoplayer2.MediaItem;
|
|||
import com.google.android.exoplayer2.PlaybackException;
|
||||
import com.google.android.exoplayer2.Player;
|
||||
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.trackselection.TrackSelectionArray;
|
||||
import com.google.android.exoplayer2.trackselection.TrackSelectionParameters;
|
||||
|
|
@ -79,7 +79,7 @@ import com.google.android.exoplayer2.util.Util;
|
|||
PositionInfo oldPosition =
|
||||
new PositionInfo(
|
||||
windowUid,
|
||||
/* windowIndex= */ 0,
|
||||
/* mediaItemIndex= */ 0,
|
||||
mediaItem,
|
||||
periodUid,
|
||||
/* periodIndex= */ 0,
|
||||
|
|
@ -97,7 +97,7 @@ import com.google.android.exoplayer2.util.Util;
|
|||
PositionInfo newPosition =
|
||||
new PositionInfo(
|
||||
windowUid,
|
||||
/* windowIndex= */ 0,
|
||||
/* mediaItemIndex= */ 0,
|
||||
mediaItem,
|
||||
periodUid,
|
||||
/* periodIndex= */ 0,
|
||||
|
|
@ -128,7 +128,7 @@ import com.google.android.exoplayer2.util.Util;
|
|||
PositionInfo oldPosition =
|
||||
new PositionInfo(
|
||||
windowUid,
|
||||
/* windowIndex= */ 0,
|
||||
/* mediaItemIndex= */ 0,
|
||||
mediaItem,
|
||||
periodUid,
|
||||
/* periodIndex= */ 0,
|
||||
|
|
@ -146,7 +146,7 @@ import com.google.android.exoplayer2.util.Util;
|
|||
PositionInfo newPosition =
|
||||
new PositionInfo(
|
||||
windowUid,
|
||||
/* windowIndex= */ 0,
|
||||
/* mediaItemIndex= */ 0,
|
||||
mediaItem,
|
||||
periodUid,
|
||||
/* periodIndex= */ 0,
|
||||
|
|
@ -266,8 +266,8 @@ import com.google.android.exoplayer2.util.Util;
|
|||
}
|
||||
|
||||
@Override
|
||||
public TracksInfo getCurrentTracksInfo() {
|
||||
return TracksInfo.EMPTY;
|
||||
public Tracks getCurrentTracks() {
|
||||
return Tracks.EMPTY;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -296,8 +296,8 @@ public final class ImaAdsLoaderTest {
|
|||
/* periodIndex= */ 0,
|
||||
/* adGroupIndex= */ 0,
|
||||
/* adIndexInAdGroup= */ 0,
|
||||
/* position= */ 0,
|
||||
/* contentPosition= */ 0);
|
||||
/* positionMs= */ 0,
|
||||
/* contentPositionMs= */ 0);
|
||||
fakePlayer.setState(Player.STATE_READY, /* playWhenReady= */ true);
|
||||
adEventListener.onAdEvent(getAdEvent(AdEventType.STARTED, mockPrerollSingleAd));
|
||||
adEventListener.onAdEvent(getAdEvent(AdEventType.FIRST_QUARTILE, mockPrerollSingleAd));
|
||||
|
|
@ -962,7 +962,7 @@ public final class ImaAdsLoaderTest {
|
|||
|
||||
@Test
|
||||
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(
|
||||
adsMediaSource, TEST_DATA_SPEC, TEST_ADS_ID, adViewProvider, adsLoaderListener);
|
||||
|
||||
|
|
@ -996,7 +996,7 @@ public final class ImaAdsLoaderTest {
|
|||
adViewProvider);
|
||||
when(mockAdsManager.getAdCuePoints()).thenReturn(PREROLL_CUE_POINTS_SECONDS);
|
||||
|
||||
imaAdsLoader.setSupportedContentTypes(C.TYPE_OTHER);
|
||||
imaAdsLoader.setSupportedContentTypes(C.CONTENT_TYPE_OTHER);
|
||||
imaAdsLoader.start(
|
||||
adsMediaSource, TEST_DATA_SPEC, TEST_ADS_ID, adViewProvider, adsLoaderListener);
|
||||
|
||||
|
|
@ -1096,8 +1096,8 @@ public final class ImaAdsLoaderTest {
|
|||
/* periodIndex= */ 0,
|
||||
/* adGroupIndex= */ 0,
|
||||
/* adIndexInAdGroup= */ 0,
|
||||
/* position= */ 0,
|
||||
/* contentPosition= */ 0);
|
||||
/* positionMs= */ 0,
|
||||
/* contentPositionMs= */ 0);
|
||||
fakePlayer.setState(Player.STATE_READY, /* playWhenReady= */ true);
|
||||
adEventListener.onAdEvent(getAdEvent(AdEventType.STARTED, mockPrerollSingleAd));
|
||||
adEventListener.onAdEvent(getAdEvent(AdEventType.FIRST_QUARTILE, mockPrerollSingleAd));
|
||||
|
|
@ -1155,8 +1155,8 @@ public final class ImaAdsLoaderTest {
|
|||
/* periodIndex= */ 0,
|
||||
/* adGroupIndex= */ 0,
|
||||
/* adIndexInAdGroup= */ 0,
|
||||
/* position= */ 0,
|
||||
/* contentPosition= */ 0);
|
||||
/* positionMs= */ 0,
|
||||
/* contentPositionMs= */ 0);
|
||||
fakePlayer.setState(Player.STATE_READY, /* playWhenReady= */ true);
|
||||
adEventListener.onAdEvent(getAdEvent(AdEventType.STARTED, mockPrerollSingleAd));
|
||||
adEventListener.onAdEvent(getAdEvent(AdEventType.FIRST_QUARTILE, mockPrerollSingleAd));
|
||||
|
|
@ -1257,7 +1257,7 @@ public final class ImaAdsLoaderTest {
|
|||
adViewProvider);
|
||||
when(mockAdsManager.getAdCuePoints()).thenReturn(PREROLL_CUE_POINTS_SECONDS);
|
||||
|
||||
imaAdsLoader.setSupportedContentTypes(C.TYPE_OTHER);
|
||||
imaAdsLoader.setSupportedContentTypes(C.CONTENT_TYPE_OTHER);
|
||||
imaAdsLoader.start(
|
||||
adsMediaSource, TEST_DATA_SPEC, TEST_ADS_ID, adViewProvider, adsLoaderListener);
|
||||
|
||||
|
|
@ -1283,7 +1283,7 @@ public final class ImaAdsLoaderTest {
|
|||
adViewProvider);
|
||||
when(mockAdsManager.getAdCuePoints()).thenReturn(PREROLL_CUE_POINTS_SECONDS);
|
||||
|
||||
imaAdsLoader.setSupportedContentTypes(C.TYPE_OTHER);
|
||||
imaAdsLoader.setSupportedContentTypes(C.CONTENT_TYPE_OTHER);
|
||||
imaAdsLoader.start(
|
||||
adsMediaSource, TEST_DATA_SPEC, TEST_ADS_ID, adViewProvider, adsLoaderListener);
|
||||
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@ public final class ImaServerSideAdInsertionUriBuilderTest {
|
|||
builder.setContentUrl(CONTENT_URL);
|
||||
builder.setAuthToken(AUTH_TOKEN);
|
||||
builder.setStreamActivityMonitorId(STREAM_ACTIVITY_MONITOR_ID);
|
||||
builder.setFormat(C.TYPE_HLS);
|
||||
builder.setFormat(C.CONTENT_TYPE_HLS);
|
||||
builder.setAdTagParameters(adTagParameters);
|
||||
builder.setLoadVideoTimeoutMs(ADS_LOADER_TIMEOUT_MS);
|
||||
Uri uri = builder.build();
|
||||
|
|
@ -96,7 +96,7 @@ public final class ImaServerSideAdInsertionUriBuilderTest {
|
|||
builder.setContentUrl(CONTENT_URL);
|
||||
builder.setAuthToken(AUTH_TOKEN);
|
||||
builder.setStreamActivityMonitorId(STREAM_ACTIVITY_MONITOR_ID);
|
||||
builder.setFormat(C.TYPE_DASH);
|
||||
builder.setFormat(C.CONTENT_TYPE_DASH);
|
||||
builder.setAdTagParameters(adTagParameters);
|
||||
builder.setLoadVideoTimeoutMs(ADS_LOADER_TIMEOUT_MS);
|
||||
Uri uri = builder.build();
|
||||
|
|
@ -127,7 +127,7 @@ public final class ImaServerSideAdInsertionUriBuilderTest {
|
|||
ImaServerSideAdInsertionUriBuilder builder = new ImaServerSideAdInsertionUriBuilder();
|
||||
builder.setContentSourceId(CONTENT_SOURCE_ID);
|
||||
builder.setVideoId(VIDEO_ID);
|
||||
builder.setFormat(C.TYPE_DASH);
|
||||
builder.setFormat(C.CONTENT_TYPE_DASH);
|
||||
|
||||
Uri streamRequest = builder.build();
|
||||
|
||||
|
|
@ -139,7 +139,7 @@ public final class ImaServerSideAdInsertionUriBuilderTest {
|
|||
public void build_liveWithNoAdsId_usesAssetKeyAsDefault() {
|
||||
ImaServerSideAdInsertionUriBuilder builder = new ImaServerSideAdInsertionUriBuilder();
|
||||
builder.setAssetKey(ASSET_KEY);
|
||||
builder.setFormat(C.TYPE_DASH);
|
||||
builder.setFormat(C.CONTENT_TYPE_DASH);
|
||||
|
||||
Uri streamRequest = builder.build();
|
||||
|
||||
|
|
@ -177,7 +177,7 @@ public final class ImaServerSideAdInsertionUriBuilderTest {
|
|||
Uri uri =
|
||||
new ImaServerSideAdInsertionUriBuilder()
|
||||
.setAssetKey(ASSET_KEY)
|
||||
.setFormat(C.TYPE_DASH)
|
||||
.setFormat(C.CONTENT_TYPE_DASH)
|
||||
.build();
|
||||
|
||||
int loadVideoTimeoutMs = ImaServerSideAdInsertionUriBuilder.getLoadVideoTimeoutMs(uri);
|
||||
|
|
|
|||
|
|
@ -29,12 +29,16 @@ import com.google.android.exoplayer2.upstream.DataSource;
|
|||
import com.google.android.exoplayer2.upstream.DataSourceException;
|
||||
import com.google.android.exoplayer2.upstream.DataSpec;
|
||||
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.TransferListener;
|
||||
import com.google.android.exoplayer2.util.Assertions;
|
||||
import com.google.android.exoplayer2.util.Util;
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.net.HttpHeaders;
|
||||
import com.google.common.util.concurrent.SettableFuture;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InterruptedIOException;
|
||||
|
|
@ -42,8 +46,10 @@ import java.util.Collections;
|
|||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import okhttp3.CacheControl;
|
||||
import okhttp3.Call;
|
||||
import okhttp3.Callback;
|
||||
import okhttp3.HttpUrl;
|
||||
import okhttp3.MediaType;
|
||||
import okhttp3.OkHttpClient;
|
||||
|
|
@ -179,21 +185,27 @@ public class OkHttpDataSource extends BaseDataSource implements HttpDataSource {
|
|||
private long bytesToRead;
|
||||
private long bytesRead;
|
||||
|
||||
/** @deprecated Use {@link OkHttpDataSource.Factory} instead. */
|
||||
/**
|
||||
* @deprecated Use {@link OkHttpDataSource.Factory} instead.
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
@Deprecated
|
||||
public OkHttpDataSource(Call.Factory callFactory) {
|
||||
this(callFactory, /* userAgent= */ null);
|
||||
}
|
||||
|
||||
/** @deprecated Use {@link OkHttpDataSource.Factory} instead. */
|
||||
/**
|
||||
* @deprecated Use {@link OkHttpDataSource.Factory} instead.
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
@Deprecated
|
||||
public OkHttpDataSource(Call.Factory callFactory, @Nullable String userAgent) {
|
||||
this(callFactory, userAgent, /* cacheControl= */ null, /* defaultRequestProperties= */ null);
|
||||
}
|
||||
|
||||
/** @deprecated Use {@link OkHttpDataSource.Factory} instead. */
|
||||
/**
|
||||
* @deprecated Use {@link OkHttpDataSource.Factory} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public OkHttpDataSource(
|
||||
Call.Factory callFactory,
|
||||
|
|
@ -275,8 +287,9 @@ public class OkHttpDataSource extends BaseDataSource implements HttpDataSource {
|
|||
Request request = makeRequest(dataSpec);
|
||||
Response response;
|
||||
ResponseBody responseBody;
|
||||
Call call = callFactory.newCall(request);
|
||||
try {
|
||||
this.response = callFactory.newCall(request).execute();
|
||||
this.response = executeCall(call);
|
||||
response = this.response;
|
||||
responseBody = Assertions.checkNotNull(response.body());
|
||||
responseByteStream = responseBody.byteStream();
|
||||
|
|
@ -422,6 +435,35 @@ public class OkHttpDataSource extends BaseDataSource implements HttpDataSource {
|
|||
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.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -22,7 +22,9 @@ import com.google.android.exoplayer2.upstream.TransferListener;
|
|||
import okhttp3.CacheControl;
|
||||
import okhttp3.Call;
|
||||
|
||||
/** @deprecated Use {@link OkHttpDataSource.Factory} instead. */
|
||||
/**
|
||||
* @deprecated Use {@link OkHttpDataSource.Factory} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public final class OkHttpDataSourceFactory extends BaseFactory {
|
||||
|
||||
|
|
|
|||
|
|
@ -113,6 +113,7 @@ public class LibopusAudioRenderer extends DecoderAudioRenderer<OpusDecoder> {
|
|||
format.initializationData,
|
||||
cryptoConfig,
|
||||
outputFloat);
|
||||
decoder.experimentalSetDiscardPaddingEnabled(experimentalGetDiscardPaddingEnabled());
|
||||
|
||||
TraceUtil.endSection();
|
||||
return decoder;
|
||||
|
|
@ -124,4 +125,14 @@ public class LibopusAudioRenderer extends DecoderAudioRenderer<OpusDecoder> {
|
|||
int pcmEncoding = decoder.outputFloat ? C.ENCODING_PCM_FLOAT : C.ENCODING_PCM_16BIT;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -54,6 +54,7 @@ public final class OpusDecoder
|
|||
private final int preSkipSamples;
|
||||
private final int seekPreRollSamples;
|
||||
private final long nativeDecoderContext;
|
||||
private boolean experimentalDiscardPaddingEnabled;
|
||||
|
||||
private int skipSamples;
|
||||
|
||||
|
|
@ -97,6 +98,7 @@ public final class OpusDecoder
|
|||
}
|
||||
preSkipSamples = getPreSkipSamples(initializationData);
|
||||
seekPreRollSamples = getSeekPreRollSamples(initializationData);
|
||||
skipSamples = preSkipSamples;
|
||||
|
||||
byte[] headerBytes = initializationData.get(0);
|
||||
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
|
||||
public String getName() {
|
||||
return "libopus" + OpusLibrary.getVersion();
|
||||
|
|
@ -221,6 +233,14 @@ public final class OpusDecoder
|
|||
skipSamples = 0;
|
||||
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;
|
||||
}
|
||||
|
|
@ -278,6 +298,25 @@ public final class OpusDecoder
|
|||
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}. */
|
||||
private static int samplesToBytes(int samples, int channelCount, boolean outputFloat) {
|
||||
int bytesPerChannel = outputFloat ? 4 : 2;
|
||||
|
|
|
|||
|
|
@ -14,17 +14,26 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifdef __ANDROID__
|
||||
#include <android/log.h>
|
||||
#endif
|
||||
#include <jni.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
|
||||
#include "opus.h" // NOLINT
|
||||
#include "opus_multistream.h" // NOLINT
|
||||
|
||||
#ifdef __ANDROID__
|
||||
#define LOG_TAG "opus_jni"
|
||||
#define LOGE(...) \
|
||||
((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, ...) \
|
||||
extern "C" { \
|
||||
|
|
|
|||
|
|
@ -16,9 +16,14 @@
|
|||
package com.google.android.exoplayer2.ext.opus;
|
||||
|
||||
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 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 java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
|
|
@ -29,16 +34,30 @@ import org.junit.runner.RunWith;
|
|||
@RunWith(AndroidJUnit4.class)
|
||||
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 =
|
||||
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 DEFAULT_SEEK_PRE_ROLL_SAMPLES = 3840;
|
||||
|
||||
private static final int DISCARD_PADDING_NANOS = 166667;
|
||||
|
||||
private static final ImmutableList<byte[]> HEADER_ONLY_INITIALIZATION_DATA =
|
||||
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 byte[] CUSTOM_PRE_SKIP_BYTES =
|
||||
buildNativeOrderByteArray(sampleCountToNanoseconds(CUSTOM_PRE_SKIP_SAMPLES));
|
||||
|
|
@ -80,11 +99,124 @@ public final class OpusDecoderTest {
|
|||
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) {
|
||||
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) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ injected from application code.
|
|||
|
||||
`DefaultDataSource` will automatically use the RTMP extension whenever it's
|
||||
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
|
||||
application code are required. Alternatively, if you know that your application
|
||||
doesn't need to handle any other protocols, you can update any
|
||||
|
|
|
|||
|
|
@ -19,7 +19,9 @@ import androidx.annotation.Nullable;
|
|||
import com.google.android.exoplayer2.upstream.DataSource;
|
||||
import com.google.android.exoplayer2.upstream.TransferListener;
|
||||
|
||||
/** @deprecated Use {@link RtmpDataSource.Factory} instead. */
|
||||
/**
|
||||
* @deprecated Use {@link RtmpDataSource.Factory} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public final class RtmpDataSourceFactory implements DataSource.Factory {
|
||||
|
||||
|
|
@ -29,7 +31,9 @@ public final class RtmpDataSourceFactory implements DataSource.Factory {
|
|||
this(null);
|
||||
}
|
||||
|
||||
/** @param listener An optional listener. */
|
||||
/**
|
||||
* @param listener An optional listener.
|
||||
*/
|
||||
public RtmpDataSourceFactory(@Nullable TransferListener listener) {
|
||||
this.listener = listener;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -54,7 +54,9 @@ public final class WorkManagerScheduler implements Scheduler {
|
|||
private final WorkManager workManager;
|
||||
private final String workName;
|
||||
|
||||
/** @deprecated Call {@link #WorkManagerScheduler(Context, String)} instead. */
|
||||
/**
|
||||
* @deprecated Call {@link #WorkManagerScheduler(Context, String)} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
@SuppressWarnings("deprecation")
|
||||
public WorkManagerScheduler(String workName) {
|
||||
|
|
|
|||
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
|
|
@ -1,5 +1,5 @@
|
|||
distributionBase=GRADLE_USER_HOME
|
||||
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
|
||||
zipStorePath=wrapper/dists
|
||||
|
|
|
|||
|
|
@ -48,9 +48,21 @@ class CombinedJavadocPlugin implements Plugin<Project> {
|
|||
libraryModule.android.libraryVariants.all { variant ->
|
||||
def name = variant.buildType.name
|
||||
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 +=
|
||||
libraryModule.project.files(
|
||||
variant.javaCompileProvider.get().classpath.files,
|
||||
filteredJarFiles,
|
||||
libraryModule.project.android.getBootClasspath())
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -92,7 +92,7 @@ public abstract class BasePlayer implements Player {
|
|||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* <p>BasePlayer and its descendents will return {@code true}.
|
||||
* <p>BasePlayer and its descendants will return {@code true}.
|
||||
*/
|
||||
@Override
|
||||
public final boolean canAdvertiseSession() {
|
||||
|
|
@ -141,12 +141,18 @@ public abstract class BasePlayer implements Player {
|
|||
seekToOffset(getSeekForwardIncrement());
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link #hasPreviousMediaItem()} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
@Override
|
||||
public final boolean hasPrevious() {
|
||||
return hasPreviousMediaItem();
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link #hasPreviousMediaItem()} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
@Override
|
||||
public final boolean hasPreviousWindow() {
|
||||
|
|
@ -158,12 +164,18 @@ public abstract class BasePlayer implements Player {
|
|||
return getPreviousMediaItemIndex() != C.INDEX_UNSET;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link #seekToPreviousMediaItem()} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
@Override
|
||||
public final void previous() {
|
||||
seekToPreviousMediaItem();
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link #seekToPreviousMediaItem()} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
@Override
|
||||
public final void seekToPreviousWindow() {
|
||||
|
|
@ -196,12 +208,18 @@ public abstract class BasePlayer implements Player {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link #hasNextMediaItem()} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
@Override
|
||||
public final boolean hasNext() {
|
||||
return hasNextMediaItem();
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link #hasNextMediaItem()} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
@Override
|
||||
public final boolean hasNextWindow() {
|
||||
|
|
@ -213,12 +231,18 @@ public abstract class BasePlayer implements Player {
|
|||
return getNextMediaItemIndex() != C.INDEX_UNSET;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link #seekToNextMediaItem()} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
@Override
|
||||
public final void next() {
|
||||
seekToNextMediaItem();
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link #seekToNextMediaItem()} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
@Override
|
||||
public final void seekToNextWindow() {
|
||||
|
|
@ -251,12 +275,18 @@ public abstract class BasePlayer implements Player {
|
|||
setPlaybackParameters(getPlaybackParameters().withSpeed(speed));
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link #getCurrentMediaItemIndex()} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
@Override
|
||||
public final int getCurrentWindowIndex() {
|
||||
return getCurrentMediaItemIndex();
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link #getNextMediaItemIndex()} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
@Override
|
||||
public final int getNextWindowIndex() {
|
||||
|
|
@ -272,6 +302,9 @@ public abstract class BasePlayer implements Player {
|
|||
getCurrentMediaItemIndex(), getRepeatModeForNavigation(), getShuffleModeEnabled());
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link #getPreviousMediaItemIndex()} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
@Override
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link #isCurrentMediaItemDynamic()} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
@Override
|
||||
public final boolean isCurrentWindowDynamic() {
|
||||
|
|
@ -336,6 +372,9 @@ public abstract class BasePlayer implements Player {
|
|||
return !timeline.isEmpty() && timeline.getWindow(getCurrentMediaItemIndex(), window).isDynamic;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link #isCurrentMediaItemLive()} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
@Override
|
||||
public final boolean isCurrentWindowLive() {
|
||||
|
|
@ -362,6 +401,9 @@ public abstract class BasePlayer implements Player {
|
|||
return window.getCurrentUnixTimeMs() - window.windowStartTimeMs - getContentPosition();
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link #isCurrentMediaItemSeekable()} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
@Override
|
||||
public final boolean isCurrentWindowSeekable() {
|
||||
|
|
|
|||
|
|
@ -69,6 +69,9 @@ public final class C {
|
|||
/** Represents an unset or unknown rate. */
|
||||
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. */
|
||||
public static final int LENGTH_UNSET = -1;
|
||||
|
||||
|
|
@ -166,11 +169,17 @@ public final class C {
|
|||
@Target(TYPE_USE)
|
||||
@IntDef({CRYPTO_MODE_UNENCRYPTED, CRYPTO_MODE_AES_CTR, CRYPTO_MODE_AES_CBC})
|
||||
public @interface CryptoMode {}
|
||||
/** @see MediaCodec#CRYPTO_MODE_UNENCRYPTED */
|
||||
/**
|
||||
* @see 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;
|
||||
/** @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;
|
||||
|
||||
/**
|
||||
|
|
@ -236,11 +245,17 @@ public final class C {
|
|||
ENCODING_PCM_FLOAT
|
||||
})
|
||||
public @interface PcmEncoding {}
|
||||
/** @see AudioFormat#ENCODING_INVALID */
|
||||
/**
|
||||
* @see 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;
|
||||
/** @see AudioFormat#ENCODING_PCM_16BIT */
|
||||
/**
|
||||
* @see 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. */
|
||||
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;
|
||||
/** PCM encoding with 32 bits per sample. */
|
||||
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;
|
||||
/** @see AudioFormat#ENCODING_MP3 */
|
||||
/**
|
||||
* @see 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;
|
||||
/** @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;
|
||||
/** @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;
|
||||
/** @see AudioFormat#ENCODING_AAC_XHE */
|
||||
/**
|
||||
* @see 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;
|
||||
/** AAC Error Resilient Bit-Sliced Arithmetic Coding. */
|
||||
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;
|
||||
/** @see AudioFormat#ENCODING_E_AC3 */
|
||||
/**
|
||||
* @see 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;
|
||||
/** @see AudioFormat#ENCODING_AC4 */
|
||||
/**
|
||||
* @see 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;
|
||||
/** @see AudioFormat#ENCODING_DTS_HD */
|
||||
/**
|
||||
* @see 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;
|
||||
|
||||
/** Represents the behavior affecting whether spatialization will be used. */
|
||||
|
|
@ -286,12 +329,16 @@ public final class C {
|
|||
@IntDef({SPATIALIZATION_BEHAVIOR_AUTO, SPATIALIZATION_BEHAVIOR_NEVER})
|
||||
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 */
|
||||
public static final int SPATIALIZATION_BEHAVIOR_AUTO = 0;
|
||||
/** See AudioAttributes#SPATIALIZATION_BEHAVIOR_NEVER */
|
||||
public static final int SPATIALIZATION_BEHAVIOR_NEVER = 1;
|
||||
/**
|
||||
* @see AudioAttributes#SPATIALIZATION_BEHAVIOR_AUTO
|
||||
*/
|
||||
public static final int SPATIALIZATION_BEHAVIOR_AUTO =
|
||||
AudioAttributes.SPATIALIZATION_BEHAVIOR_AUTO;
|
||||
/**
|
||||
* @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
|
||||
|
|
@ -316,27 +363,47 @@ public final class C {
|
|||
STREAM_TYPE_DEFAULT
|
||||
})
|
||||
public @interface StreamType {}
|
||||
/** @see AudioManager#STREAM_ALARM */
|
||||
/**
|
||||
* @see 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;
|
||||
/** @see AudioManager#STREAM_MUSIC */
|
||||
/**
|
||||
* @see 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;
|
||||
/** @see AudioManager#STREAM_RING */
|
||||
/**
|
||||
* @see 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;
|
||||
/** @see AudioManager#STREAM_VOICE_CALL */
|
||||
/**
|
||||
* @see 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}. */
|
||||
public static final int STREAM_TYPE_DEFAULT = STREAM_TYPE_MUSIC;
|
||||
|
||||
/**
|
||||
* Content types for audio attributes. One of {@link #CONTENT_TYPE_MOVIE}, {@link
|
||||
* #CONTENT_TYPE_MUSIC}, {@link #CONTENT_TYPE_SONIFICATION}, {@link #CONTENT_TYPE_SPEECH} or
|
||||
* {@link #CONTENT_TYPE_UNKNOWN}.
|
||||
* Content types for audio attributes. One of:
|
||||
*
|
||||
* <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
|
||||
// with Kotlin usages from before TYPE_USE was added.
|
||||
|
|
@ -344,24 +411,44 @@ public final class C {
|
|||
@Retention(RetentionPolicy.SOURCE)
|
||||
@Target({FIELD, METHOD, PARAMETER, LOCAL_VARIABLE, TYPE_USE})
|
||||
@IntDef({
|
||||
CONTENT_TYPE_MOVIE,
|
||||
CONTENT_TYPE_MUSIC,
|
||||
CONTENT_TYPE_SONIFICATION,
|
||||
CONTENT_TYPE_SPEECH,
|
||||
CONTENT_TYPE_UNKNOWN
|
||||
AUDIO_CONTENT_TYPE_MOVIE,
|
||||
AUDIO_CONTENT_TYPE_MUSIC,
|
||||
AUDIO_CONTENT_TYPE_SONIFICATION,
|
||||
AUDIO_CONTENT_TYPE_SPEECH,
|
||||
AUDIO_CONTENT_TYPE_UNKNOWN
|
||||
})
|
||||
public @interface AudioContentType {}
|
||||
/** @see android.media.AudioAttributes#CONTENT_TYPE_MOVIE */
|
||||
public static final int CONTENT_TYPE_MOVIE = android.media.AudioAttributes.CONTENT_TYPE_MOVIE;
|
||||
/** @see android.media.AudioAttributes#CONTENT_TYPE_MUSIC */
|
||||
public static final int CONTENT_TYPE_MUSIC = android.media.AudioAttributes.CONTENT_TYPE_MUSIC;
|
||||
/** @see android.media.AudioAttributes#CONTENT_TYPE_SONIFICATION */
|
||||
public static final int CONTENT_TYPE_SONIFICATION =
|
||||
android.media.AudioAttributes.CONTENT_TYPE_SONIFICATION;
|
||||
/** @see android.media.AudioAttributes#CONTENT_TYPE_SPEECH */
|
||||
public static final int CONTENT_TYPE_SPEECH = android.media.AudioAttributes.CONTENT_TYPE_SPEECH;
|
||||
/** @see android.media.AudioAttributes#CONTENT_TYPE_UNKNOWN */
|
||||
public static final int CONTENT_TYPE_UNKNOWN = android.media.AudioAttributes.CONTENT_TYPE_UNKNOWN;
|
||||
/** See {@link AudioAttributes#CONTENT_TYPE_MOVIE}. */
|
||||
public static final int AUDIO_CONTENT_TYPE_MOVIE = AudioAttributes.CONTENT_TYPE_MOVIE;
|
||||
/**
|
||||
* @deprecated Use {@link #AUDIO_CONTENT_TYPE_MOVIE} instead.
|
||||
*/
|
||||
@Deprecated public static final int CONTENT_TYPE_MOVIE = AUDIO_CONTENT_TYPE_MOVIE;
|
||||
/** See {@link AudioAttributes#CONTENT_TYPE_MUSIC}. */
|
||||
public static final int AUDIO_CONTENT_TYPE_MUSIC = AudioAttributes.CONTENT_TYPE_MUSIC;
|
||||
/**
|
||||
* @deprecated Use {@link #AUDIO_CONTENT_TYPE_MUSIC} instead.
|
||||
*/
|
||||
@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}.
|
||||
|
|
@ -378,7 +465,9 @@ public final class C {
|
|||
flag = true,
|
||||
value = {FLAG_AUDIBILITY_ENFORCED})
|
||||
public @interface AudioFlags {}
|
||||
/** @see android.media.AudioAttributes#FLAG_AUDIBILITY_ENFORCED */
|
||||
/**
|
||||
* @see android.media.AudioAttributes#FLAG_AUDIBILITY_ENFORCED
|
||||
*/
|
||||
public static final int FLAG_AUDIBILITY_ENFORCED =
|
||||
android.media.AudioAttributes.FLAG_AUDIBILITY_ENFORCED;
|
||||
|
||||
|
|
@ -416,46 +505,78 @@ public final class C {
|
|||
USAGE_VOICE_COMMUNICATION_SIGNALLING
|
||||
})
|
||||
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;
|
||||
/** @see android.media.AudioAttributes#USAGE_ASSISTANCE_ACCESSIBILITY */
|
||||
/**
|
||||
* @see android.media.AudioAttributes#USAGE_ASSISTANCE_ACCESSIBILITY
|
||||
*/
|
||||
public static final int 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 =
|
||||
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 =
|
||||
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;
|
||||
/** @see android.media.AudioAttributes#USAGE_GAME */
|
||||
/**
|
||||
* @see 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;
|
||||
/** @see android.media.AudioAttributes#USAGE_NOTIFICATION */
|
||||
/**
|
||||
* @see 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 =
|
||||
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 =
|
||||
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 =
|
||||
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 =
|
||||
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 =
|
||||
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;
|
||||
/** @see android.media.AudioAttributes#USAGE_VOICE_COMMUNICATION */
|
||||
/**
|
||||
* @see android.media.AudioAttributes#USAGE_VOICE_COMMUNICATION
|
||||
*/
|
||||
public static final int 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 =
|
||||
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
|
||||
* #BUFFER_FLAG_KEY_FRAME}, {@link #BUFFER_FLAG_END_OF_STREAM}, {@link #BUFFER_FLAG_LAST_SAMPLE},
|
||||
* {@link #BUFFER_FLAG_ENCRYPTED} and {@link #BUFFER_FLAG_DECODE_ONLY}.
|
||||
* #BUFFER_FLAG_KEY_FRAME}, {@link #BUFFER_FLAG_END_OF_STREAM}, {@link #BUFFER_FLAG_FIRST_SAMPLE},
|
||||
* {@link #BUFFER_FLAG_LAST_SAMPLE}, {@link #BUFFER_FLAG_ENCRYPTED} and {@link
|
||||
* #BUFFER_FLAG_DECODE_ONLY}.
|
||||
*/
|
||||
@Documented
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
|
|
@ -490,6 +612,7 @@ public final class C {
|
|||
value = {
|
||||
BUFFER_FLAG_KEY_FRAME,
|
||||
BUFFER_FLAG_END_OF_STREAM,
|
||||
BUFFER_FLAG_FIRST_SAMPLE,
|
||||
BUFFER_FLAG_HAS_SUPPLEMENTAL_DATA,
|
||||
BUFFER_FLAG_LAST_SAMPLE,
|
||||
BUFFER_FLAG_ENCRYPTED,
|
||||
|
|
@ -500,6 +623,8 @@ public final class C {
|
|||
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. */
|
||||
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. */
|
||||
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. */
|
||||
|
|
@ -607,29 +732,59 @@ public final class C {
|
|||
public static final String LANGUAGE_UNDETERMINED = "und";
|
||||
|
||||
/**
|
||||
* Represents a streaming or other media type. One of {@link #TYPE_DASH}, {@link #TYPE_SS}, {@link
|
||||
* #TYPE_HLS}, {@link #TYPE_RTSP} or {@link #TYPE_OTHER}.
|
||||
* Represents a streaming or other media type. One of:
|
||||
*
|
||||
* <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
|
||||
// with Kotlin usages from before TYPE_USE was added.
|
||||
@Documented
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@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 {}
|
||||
/** Value returned by {@link Util#inferContentType(String)} for DASH manifests. */
|
||||
public static final int 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 representing a DASH manifest. */
|
||||
public static final int CONTENT_TYPE_DASH = 0;
|
||||
/**
|
||||
* Value returned by {@link Util#inferContentType(String)} for files other than DASH, HLS or
|
||||
* Smooth Streaming manifests, or RTSP URIs.
|
||||
* @deprecated Use {@link #CONTENT_TYPE_DASH} instead.
|
||||
*/
|
||||
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. */
|
||||
public static final int RESULT_END_OF_INPUT = -1;
|
||||
|
|
@ -876,11 +1031,17 @@ public final class C {
|
|||
@Target(TYPE_USE)
|
||||
@IntDef({Format.NO_VALUE, COLOR_SPACE_BT709, COLOR_SPACE_BT601, COLOR_SPACE_BT2020})
|
||||
public @interface ColorSpace {}
|
||||
/** @see MediaFormat#COLOR_STANDARD_BT709 */
|
||||
/**
|
||||
* @see 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;
|
||||
/** @see MediaFormat#COLOR_STANDARD_BT2020 */
|
||||
/**
|
||||
* @see 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)
|
||||
@IntDef({Format.NO_VALUE, COLOR_TRANSFER_SDR, COLOR_TRANSFER_ST2084, COLOR_TRANSFER_HLG})
|
||||
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;
|
||||
/** @see MediaFormat#COLOR_TRANSFER_ST2084 */
|
||||
/**
|
||||
* @see 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;
|
||||
|
||||
/**
|
||||
|
|
@ -908,9 +1075,13 @@ public final class C {
|
|||
@Target(TYPE_USE)
|
||||
@IntDef({Format.NO_VALUE, COLOR_RANGE_LIMITED, COLOR_RANGE_FULL})
|
||||
public @interface ColorRange {}
|
||||
/** @see MediaFormat#COLOR_RANGE_LIMITED */
|
||||
/**
|
||||
* @see 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;
|
||||
|
||||
/** Video projection types. */
|
||||
|
|
@ -1173,7 +1344,9 @@ public final class C {
|
|||
*/
|
||||
public static final int FORMAT_UNSUPPORTED_TYPE = 0b000;
|
||||
|
||||
/** @deprecated Use {@link Util#usToMs(long)}. */
|
||||
/**
|
||||
* @deprecated Use {@link Util#usToMs(long)}.
|
||||
*/
|
||||
@InlineMe(
|
||||
replacement = "Util.usToMs(timeUs)",
|
||||
imports = {"com.google.android.exoplayer2.util.Util"})
|
||||
|
|
@ -1182,7 +1355,9 @@ public final class C {
|
|||
return Util.usToMs(timeUs);
|
||||
}
|
||||
|
||||
/** @deprecated Use {@link Util#msToUs(long)}. */
|
||||
/**
|
||||
* @deprecated Use {@link Util#msToUs(long)}.
|
||||
*/
|
||||
@InlineMe(
|
||||
replacement = "Util.msToUs(timeMs)",
|
||||
imports = {"com.google.android.exoplayer2.util.Util"})
|
||||
|
|
@ -1191,7 +1366,9 @@ public final class C {
|
|||
return Util.msToUs(timeMs);
|
||||
}
|
||||
|
||||
/** @deprecated Use {@link Util#generateAudioSessionIdV21(Context)}. */
|
||||
/**
|
||||
* @deprecated Use {@link Util#generateAudioSessionIdV21(Context)}.
|
||||
*/
|
||||
@InlineMe(
|
||||
replacement = "Util.generateAudioSessionIdV21(context)",
|
||||
imports = {"com.google.android.exoplayer2.util.Util"})
|
||||
|
|
@ -1201,7 +1378,9 @@ public final class C {
|
|||
return Util.generateAudioSessionIdV21(context);
|
||||
}
|
||||
|
||||
/** @deprecated Use {@link Util#getFormatSupportString(int)}. */
|
||||
/**
|
||||
* @deprecated Use {@link Util#getFormatSupportString(int)}.
|
||||
*/
|
||||
@InlineMe(
|
||||
replacement = "Util.getFormatSupportString(formatSupport)",
|
||||
imports = {"com.google.android.exoplayer2.util.Util"})
|
||||
|
|
@ -1210,7 +1389,9 @@ public final class C {
|
|||
return Util.getFormatSupportString(formatSupport);
|
||||
}
|
||||
|
||||
/** @deprecated Use {@link Util#getErrorCodeForMediaDrmErrorCode(int)}. */
|
||||
/**
|
||||
* @deprecated Use {@link Util#getErrorCodeForMediaDrmErrorCode(int)}.
|
||||
*/
|
||||
@InlineMe(
|
||||
replacement = "Util.getErrorCodeForMediaDrmErrorCode(mediaDrmErrorCode)",
|
||||
imports = {"com.google.android.exoplayer2.util.Util"})
|
||||
|
|
|
|||
|
|
@ -771,7 +771,9 @@ public final class Format implements Bundleable {
|
|||
|
||||
// Video.
|
||||
|
||||
/** @deprecated Use {@link Format.Builder}. */
|
||||
/**
|
||||
* @deprecated Use {@link Format.Builder}.
|
||||
*/
|
||||
@Deprecated
|
||||
public static Format createVideoSampleFormat(
|
||||
@Nullable String id,
|
||||
|
|
@ -799,7 +801,9 @@ public final class Format implements Bundleable {
|
|||
.build();
|
||||
}
|
||||
|
||||
/** @deprecated Use {@link Format.Builder}. */
|
||||
/**
|
||||
* @deprecated Use {@link Format.Builder}.
|
||||
*/
|
||||
@Deprecated
|
||||
public static Format createVideoSampleFormat(
|
||||
@Nullable String id,
|
||||
|
|
@ -833,7 +837,9 @@ public final class Format implements Bundleable {
|
|||
|
||||
// Audio.
|
||||
|
||||
/** @deprecated Use {@link Format.Builder}. */
|
||||
/**
|
||||
* @deprecated Use {@link Format.Builder}.
|
||||
*/
|
||||
@Deprecated
|
||||
public static Format createAudioSampleFormat(
|
||||
@Nullable String id,
|
||||
|
|
@ -863,7 +869,9 @@ public final class Format implements Bundleable {
|
|||
.build();
|
||||
}
|
||||
|
||||
/** @deprecated Use {@link Format.Builder}. */
|
||||
/**
|
||||
* @deprecated Use {@link Format.Builder}.
|
||||
*/
|
||||
@Deprecated
|
||||
public static Format createAudioSampleFormat(
|
||||
@Nullable String id,
|
||||
|
|
@ -897,7 +905,9 @@ public final class Format implements Bundleable {
|
|||
|
||||
// Generic.
|
||||
|
||||
/** @deprecated Use {@link Format.Builder}. */
|
||||
/**
|
||||
* @deprecated Use {@link Format.Builder}.
|
||||
*/
|
||||
@Deprecated
|
||||
public static Format createContainerFormat(
|
||||
@Nullable String id,
|
||||
|
|
@ -923,7 +933,9 @@ public final class Format implements Bundleable {
|
|||
.build();
|
||||
}
|
||||
|
||||
/** @deprecated Use {@link Format.Builder}. */
|
||||
/**
|
||||
* @deprecated Use {@link Format.Builder}.
|
||||
*/
|
||||
@Deprecated
|
||||
public static Format createSampleFormat(@Nullable String id, @Nullable String sampleMimeType) {
|
||||
return new Builder().setId(id).setSampleMimeType(sampleMimeType).build();
|
||||
|
|
@ -981,25 +993,33 @@ public final class Format implements Bundleable {
|
|||
return new Builder(this);
|
||||
}
|
||||
|
||||
/** @deprecated Use {@link #buildUpon()} and {@link Builder#setMaxInputSize(int)}. */
|
||||
/**
|
||||
* @deprecated Use {@link #buildUpon()} and {@link Builder#setMaxInputSize(int)}.
|
||||
*/
|
||||
@Deprecated
|
||||
public Format copyWithMaxInputSize(int maxInputSize) {
|
||||
return buildUpon().setMaxInputSize(maxInputSize).build();
|
||||
}
|
||||
|
||||
/** @deprecated Use {@link #buildUpon()} and {@link Builder#setSubsampleOffsetUs(long)}. */
|
||||
/**
|
||||
* @deprecated Use {@link #buildUpon()} and {@link Builder#setSubsampleOffsetUs(long)}.
|
||||
*/
|
||||
@Deprecated
|
||||
public Format copyWithSubsampleOffsetUs(long subsampleOffsetUs) {
|
||||
return buildUpon().setSubsampleOffsetUs(subsampleOffsetUs).build();
|
||||
}
|
||||
|
||||
/** @deprecated Use {@link #buildUpon()} and {@link Builder#setLabel(String)} . */
|
||||
/**
|
||||
* @deprecated Use {@link #buildUpon()} and {@link Builder#setLabel(String)} .
|
||||
*/
|
||||
@Deprecated
|
||||
public Format copyWithLabel(@Nullable String label) {
|
||||
return buildUpon().setLabel(label).build();
|
||||
}
|
||||
|
||||
/** @deprecated Use {@link #withManifestFormatInfo(Format)}. */
|
||||
/**
|
||||
* @deprecated Use {@link #withManifestFormatInfo(Format)}.
|
||||
*/
|
||||
@Deprecated
|
||||
public Format copyWithManifestFormatInfo(Format manifestFormat) {
|
||||
return withManifestFormatInfo(manifestFormat);
|
||||
|
|
@ -1081,19 +1101,25 @@ public final class Format implements Bundleable {
|
|||
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
|
||||
public Format copyWithFrameRate(float frameRate) {
|
||||
return buildUpon().setFrameRate(frameRate).build();
|
||||
}
|
||||
|
||||
/** @deprecated Use {@link #buildUpon()} and {@link Builder#setDrmInitData(DrmInitData)}. */
|
||||
/**
|
||||
* @deprecated Use {@link #buildUpon()} and {@link Builder#setDrmInitData(DrmInitData)}.
|
||||
*/
|
||||
@Deprecated
|
||||
public Format copyWithDrmInitData(@Nullable DrmInitData drmInitData) {
|
||||
return buildUpon().setDrmInitData(drmInitData).build();
|
||||
}
|
||||
|
||||
/** @deprecated Use {@link #buildUpon()} and {@link Builder#setMetadata(Metadata)}. */
|
||||
/**
|
||||
* @deprecated Use {@link #buildUpon()} and {@link Builder#setMetadata(Metadata)}.
|
||||
*/
|
||||
@Deprecated
|
||||
public Format copyWithMetadata(@Nullable Metadata metadata) {
|
||||
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.putByteArray(keyForField(FIELD_PROJECTION_DATA), projectionData);
|
||||
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.
|
||||
bundle.putInt(keyForField(FIELD_CHANNEL_COUNT), channelCount);
|
||||
bundle.putInt(keyForField(FIELD_SAMPLE_RATE), sampleRate);
|
||||
|
|
@ -1568,11 +1596,13 @@ public final class Format implements Bundleable {
|
|||
bundle.getFloat(
|
||||
keyForField(FIELD_PIXEL_WIDTH_HEIGHT_RATIO), DEFAULT.pixelWidthHeightRatio))
|
||||
.setProjectionData(bundle.getByteArray(keyForField(FIELD_PROJECTION_DATA)))
|
||||
.setStereoMode(bundle.getInt(keyForField(FIELD_STEREO_MODE), DEFAULT.stereoMode))
|
||||
.setColorInfo(
|
||||
BundleableUtil.fromNullableBundle(
|
||||
ColorInfo.CREATOR, bundle.getBundle(keyForField(FIELD_COLOR_INFO))))
|
||||
// Audio specific.
|
||||
.setStereoMode(bundle.getInt(keyForField(FIELD_STEREO_MODE), DEFAULT.stereoMode));
|
||||
Bundle colorInfoBundle = bundle.getBundle(keyForField(FIELD_COLOR_INFO));
|
||||
if (colorInfoBundle != null) {
|
||||
builder.setColorInfo(ColorInfo.CREATOR.fromBundle(colorInfoBundle));
|
||||
}
|
||||
// Audio specific.
|
||||
builder
|
||||
.setChannelCount(bundle.getInt(keyForField(FIELD_CHANNEL_COUNT), DEFAULT.channelCount))
|
||||
.setSampleRate(bundle.getInt(keyForField(FIELD_SAMPLE_RATE), DEFAULT.sampleRate))
|
||||
.setPcmEncoding(bundle.getInt(keyForField(FIELD_PCM_ENCODING), DEFAULT.pcmEncoding))
|
||||
|
|
|
|||
|
|
@ -23,9 +23,8 @@ import android.view.TextureView;
|
|||
import androidx.annotation.Nullable;
|
||||
import com.google.android.exoplayer2.audio.AudioAttributes;
|
||||
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.trackselection.TrackSelectionArray;
|
||||
import com.google.android.exoplayer2.text.CueGroup;
|
||||
import com.google.android.exoplayer2.trackselection.TrackSelectionParameters;
|
||||
import com.google.android.exoplayer2.video.VideoSize;
|
||||
import java.util.List;
|
||||
|
|
@ -302,7 +301,11 @@ public class ForwardingPlayer implements Player {
|
|||
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
|
||||
@Deprecated
|
||||
@Override
|
||||
|
|
@ -310,7 +313,11 @@ public class ForwardingPlayer implements Player {
|
|||
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
|
||||
@Deprecated
|
||||
@Override
|
||||
|
|
@ -324,7 +331,11 @@ public class ForwardingPlayer implements Player {
|
|||
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
|
||||
@Deprecated
|
||||
@Override
|
||||
|
|
@ -332,7 +343,11 @@ public class ForwardingPlayer implements Player {
|
|||
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
|
||||
@Deprecated
|
||||
@Override
|
||||
|
|
@ -358,7 +373,11 @@ public class ForwardingPlayer implements Player {
|
|||
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
|
||||
@Deprecated
|
||||
@Override
|
||||
|
|
@ -366,7 +385,11 @@ public class ForwardingPlayer implements Player {
|
|||
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
|
||||
@Deprecated
|
||||
@Override
|
||||
|
|
@ -380,7 +403,11 @@ public class ForwardingPlayer implements Player {
|
|||
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
|
||||
@Deprecated
|
||||
@Override
|
||||
|
|
@ -388,7 +415,11 @@ public class ForwardingPlayer implements Player {
|
|||
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
|
||||
@Deprecated
|
||||
@Override
|
||||
|
|
@ -432,7 +463,13 @@ public class ForwardingPlayer implements Player {
|
|||
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
|
||||
@Deprecated
|
||||
@Override
|
||||
|
|
@ -446,26 +483,10 @@ public class ForwardingPlayer implements Player {
|
|||
player.release();
|
||||
}
|
||||
|
||||
/** Calls {@link Player#getCurrentTrackGroups()} on the delegate and returns the result. */
|
||||
@SuppressWarnings("deprecation") // Forwarding to deprecated method
|
||||
@Deprecated
|
||||
/** Calls {@link Player#getCurrentTracks()} on the delegate and returns the result. */
|
||||
@Override
|
||||
public TrackGroupArray getCurrentTrackGroups() {
|
||||
return player.getCurrentTrackGroups();
|
||||
}
|
||||
|
||||
/** 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();
|
||||
public Tracks getCurrentTracks() {
|
||||
return player.getCurrentTracks();
|
||||
}
|
||||
|
||||
/** Calls {@link Player#getTrackSelectionParameters()} on the delegate and returns the result. */
|
||||
|
|
@ -517,7 +538,11 @@ public class ForwardingPlayer implements Player {
|
|||
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
|
||||
@Deprecated
|
||||
@Override
|
||||
|
|
@ -531,7 +556,11 @@ public class ForwardingPlayer implements Player {
|
|||
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
|
||||
@Deprecated
|
||||
@Override
|
||||
|
|
@ -545,7 +574,11 @@ public class ForwardingPlayer implements Player {
|
|||
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
|
||||
@Deprecated
|
||||
@Override
|
||||
|
|
@ -608,7 +641,11 @@ public class ForwardingPlayer implements Player {
|
|||
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
|
||||
@Deprecated
|
||||
@Override
|
||||
|
|
@ -622,7 +659,11 @@ public class ForwardingPlayer implements Player {
|
|||
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
|
||||
@Deprecated
|
||||
@Override
|
||||
|
|
@ -642,7 +683,11 @@ public class ForwardingPlayer implements Player {
|
|||
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
|
||||
@Deprecated
|
||||
@Override
|
||||
|
|
@ -772,7 +817,7 @@ public class ForwardingPlayer implements Player {
|
|||
|
||||
/** Calls {@link Player#getCurrentCues()} on the delegate and returns the result. */
|
||||
@Override
|
||||
public List<Cue> getCurrentCues() {
|
||||
public CueGroup getCurrentCues() {
|
||||
return player.getCurrentCues();
|
||||
}
|
||||
|
||||
|
|
@ -851,14 +896,8 @@ public class ForwardingPlayer implements Player {
|
|||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("deprecation")
|
||||
public void onTracksChanged(TrackGroupArray trackGroups, TrackSelectionArray trackSelections) {
|
||||
listener.onTracksChanged(trackGroups, trackSelections);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTracksInfoChanged(TracksInfo tracksInfo) {
|
||||
listener.onTracksInfoChanged(tracksInfo);
|
||||
public void onTracksChanged(Tracks tracks) {
|
||||
listener.onTracksChanged(tracks);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -1018,6 +1057,11 @@ public class ForwardingPlayer implements Player {
|
|||
listener.onCues(cues);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCues(CueGroup cueGroup) {
|
||||
listener.onCues(cueGroup);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMetadata(Metadata metadata) {
|
||||
listener.onMetadata(metadata);
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ import com.google.android.exoplayer2.util.Assertions;
|
|||
import com.google.android.exoplayer2.util.Util;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.errorprone.annotations.InlineMe;
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.Retention;
|
||||
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
|
||||
// are removed.
|
||||
private LiveConfiguration.Builder liveConfiguration;
|
||||
private RequestMetadata requestMetadata;
|
||||
|
||||
/** Creates a builder. */
|
||||
@SuppressWarnings("deprecation") // Temporarily uses DrmConfiguration.Builder() constructor.
|
||||
|
|
@ -93,6 +95,7 @@ public final class MediaItem implements Bundleable {
|
|||
streamKeys = Collections.emptyList();
|
||||
subtitleConfigurations = ImmutableList.of();
|
||||
liveConfiguration = new LiveConfiguration.Builder();
|
||||
requestMetadata = RequestMetadata.EMPTY;
|
||||
}
|
||||
|
||||
private Builder(MediaItem mediaItem) {
|
||||
|
|
@ -101,6 +104,7 @@ public final class MediaItem implements Bundleable {
|
|||
mediaId = mediaItem.mediaId;
|
||||
mediaMetadata = mediaItem.mediaMetadata;
|
||||
liveConfiguration = mediaItem.liveConfiguration.buildUpon();
|
||||
requestMetadata = mediaItem.requestMetadata;
|
||||
@Nullable LocalConfiguration localConfiguration = mediaItem.localConfiguration;
|
||||
if (localConfiguration != null) {
|
||||
customCacheKey = localConfiguration.customCacheKey;
|
||||
|
|
@ -303,11 +307,11 @@ public final class MediaItem implements Bundleable {
|
|||
|
||||
/**
|
||||
* @deprecated Use {@link #setDrmConfiguration(DrmConfiguration)} and {@link
|
||||
* DrmConfiguration.Builder#forceSessionsForAudioAndVideoTracks(boolean)} instead.
|
||||
* DrmConfiguration.Builder#setForceSessionsForAudioAndVideoTracks(boolean)} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public Builder setDrmSessionForClearPeriods(boolean sessionForClearPeriods) {
|
||||
drmConfiguration.forceSessionsForAudioAndVideoTracks(sessionForClearPeriods);
|
||||
drmConfiguration.setForceSessionsForAudioAndVideoTracks(sessionForClearPeriods);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
|
@ -499,6 +503,12 @@ public final class MediaItem implements Bundleable {
|
|||
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. */
|
||||
@SuppressWarnings("deprecation") // Using PlaybackProperties while it exists.
|
||||
public MediaItem build() {
|
||||
|
|
@ -523,7 +533,8 @@ public final class MediaItem implements Bundleable {
|
|||
clippingConfiguration.buildClippingProperties(),
|
||||
localConfiguration,
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* @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
|
||||
* 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
|
||||
* #setForcedSessionTrackTypes(List)}.
|
||||
*/
|
||||
public Builder forceSessionsForAudioAndVideoTracks(
|
||||
boolean useClearSessionsForAudioAndVideoTracks) {
|
||||
public Builder setForceSessionsForAudioAndVideoTracks(
|
||||
boolean forceSessionsForAudioAndVideoTracks) {
|
||||
this.setForcedSessionTrackTypes(
|
||||
useClearSessionsForAudioAndVideoTracks
|
||||
forceSessionsForAudioAndVideoTracks
|
||||
? ImmutableList.of(C.TRACK_TYPE_VIDEO, C.TRACK_TYPE_AUDIO)
|
||||
: ImmutableList.of());
|
||||
return this;
|
||||
|
|
@ -654,10 +677,10 @@ public final class MediaItem implements Bundleable {
|
|||
* 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
|
||||
* 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
|
||||
* #forceSessionsForAudioAndVideoTracks(boolean)}.
|
||||
* #setForceSessionsForAudioAndVideoTracks(boolean)}.
|
||||
*/
|
||||
public Builder setForcedSessionTrackTypes(
|
||||
List<@C.TrackType Integer> forcedSessionTrackTypes) {
|
||||
|
|
@ -686,7 +709,9 @@ public final class MediaItem implements Bundleable {
|
|||
/** The UUID of the protection scheme. */
|
||||
public final UUID scheme;
|
||||
|
||||
/** @deprecated Use {@link #scheme} instead. */
|
||||
/**
|
||||
* @deprecated Use {@link #scheme} instead.
|
||||
*/
|
||||
@Deprecated public final UUID uuid;
|
||||
|
||||
/**
|
||||
|
|
@ -695,7 +720,9 @@ public final class MediaItem implements Bundleable {
|
|||
*/
|
||||
@Nullable public final Uri licenseUri;
|
||||
|
||||
/** @deprecated Use {@link #licenseRequestHeaders} instead. */
|
||||
/**
|
||||
* @deprecated Use {@link #licenseRequestHeaders} instead.
|
||||
*/
|
||||
@Deprecated public final ImmutableMap<String, String> requestHeaders;
|
||||
|
||||
/** 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;
|
||||
|
||||
/** @deprecated Use {@link #forcedSessionTrackTypes}. */
|
||||
/**
|
||||
* @deprecated Use {@link #forcedSessionTrackTypes}.
|
||||
*/
|
||||
@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.
|
||||
|
|
@ -903,7 +932,9 @@ public final class MediaItem implements Bundleable {
|
|||
|
||||
/** Optional subtitles to be sideloaded. */
|
||||
public final ImmutableList<SubtitleConfiguration> subtitleConfigurations;
|
||||
/** @deprecated Use {@link #subtitleConfigurations} instead. */
|
||||
/**
|
||||
* @deprecated Use {@link #subtitleConfigurations} instead.
|
||||
*/
|
||||
@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
|
||||
public static final class PlaybackProperties extends LocalConfiguration {
|
||||
|
||||
|
|
@ -1133,7 +1166,9 @@ public final class MediaItem implements Bundleable {
|
|||
builder.maxPlaybackSpeed);
|
||||
}
|
||||
|
||||
/** @deprecated Use {@link Builder} instead. */
|
||||
/**
|
||||
* @deprecated Use {@link Builder} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public LiveConfiguration(
|
||||
long targetOffsetMs,
|
||||
|
|
@ -1269,7 +1304,7 @@ public final class MediaItem implements Bundleable {
|
|||
}
|
||||
|
||||
/** Sets the MIME type. */
|
||||
public Builder setMimeType(String mimeType) {
|
||||
public Builder setMimeType(@Nullable String mimeType) {
|
||||
this.mimeType = mimeType;
|
||||
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
|
||||
public static final class Subtitle extends SubtitleConfiguration {
|
||||
|
||||
/** @deprecated Use {@link Builder} instead. */
|
||||
/**
|
||||
* @deprecated Use {@link Builder} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public Subtitle(Uri uri, String mimeType, @Nullable String language) {
|
||||
this(uri, mimeType, language, /* selectionFlags= */ 0);
|
||||
}
|
||||
|
||||
/** @deprecated Use {@link Builder} instead. */
|
||||
/**
|
||||
* @deprecated Use {@link Builder} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public Subtitle(
|
||||
Uri uri, String mimeType, @Nullable String language, @C.SelectionFlags int selectionFlags) {
|
||||
this(uri, mimeType, language, selectionFlags, /* roleFlags= */ 0, /* label= */ null);
|
||||
}
|
||||
|
||||
/** @deprecated Use {@link Builder} instead. */
|
||||
/**
|
||||
* @deprecated Use {@link Builder} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public Subtitle(
|
||||
Uri uri,
|
||||
|
|
@ -1516,7 +1559,9 @@ public final class MediaItem implements Bundleable {
|
|||
return buildClippingProperties();
|
||||
}
|
||||
|
||||
/** @deprecated Use {@link #build()} instead. */
|
||||
/**
|
||||
* @deprecated Use {@link #build()} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public ClippingProperties buildClippingProperties() {
|
||||
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
|
||||
public static final class ClippingProperties extends ClippingConfiguration {
|
||||
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
|
||||
* Builder#setMediaId(String)}.
|
||||
|
|
@ -1671,7 +1856,9 @@ public final class MediaItem implements Bundleable {
|
|||
* boundaries.
|
||||
*/
|
||||
@Nullable public final LocalConfiguration localConfiguration;
|
||||
/** @deprecated Use {@link #localConfiguration} instead. */
|
||||
/**
|
||||
* @deprecated Use {@link #localConfiguration} instead.
|
||||
*/
|
||||
@Deprecated @Nullable public final PlaybackProperties playbackProperties;
|
||||
|
||||
/** The live playback configuration. */
|
||||
|
|
@ -1682,9 +1869,14 @@ public final class MediaItem implements Bundleable {
|
|||
|
||||
/** The clipping properties. */
|
||||
public final ClippingConfiguration clippingConfiguration;
|
||||
/** @deprecated Use {@link #clippingConfiguration} instead. */
|
||||
/**
|
||||
* @deprecated Use {@link #clippingConfiguration} instead.
|
||||
*/
|
||||
@Deprecated public final ClippingProperties clippingProperties;
|
||||
|
||||
/** The media {@link RequestMetadata}. */
|
||||
public final RequestMetadata requestMetadata;
|
||||
|
||||
// Using PlaybackProperties and ClippingProperties until they're deleted.
|
||||
@SuppressWarnings("deprecation")
|
||||
private MediaItem(
|
||||
|
|
@ -1692,7 +1884,8 @@ public final class MediaItem implements Bundleable {
|
|||
ClippingProperties clippingConfiguration,
|
||||
@Nullable PlaybackProperties localConfiguration,
|
||||
LiveConfiguration liveConfiguration,
|
||||
MediaMetadata mediaMetadata) {
|
||||
MediaMetadata mediaMetadata,
|
||||
RequestMetadata requestMetadata) {
|
||||
this.mediaId = mediaId;
|
||||
this.localConfiguration = localConfiguration;
|
||||
this.playbackProperties = localConfiguration;
|
||||
|
|
@ -1700,6 +1893,7 @@ public final class MediaItem implements Bundleable {
|
|||
this.mediaMetadata = mediaMetadata;
|
||||
this.clippingConfiguration = clippingConfiguration;
|
||||
this.clippingProperties = clippingConfiguration;
|
||||
this.requestMetadata = requestMetadata;
|
||||
}
|
||||
|
||||
/** 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)
|
||||
&& Util.areEqual(localConfiguration, other.localConfiguration)
|
||||
&& Util.areEqual(liveConfiguration, other.liveConfiguration)
|
||||
&& Util.areEqual(mediaMetadata, other.mediaMetadata);
|
||||
&& Util.areEqual(mediaMetadata, other.mediaMetadata)
|
||||
&& Util.areEqual(requestMetadata, other.requestMetadata);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -1732,6 +1927,7 @@ public final class MediaItem implements Bundleable {
|
|||
result = 31 * result + liveConfiguration.hashCode();
|
||||
result = 31 * result + clippingConfiguration.hashCode();
|
||||
result = 31 * result + mediaMetadata.hashCode();
|
||||
result = 31 * result + requestMetadata.hashCode();
|
||||
return result;
|
||||
}
|
||||
|
||||
|
|
@ -1744,7 +1940,8 @@ public final class MediaItem implements Bundleable {
|
|||
FIELD_MEDIA_ID,
|
||||
FIELD_LIVE_CONFIGURATION,
|
||||
FIELD_MEDIA_METADATA,
|
||||
FIELD_CLIPPING_PROPERTIES
|
||||
FIELD_CLIPPING_PROPERTIES,
|
||||
FIELD_REQUEST_METADATA
|
||||
})
|
||||
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_MEDIA_METADATA = 2;
|
||||
private static final int FIELD_CLIPPING_PROPERTIES = 3;
|
||||
private static final int FIELD_REQUEST_METADATA = 4;
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
|
|
@ -1766,6 +1964,7 @@ public final class MediaItem implements Bundleable {
|
|||
bundle.putBundle(keyForField(FIELD_LIVE_CONFIGURATION), liveConfiguration.toBundle());
|
||||
bundle.putBundle(keyForField(FIELD_MEDIA_METADATA), mediaMetadata.toBundle());
|
||||
bundle.putBundle(keyForField(FIELD_CLIPPING_PROPERTIES), clippingConfiguration.toBundle());
|
||||
bundle.putBundle(keyForField(FIELD_REQUEST_METADATA), requestMetadata.toBundle());
|
||||
return bundle;
|
||||
}
|
||||
|
||||
|
|
@ -1802,12 +2001,20 @@ public final class MediaItem implements Bundleable {
|
|||
} else {
|
||||
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(
|
||||
mediaId,
|
||||
clippingConfiguration,
|
||||
/* localConfiguration= */ null,
|
||||
liveConfiguration,
|
||||
mediaMetadata);
|
||||
mediaMetadata,
|
||||
requestMetadata);
|
||||
}
|
||||
|
||||
private static String keyForField(@FieldNumber int field) {
|
||||
|
|
|
|||
|
|
@ -52,7 +52,6 @@ public final class MediaMetadata implements Bundleable {
|
|||
@Nullable private CharSequence displayTitle;
|
||||
@Nullable private CharSequence subtitle;
|
||||
@Nullable private CharSequence description;
|
||||
@Nullable private Uri mediaUri;
|
||||
@Nullable private Rating userRating;
|
||||
@Nullable private Rating overallRating;
|
||||
@Nullable private byte[] artworkData;
|
||||
|
|
@ -88,7 +87,6 @@ public final class MediaMetadata implements Bundleable {
|
|||
this.displayTitle = mediaMetadata.displayTitle;
|
||||
this.subtitle = mediaMetadata.subtitle;
|
||||
this.description = mediaMetadata.description;
|
||||
this.mediaUri = mediaMetadata.mediaUri;
|
||||
this.userRating = mediaMetadata.userRating;
|
||||
this.overallRating = mediaMetadata.overallRating;
|
||||
this.artworkData = mediaMetadata.artworkData;
|
||||
|
|
@ -161,12 +159,6 @@ public final class MediaMetadata implements Bundleable {
|
|||
return this;
|
||||
}
|
||||
|
||||
/** Sets the media {@link Uri}. */
|
||||
public Builder setMediaUri(@Nullable Uri mediaUri) {
|
||||
this.mediaUri = mediaUri;
|
||||
return this;
|
||||
}
|
||||
|
||||
/** Sets the user {@link Rating}. */
|
||||
public Builder setUserRating(@Nullable Rating userRating) {
|
||||
this.userRating = userRating;
|
||||
|
|
@ -247,7 +239,9 @@ public final class MediaMetadata implements Bundleable {
|
|||
return this;
|
||||
}
|
||||
|
||||
/** @deprecated Use {@link #setRecordingYear(Integer)} instead. */
|
||||
/**
|
||||
* @deprecated Use {@link #setRecordingYear(Integer)} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public Builder setYear(@Nullable Integer year) {
|
||||
return setRecordingYear(year);
|
||||
|
|
@ -424,9 +418,6 @@ public final class MediaMetadata implements Bundleable {
|
|||
if (mediaMetadata.description != null) {
|
||||
setDescription(mediaMetadata.description);
|
||||
}
|
||||
if (mediaMetadata.mediaUri != null) {
|
||||
setMediaUri(mediaMetadata.mediaUri);
|
||||
}
|
||||
if (mediaMetadata.userRating != null) {
|
||||
setUserRating(mediaMetadata.userRating);
|
||||
}
|
||||
|
|
@ -629,8 +620,6 @@ public final class MediaMetadata implements Bundleable {
|
|||
@Nullable public final CharSequence subtitle;
|
||||
/** Optional description. */
|
||||
@Nullable public final CharSequence description;
|
||||
/** Optional media {@link Uri}. */
|
||||
@Nullable public final Uri mediaUri;
|
||||
/** Optional user {@link Rating}. */
|
||||
@Nullable public final Rating userRating;
|
||||
/** Optional overall {@link Rating}. */
|
||||
|
|
@ -649,7 +638,9 @@ public final class MediaMetadata implements Bundleable {
|
|||
@Nullable public final @FolderType Integer folderType;
|
||||
/** Optional boolean for media playability. */
|
||||
@Nullable public final Boolean isPlayable;
|
||||
/** @deprecated Use {@link #recordingYear} instead. */
|
||||
/**
|
||||
* @deprecated Use {@link #recordingYear} instead.
|
||||
*/
|
||||
@Deprecated @Nullable public final Integer year;
|
||||
/** Optional year of the recording date. */
|
||||
@Nullable public final Integer recordingYear;
|
||||
|
|
@ -713,7 +704,6 @@ public final class MediaMetadata implements Bundleable {
|
|||
this.displayTitle = builder.displayTitle;
|
||||
this.subtitle = builder.subtitle;
|
||||
this.description = builder.description;
|
||||
this.mediaUri = builder.mediaUri;
|
||||
this.userRating = builder.userRating;
|
||||
this.overallRating = builder.overallRating;
|
||||
this.artworkData = builder.artworkData;
|
||||
|
|
@ -762,7 +752,6 @@ public final class MediaMetadata implements Bundleable {
|
|||
&& Util.areEqual(displayTitle, that.displayTitle)
|
||||
&& Util.areEqual(subtitle, that.subtitle)
|
||||
&& Util.areEqual(description, that.description)
|
||||
&& Util.areEqual(mediaUri, that.mediaUri)
|
||||
&& Util.areEqual(userRating, that.userRating)
|
||||
&& Util.areEqual(overallRating, that.overallRating)
|
||||
&& Arrays.equals(artworkData, that.artworkData)
|
||||
|
|
@ -798,7 +787,6 @@ public final class MediaMetadata implements Bundleable {
|
|||
displayTitle,
|
||||
subtitle,
|
||||
description,
|
||||
mediaUri,
|
||||
userRating,
|
||||
overallRating,
|
||||
Arrays.hashCode(artworkData),
|
||||
|
|
@ -908,7 +896,6 @@ public final class MediaMetadata implements Bundleable {
|
|||
bundle.putCharSequence(keyForField(FIELD_DISPLAY_TITLE), displayTitle);
|
||||
bundle.putCharSequence(keyForField(FIELD_SUBTITLE), subtitle);
|
||||
bundle.putCharSequence(keyForField(FIELD_DESCRIPTION), description);
|
||||
bundle.putParcelable(keyForField(FIELD_MEDIA_URI), mediaUri);
|
||||
bundle.putByteArray(keyForField(FIELD_ARTWORK_DATA), artworkData);
|
||||
bundle.putParcelable(keyForField(FIELD_ARTWORK_URI), artworkUri);
|
||||
bundle.putCharSequence(keyForField(FIELD_WRITER), writer);
|
||||
|
|
@ -982,7 +969,6 @@ public final class MediaMetadata implements Bundleable {
|
|||
.setDisplayTitle(bundle.getCharSequence(keyForField(FIELD_DISPLAY_TITLE)))
|
||||
.setSubtitle(bundle.getCharSequence(keyForField(FIELD_SUBTITLE)))
|
||||
.setDescription(bundle.getCharSequence(keyForField(FIELD_DESCRIPTION)))
|
||||
.setMediaUri(bundle.getParcelable(keyForField(FIELD_MEDIA_URI)))
|
||||
.setArtworkData(
|
||||
bundle.getByteArray(keyForField(FIELD_ARTWORK_DATA)),
|
||||
bundle.containsKey(keyForField(FIELD_ARTWORK_DATA_TYPE))
|
||||
|
|
|
|||
|
|
@ -397,27 +397,6 @@ public class PlaybackException extends Exception implements Bundleable {
|
|||
|
||||
// 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_LONG_TIMESTAMP_MS = 1;
|
||||
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;
|
||||
|
||||
/**
|
||||
* 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}.
|
||||
*
|
||||
* <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
|
||||
* implementing {@link #toBundle()} and {@link Bundleable.Creator}.
|
||||
* 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(@FieldNumber int field) {
|
||||
protected static String keyForField(int field) {
|
||||
return Integer.toString(field, Character.MAX_RADIX);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -33,12 +33,9 @@ import androidx.annotation.IntRange;
|
|||
import androidx.annotation.Nullable;
|
||||
import com.google.android.exoplayer2.audio.AudioAttributes;
|
||||
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.trackselection.TrackSelection;
|
||||
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.util.BundleableUtil;
|
||||
import com.google.android.exoplayer2.util.FlagSet;
|
||||
import com.google.android.exoplayer2.util.Util;
|
||||
import com.google.android.exoplayer2.video.VideoSize;
|
||||
|
|
@ -64,8 +61,8 @@ import java.util.List;
|
|||
* <ul>
|
||||
* <li>They can provide a {@link Timeline} representing the structure of the media being played,
|
||||
* which can be obtained by calling {@link #getCurrentTimeline()}.
|
||||
* <li>They can provide a {@link TracksInfo} defining the currently available tracks and which are
|
||||
* selected to be rendered, which can be obtained by calling {@link #getCurrentTracksInfo()}.
|
||||
* <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 #getCurrentTracks()}.
|
||||
* </ul>
|
||||
*/
|
||||
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}.
|
||||
*/
|
||||
@Nullable public final Object windowUid;
|
||||
/** @deprecated Use {@link #mediaItemIndex} instead. */
|
||||
/**
|
||||
* @deprecated Use {@link #mediaItemIndex} instead.
|
||||
*/
|
||||
@Deprecated public final int windowIndex;
|
||||
/** The media item index. */
|
||||
public final int mediaItemIndex;
|
||||
|
|
@ -295,7 +294,9 @@ public interface Player {
|
|||
public Bundle toBundle() {
|
||||
Bundle bundle = new Bundle();
|
||||
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.putLong(keyForField(FIELD_POSITION_MS), positionMs);
|
||||
bundle.putLong(keyForField(FIELD_CONTENT_POSITION_MS), contentPositionMs);
|
||||
|
|
@ -310,10 +311,10 @@ public interface Player {
|
|||
private static PositionInfo fromBundle(Bundle bundle) {
|
||||
int mediaItemIndex =
|
||||
bundle.getInt(keyForField(FIELD_MEDIA_ITEM_INDEX), /* defaultValue= */ C.INDEX_UNSET);
|
||||
@Nullable Bundle mediaItemBundle = bundle.getBundle(keyForField(FIELD_MEDIA_ITEM));
|
||||
@Nullable
|
||||
MediaItem mediaItem =
|
||||
BundleableUtil.fromNullableBundle(
|
||||
MediaItem.CREATOR, bundle.getBundle(keyForField(FIELD_MEDIA_ITEM)));
|
||||
mediaItemBundle == null ? null : MediaItem.CREATOR.fromBundle(mediaItemBundle);
|
||||
int periodIndex =
|
||||
bundle.getInt(keyForField(FIELD_PERIOD_INDEX), /* defaultValue= */ C.INDEX_UNSET);
|
||||
long positionMs =
|
||||
|
|
@ -381,7 +382,7 @@ public interface Player {
|
|||
COMMAND_SET_VIDEO_SURFACE,
|
||||
COMMAND_GET_TEXT,
|
||||
COMMAND_SET_TRACK_SELECTION_PARAMETERS,
|
||||
COMMAND_GET_TRACK_INFOS,
|
||||
COMMAND_GET_TRACKS,
|
||||
};
|
||||
|
||||
private final FlagSet.Builder flagsBuilder;
|
||||
|
|
@ -522,6 +523,11 @@ public interface Player {
|
|||
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. */
|
||||
public int size() {
|
||||
return flags.size();
|
||||
|
|
@ -669,40 +675,24 @@ public interface Player {
|
|||
@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
|
||||
* 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 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.
|
||||
* @param tracks The available tracks information. Never null, but may be of length zero.
|
||||
*/
|
||||
@Deprecated
|
||||
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) {}
|
||||
default void onTracksChanged(Tracks tracks) {}
|
||||
|
||||
/**
|
||||
* Called when the combined {@link MediaMetadata} changes.
|
||||
*
|
||||
* <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
|
||||
* selections' formats} and {@link Listener#onMetadata(Metadata)}. If a field is populated in
|
||||
* the {@link MediaItem#mediaMetadata}, it will be prioritised above the same field coming from
|
||||
* static or dynamic metadata.
|
||||
* <p>The provided {@link MediaMetadata} is a combination of the {@link MediaItem#mediaMetadata
|
||||
* MediaItem metadata}, the static metadata in the media's {@link Format#metadata Format}, and
|
||||
* any timed metadata that has been parsed from the media and output via {@link
|
||||
* Listener#onMetadata(Metadata)}. If a field is populated in the {@link
|
||||
* 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.
|
||||
*
|
||||
|
|
@ -731,7 +721,9 @@ public interface Player {
|
|||
*/
|
||||
default void onIsLoadingChanged(boolean isLoading) {}
|
||||
|
||||
/** @deprecated Use {@link #onIsLoadingChanged(boolean)} instead. */
|
||||
/**
|
||||
* @deprecated Use {@link #onIsLoadingChanged(boolean)} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
default void onLoadingChanged(boolean isLoading) {}
|
||||
|
||||
|
|
@ -1026,16 +1018,28 @@ public interface Player {
|
|||
/**
|
||||
* 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
|
||||
* displayed, the {@link Cue} nearer the end of the list should be shown on top.
|
||||
* <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.
|
||||
*
|
||||
* @param cues The {@link Cue Cues}. May be empty.
|
||||
* @deprecated Use {@link #onCues(CueGroup)} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
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.
|
||||
*
|
||||
|
|
@ -1309,7 +1313,7 @@ public interface Player {
|
|||
int EVENT_TIMELINE_CHANGED = 0;
|
||||
/** {@link #getCurrentMediaItem()} changed or the player started repeating the current item. */
|
||||
int EVENT_MEDIA_ITEM_TRANSITION = 1;
|
||||
/** {@link #getCurrentTracksInfo()} changed. */
|
||||
/** {@link #getCurrentTracks()} changed. */
|
||||
int EVENT_TRACKS_CHANGED = 2;
|
||||
/** {@link #isLoading()} ()} changed. */
|
||||
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_SET_DEVICE_VOLUME}, {@link #COMMAND_ADJUST_DEVICE_VOLUME}, {@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
|
||||
// with Kotlin usages from before TYPE_USE was added.
|
||||
|
|
@ -1426,7 +1430,7 @@ public interface Player {
|
|||
COMMAND_SET_VIDEO_SURFACE,
|
||||
COMMAND_GET_TEXT,
|
||||
COMMAND_SET_TRACK_SELECTION_PARAMETERS,
|
||||
COMMAND_GET_TRACK_INFOS,
|
||||
COMMAND_GET_TRACKS,
|
||||
})
|
||||
@interface Command {}
|
||||
/** Command to start, pause or resume playback. */
|
||||
|
|
@ -1439,23 +1443,31 @@ public interface Player {
|
|||
int COMMAND_SEEK_TO_DEFAULT_POSITION = 4;
|
||||
/** Command to seek anywhere into the current {@link MediaItem}. */
|
||||
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;
|
||||
/** Command to seek to the default position of the previous {@link MediaItem}. */
|
||||
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;
|
||||
/** Command to seek to an earlier position in the current or previous {@link MediaItem}. */
|
||||
int COMMAND_SEEK_TO_PREVIOUS = 7;
|
||||
/** Command to seek to the default position of the next {@link MediaItem}. */
|
||||
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;
|
||||
/** Command to seek to a later position in the current or next {@link MediaItem}. */
|
||||
int COMMAND_SEEK_TO_NEXT = 9;
|
||||
/** Command to seek anywhere in any {@link MediaItem}. */
|
||||
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;
|
||||
/** Command to seek back by a fixed increment into the current {@link MediaItem}. */
|
||||
int COMMAND_SEEK_BACK = 11;
|
||||
|
|
@ -1495,8 +1507,8 @@ public interface Player {
|
|||
int COMMAND_GET_TEXT = 28;
|
||||
/** Command to set the player's track selection parameters. */
|
||||
int COMMAND_SET_TRACK_SELECTION_PARAMETERS = 29;
|
||||
/** Command to get track infos. */
|
||||
int COMMAND_GET_TRACK_INFOS = 30;
|
||||
/** Command to get details of the current track selection. */
|
||||
int COMMAND_GET_TRACKS = 30;
|
||||
|
||||
/** Represents an invalid {@link Command}. */
|
||||
int COMMAND_INVALID = -1;
|
||||
|
|
@ -1881,11 +1893,15 @@ public interface Player {
|
|||
*/
|
||||
void seekForward();
|
||||
|
||||
/** @deprecated Use {@link #hasPreviousMediaItem()} instead. */
|
||||
/**
|
||||
* @deprecated Use {@link #hasPreviousMediaItem()} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
boolean hasPrevious();
|
||||
|
||||
/** @deprecated Use {@link #hasPreviousMediaItem()} instead. */
|
||||
/**
|
||||
* @deprecated Use {@link #hasPreviousMediaItem()} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
boolean hasPreviousWindow();
|
||||
|
||||
|
|
@ -1899,11 +1915,15 @@ public interface Player {
|
|||
*/
|
||||
boolean hasPreviousMediaItem();
|
||||
|
||||
/** @deprecated Use {@link #seekToPreviousMediaItem()} instead. */
|
||||
/**
|
||||
* @deprecated Use {@link #seekToPreviousMediaItem()} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
void previous();
|
||||
|
||||
/** @deprecated Use {@link #seekToPreviousMediaItem()} instead. */
|
||||
/**
|
||||
* @deprecated Use {@link #seekToPreviousMediaItem()} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
void seekToPreviousWindow();
|
||||
|
||||
|
|
@ -1949,11 +1969,15 @@ public interface Player {
|
|||
*/
|
||||
void seekToPrevious();
|
||||
|
||||
/** @deprecated Use {@link #hasNextMediaItem()} instead. */
|
||||
/**
|
||||
* @deprecated Use {@link #hasNextMediaItem()} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
boolean hasNext();
|
||||
|
||||
/** @deprecated Use {@link #hasNextMediaItem()} instead. */
|
||||
/**
|
||||
* @deprecated Use {@link #hasNextMediaItem()} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
boolean hasNextWindow();
|
||||
|
||||
|
|
@ -1967,11 +1991,15 @@ public interface Player {
|
|||
*/
|
||||
boolean hasNextMediaItem();
|
||||
|
||||
/** @deprecated Use {@link #seekToNextMediaItem()} instead. */
|
||||
/**
|
||||
* @deprecated Use {@link #seekToNextMediaItem()} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
void next();
|
||||
|
||||
/** @deprecated Use {@link #seekToNextMediaItem()} instead. */
|
||||
/**
|
||||
* @deprecated Use {@link #seekToNextMediaItem()} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
void seekToNextWindow();
|
||||
|
||||
|
|
@ -2060,33 +2088,11 @@ public interface Player {
|
|||
void release();
|
||||
|
||||
/**
|
||||
* Returns the available track groups.
|
||||
* Returns the current tracks.
|
||||
*
|
||||
* @see Listener#onTracksChanged(TrackGroupArray, TrackSelectionArray)
|
||||
* @deprecated Use {@link #getCurrentTracksInfo()}.
|
||||
* @see Listener#onTracksChanged(Tracks)
|
||||
*/
|
||||
@Deprecated
|
||||
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();
|
||||
Tracks getCurrentTracks();
|
||||
|
||||
/**
|
||||
* 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
|
||||
* supported.
|
||||
*
|
||||
* <p>This {@link MediaMetadata} is a combination of the {@link MediaItem#mediaMetadata} and the
|
||||
* static and dynamic metadata from the {@link TrackSelection#getFormat(int) track selections'
|
||||
* formats} and {@link Listener#onMetadata(Metadata)}. If a field is populated in the {@link
|
||||
* MediaItem#mediaMetadata}, it will be prioritised above the same field coming from static or
|
||||
* dynamic metadata.
|
||||
* <p>This {@link MediaMetadata} is a combination of the {@link MediaItem#mediaMetadata MediaItem
|
||||
* metadata}, the static metadata in the media's {@link Format#metadata Format}, and any timed
|
||||
* metadata that has been parsed from the media and output via {@link
|
||||
* Listener#onMetadata(Metadata)}. If a field is populated in the {@link MediaItem#mediaMetadata},
|
||||
* it will be prioritised above the same field coming from static or timed metadata.
|
||||
*/
|
||||
MediaMetadata getMediaMetadata();
|
||||
|
||||
|
|
@ -2151,7 +2157,9 @@ public interface Player {
|
|||
/** Returns the index of the period currently being played. */
|
||||
int getCurrentPeriodIndex();
|
||||
|
||||
/** @deprecated Use {@link #getCurrentMediaItemIndex()} instead. */
|
||||
/**
|
||||
* @deprecated Use {@link #getCurrentMediaItemIndex()} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
int getCurrentWindowIndex();
|
||||
|
||||
|
|
@ -2162,7 +2170,9 @@ public interface Player {
|
|||
*/
|
||||
int getCurrentMediaItemIndex();
|
||||
|
||||
/** @deprecated Use {@link #getNextMediaItemIndex()} instead. */
|
||||
/**
|
||||
* @deprecated Use {@link #getNextMediaItemIndex()} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
int getNextWindowIndex();
|
||||
|
||||
|
|
@ -2178,7 +2188,9 @@ public interface Player {
|
|||
*/
|
||||
int getNextMediaItemIndex();
|
||||
|
||||
/** @deprecated Use {@link #getPreviousMediaItemIndex()} instead. */
|
||||
/**
|
||||
* @deprecated Use {@link #getPreviousMediaItemIndex()} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
int getPreviousWindowIndex();
|
||||
|
||||
|
|
@ -2239,7 +2251,9 @@ public interface Player {
|
|||
*/
|
||||
long getTotalBufferedDuration();
|
||||
|
||||
/** @deprecated Use {@link #isCurrentMediaItemDynamic()} instead. */
|
||||
/**
|
||||
* @deprecated Use {@link #isCurrentMediaItemDynamic()} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
boolean isCurrentWindowDynamic();
|
||||
|
||||
|
|
@ -2251,7 +2265,9 @@ public interface Player {
|
|||
*/
|
||||
boolean isCurrentMediaItemDynamic();
|
||||
|
||||
/** @deprecated Use {@link #isCurrentMediaItemLive()} instead. */
|
||||
/**
|
||||
* @deprecated Use {@link #isCurrentMediaItemLive()} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
boolean isCurrentWindowLive();
|
||||
|
||||
|
|
@ -2276,7 +2292,9 @@ public interface Player {
|
|||
*/
|
||||
long getCurrentLiveOffset();
|
||||
|
||||
/** @deprecated Use {@link #isCurrentMediaItemSeekable()} instead. */
|
||||
/**
|
||||
* @deprecated Use {@link #isCurrentMediaItemSeekable()} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
boolean isCurrentWindowSeekable();
|
||||
|
||||
|
|
@ -2439,8 +2457,8 @@ public interface Player {
|
|||
*/
|
||||
VideoSize getVideoSize();
|
||||
|
||||
/** Returns the current {@link Cue Cues}. This list may be empty. */
|
||||
List<Cue> getCurrentCues();
|
||||
/** Returns the current {@link CueGroup}. */
|
||||
CueGroup getCurrentCues();
|
||||
|
||||
/** Gets the device information. */
|
||||
DeviceInfo getDeviceInfo();
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ import java.lang.annotation.Target;
|
|||
public abstract class Rating implements Bundleable {
|
||||
|
||||
/** 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.
|
||||
/* package */ Rating() {}
|
||||
|
|
|
|||
|
|
@ -169,7 +169,9 @@ public abstract class Timeline implements Bundleable {
|
|||
*/
|
||||
public Object uid;
|
||||
|
||||
/** @deprecated Use {@link #mediaItem} instead. */
|
||||
/**
|
||||
* @deprecated Use {@link #mediaItem} instead.
|
||||
*/
|
||||
@Deprecated @Nullable public Object tag;
|
||||
|
||||
/** 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. */
|
||||
public boolean isDynamic;
|
||||
|
||||
/** @deprecated Use {@link #isLive()} instead. */
|
||||
/**
|
||||
* @deprecated Use {@link #isLive()} instead.
|
||||
*/
|
||||
@Deprecated public boolean isLive;
|
||||
|
||||
/**
|
||||
|
|
@ -1169,14 +1173,18 @@ public abstract class Timeline implements Bundleable {
|
|||
== C.INDEX_UNSET;
|
||||
}
|
||||
|
||||
/** @deprecated Use {@link #getPeriodPositionUs(Window, Period, int, long)} instead. */
|
||||
/**
|
||||
* @deprecated Use {@link #getPeriodPositionUs(Window, Period, int, long)} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
@InlineMe(replacement = "this.getPeriodPositionUs(window, period, windowIndex, windowPositionUs)")
|
||||
public final Pair<Object, Long> getPeriodPosition(
|
||||
Window window, Period period, int windowIndex, long 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
|
||||
@Nullable
|
||||
@InlineMe(
|
||||
|
|
|
|||
|
|
@ -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.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 java.lang.annotation.ElementType.TYPE_USE;
|
||||
|
||||
|
|
@ -26,6 +24,7 @@ import android.os.Bundle;
|
|||
import androidx.annotation.IntDef;
|
||||
import androidx.annotation.Nullable;
|
||||
import com.google.android.exoplayer2.source.TrackGroup;
|
||||
import com.google.android.exoplayer2.util.BundleableUtil;
|
||||
import com.google.common.base.MoreObjects;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.primitives.Booleans;
|
||||
|
|
@ -37,48 +36,71 @@ import java.util.Arrays;
|
|||
import java.util.List;
|
||||
|
||||
/** 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
|
||||
* {@link C.TrackType type} of tracks it contains, and the level to which each track is supported
|
||||
* by the player.
|
||||
* level to which each track is supported by the player, and whether any of the tracks are
|
||||
* selected.
|
||||
*/
|
||||
public static final class TrackGroupInfo implements Bundleable {
|
||||
private final TrackGroup trackGroup;
|
||||
public static final class Group implements Bundleable {
|
||||
|
||||
/** 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.TrackType int trackType;
|
||||
private final boolean[] trackSelected;
|
||||
|
||||
/**
|
||||
* Constructs a TrackGroupInfo.
|
||||
* Constructs an instance.
|
||||
*
|
||||
* @param trackGroup The {@link TrackGroup} described.
|
||||
* @param trackSupport The {@link C.FormatSupport} of each track in the {@code trackGroup}.
|
||||
* @param trackType The {@link C.TrackType} of the tracks in the {@code trackGroup}.
|
||||
* @param tracksSelected Whether a track is selected for each track in {@code trackGroup}.
|
||||
* @param mediaTrackGroup The underlying {@link TrackGroup} defined by the media.
|
||||
* @param adaptiveSupported Whether the player supports adaptive selections containing more than
|
||||
* one track in the group.
|
||||
* @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(
|
||||
TrackGroup trackGroup,
|
||||
public Group(
|
||||
TrackGroup mediaTrackGroup,
|
||||
boolean adaptiveSupported,
|
||||
@C.FormatSupport int[] trackSupport,
|
||||
@C.TrackType int trackType,
|
||||
boolean[] tracksSelected) {
|
||||
int length = trackGroup.length;
|
||||
checkArgument(length == trackSupport.length && length == tracksSelected.length);
|
||||
this.trackGroup = trackGroup;
|
||||
boolean[] trackSelected) {
|
||||
length = mediaTrackGroup.length;
|
||||
checkArgument(length == trackSupport.length && length == trackSelected.length);
|
||||
this.mediaTrackGroup = mediaTrackGroup;
|
||||
this.adaptiveSupported = adaptiveSupported && length > 1;
|
||||
this.trackSupport = trackSupport.clone();
|
||||
this.trackType = trackType;
|
||||
this.trackSelected = tracksSelected.clone();
|
||||
this.trackSelected = trackSelected.clone();
|
||||
}
|
||||
|
||||
/** Returns the {@link TrackGroup} described by this {@code TrackGroupInfo}. */
|
||||
public TrackGroup getTrackGroup() {
|
||||
return trackGroup;
|
||||
/**
|
||||
* Returns the underlying {@link TrackGroup} defined by the media.
|
||||
*
|
||||
* <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.
|
||||
*
|
||||
* @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.
|
||||
*/
|
||||
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
|
||||
* 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.
|
||||
*/
|
||||
public boolean isTrackSupported(int trackIndex) {
|
||||
|
|
@ -99,7 +121,7 @@ public final class TracksInfo implements Bundleable {
|
|||
/**
|
||||
* 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
|
||||
* 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
|
||||
|
|
@ -118,6 +140,11 @@ public final class TracksInfo implements Bundleable {
|
|||
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
|
||||
* 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
|
||||
* 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.
|
||||
*/
|
||||
public boolean isTrackSelected(int trackIndex) {
|
||||
|
|
@ -163,8 +190,8 @@ public final class TracksInfo implements Bundleable {
|
|||
}
|
||||
|
||||
/** Returns the {@link C.TrackType} of the group. */
|
||||
public @C.TrackType int getTrackType() {
|
||||
return trackType;
|
||||
public @C.TrackType int getType() {
|
||||
return mediaTrackGroup.type;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -175,18 +202,18 @@ public final class TracksInfo implements Bundleable {
|
|||
if (other == null || getClass() != other.getClass()) {
|
||||
return false;
|
||||
}
|
||||
TrackGroupInfo that = (TrackGroupInfo) other;
|
||||
return trackType == that.trackType
|
||||
&& trackGroup.equals(that.trackGroup)
|
||||
Group that = (Group) other;
|
||||
return adaptiveSupported == that.adaptiveSupported
|
||||
&& mediaTrackGroup.equals(that.mediaTrackGroup)
|
||||
&& Arrays.equals(trackSupport, that.trackSupport)
|
||||
&& Arrays.equals(trackSelected, that.trackSelected);
|
||||
}
|
||||
|
||||
@Override
|
||||
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 + trackType;
|
||||
result = 31 * result + Arrays.hashCode(trackSelected);
|
||||
return result;
|
||||
}
|
||||
|
|
@ -198,43 +225,43 @@ public final class TracksInfo implements Bundleable {
|
|||
@IntDef({
|
||||
FIELD_TRACK_GROUP,
|
||||
FIELD_TRACK_SUPPORT,
|
||||
FIELD_TRACK_TYPE,
|
||||
FIELD_TRACK_SELECTED,
|
||||
FIELD_ADAPTIVE_SUPPORTED,
|
||||
})
|
||||
private @interface FieldNumber {}
|
||||
|
||||
private static final int FIELD_TRACK_GROUP = 0;
|
||||
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_ADAPTIVE_SUPPORTED = 4;
|
||||
|
||||
@Override
|
||||
public Bundle toBundle() {
|
||||
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.putInt(keyForField(FIELD_TRACK_TYPE), trackType);
|
||||
bundle.putBooleanArray(keyForField(FIELD_TRACK_SELECTED), trackSelected);
|
||||
bundle.putBoolean(keyForField(FIELD_ADAPTIVE_SUPPORTED), adaptiveSupported);
|
||||
return bundle;
|
||||
}
|
||||
|
||||
/** Object that can restores a {@code TracksInfo} from a {@link Bundle}. */
|
||||
public static final Creator<TrackGroupInfo> CREATOR =
|
||||
/** Object that can restore a group of tracks from a {@link Bundle}. */
|
||||
public static final Creator<Group> CREATOR =
|
||||
bundle -> {
|
||||
// Can't create a Tracks.Group without a TrackGroup
|
||||
TrackGroup trackGroup =
|
||||
fromNullableBundle(
|
||||
TrackGroup.CREATOR, bundle.getBundle(keyForField(FIELD_TRACK_GROUP)));
|
||||
checkNotNull(trackGroup); // Can't create a trackGroup info without a trackGroup
|
||||
TrackGroup.CREATOR.fromBundle(
|
||||
checkNotNull(bundle.getBundle(keyForField(FIELD_TRACK_GROUP))));
|
||||
final @C.FormatSupport int[] trackSupport =
|
||||
MoreObjects.firstNonNull(
|
||||
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 =
|
||||
MoreObjects.firstNonNull(
|
||||
bundle.getBooleanArray(keyForField(FIELD_TRACK_SELECTED)),
|
||||
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) {
|
||||
|
|
@ -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. */
|
||||
public static final TracksInfo EMPTY = new TracksInfo(ImmutableList.of());
|
||||
private final ImmutableList<Group> groups;
|
||||
|
||||
/**
|
||||
* Constructs an instance.
|
||||
*
|
||||
* @param trackGroupInfos The {@link TrackGroupInfo TrackGroupInfos} describing the groups of
|
||||
* tracks.
|
||||
* @param groups The {@link Group groups} of tracks.
|
||||
*/
|
||||
public TracksInfo(List<TrackGroupInfo> trackGroupInfos) {
|
||||
this.trackGroupInfos = ImmutableList.copyOf(trackGroupInfos);
|
||||
public Tracks(List<Group> groups) {
|
||||
this.groups = ImmutableList.copyOf(groups);
|
||||
}
|
||||
|
||||
/** Returns the {@link TrackGroupInfo TrackGroupInfos} describing the groups of tracks. */
|
||||
public ImmutableList<TrackGroupInfo> getTrackGroupInfos() {
|
||||
return trackGroupInfos;
|
||||
/** Returns the {@link Group groups} of tracks. */
|
||||
public ImmutableList<Group> getGroups() {
|
||||
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
|
||||
* TrackGroupInfo#isTrackSupported(int) supported} or if there are no tracks of this type.
|
||||
* Group#isTrackSupported(int) supported}.
|
||||
*/
|
||||
public boolean isTypeSupportedOrEmpty(@C.TrackType int trackType) {
|
||||
return isTypeSupportedOrEmpty(trackType, /* allowExceedsCapabilities= */ false);
|
||||
public boolean isTypeSupported(@C.TrackType int trackType) {
|
||||
return isTypeSupported(trackType, /* allowExceedsCapabilities= */ false);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
* type.
|
||||
* Group#isTrackSupported(int, boolean) supported}.
|
||||
*
|
||||
* @param allowExceedsCapabilities Whether to consider the track as supported if it has a
|
||||
* 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.
|
||||
* Such tracks may be playable in some cases.
|
||||
*/
|
||||
public boolean isTypeSupportedOrEmpty(
|
||||
@C.TrackType int trackType, boolean allowExceedsCapabilities) {
|
||||
boolean supported = true;
|
||||
for (int i = 0; i < trackGroupInfos.size(); i++) {
|
||||
if (trackGroupInfos.get(i).trackType == trackType) {
|
||||
if (trackGroupInfos.get(i).isSupported(allowExceedsCapabilities)) {
|
||||
public boolean isTypeSupported(@C.TrackType int trackType, boolean allowExceedsCapabilities) {
|
||||
for (int i = 0; i < groups.size(); i++) {
|
||||
if (groups.get(i).getType() == trackType) {
|
||||
if (groups.get(i).isSupported(allowExceedsCapabilities)) {
|
||||
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. */
|
||||
public boolean isTypeSelected(@C.TrackType int trackType) {
|
||||
for (int i = 0; i < trackGroupInfos.size(); i++) {
|
||||
TrackGroupInfo trackGroupInfo = trackGroupInfos.get(i);
|
||||
if (trackGroupInfo.isSelected() && trackGroupInfo.getTrackType() == trackType) {
|
||||
for (int i = 0; i < groups.size(); i++) {
|
||||
Group group = groups.get(i);
|
||||
if (group.isSelected() && group.getType() == trackType) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
@ -315,13 +369,13 @@ public final class TracksInfo implements Bundleable {
|
|||
if (other == null || getClass() != other.getClass()) {
|
||||
return false;
|
||||
}
|
||||
TracksInfo that = (TracksInfo) other;
|
||||
return trackGroupInfos.equals(that.trackGroupInfos);
|
||||
Tracks that = (Tracks) other;
|
||||
return groups.equals(that.groups);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return trackGroupInfos.hashCode();
|
||||
return groups.hashCode();
|
||||
}
|
||||
// Bundleable implementation.
|
||||
|
||||
|
|
@ -329,29 +383,29 @@ public final class TracksInfo implements Bundleable {
|
|||
@Retention(RetentionPolicy.SOURCE)
|
||||
@Target(TYPE_USE)
|
||||
@IntDef({
|
||||
FIELD_TRACK_GROUP_INFOS,
|
||||
FIELD_TRACK_GROUPS,
|
||||
})
|
||||
private @interface FieldNumber {}
|
||||
|
||||
private static final int FIELD_TRACK_GROUP_INFOS = 0;
|
||||
private static final int FIELD_TRACK_GROUPS = 0;
|
||||
|
||||
@Override
|
||||
public Bundle toBundle() {
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putParcelableArrayList(
|
||||
keyForField(FIELD_TRACK_GROUP_INFOS), toBundleArrayList(trackGroupInfos));
|
||||
bundle.putParcelableArrayList(keyForField(FIELD_TRACK_GROUPS), toBundleArrayList(groups));
|
||||
return bundle;
|
||||
}
|
||||
|
||||
/** Object that can restore a {@code TracksInfo} from a {@link Bundle}. */
|
||||
public static final Creator<TracksInfo> CREATOR =
|
||||
/** Object that can restore tracks from a {@link Bundle}. */
|
||||
public static final Creator<Tracks> CREATOR =
|
||||
bundle -> {
|
||||
List<TrackGroupInfo> trackGroupInfos =
|
||||
fromBundleNullableList(
|
||||
TrackGroupInfo.CREATOR,
|
||||
bundle.getParcelableArrayList(keyForField(FIELD_TRACK_GROUP_INFOS)),
|
||||
/* defaultValue= */ ImmutableList.of());
|
||||
return new TracksInfo(trackGroupInfos);
|
||||
@Nullable
|
||||
List<Bundle> groupBundles = bundle.getParcelableArrayList(keyForField(FIELD_TRACK_GROUPS));
|
||||
List<Group> groups =
|
||||
groupBundles == null
|
||||
? ImmutableList.of()
|
||||
: BundleableUtil.fromBundleList(Group.CREATOR, groupBundles);
|
||||
return new Tracks(groups);
|
||||
};
|
||||
|
||||
private static String keyForField(@FieldNumber int field) {
|
||||
|
|
@ -29,7 +29,6 @@ import java.lang.annotation.Documented;
|
|||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
|
||||
/** 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
|
||||
* is {@link C#USAGE_MEDIA}, capture policy is {@link C#ALLOW_CAPTURE_BY_ALL} and no flags are
|
||||
* set.
|
||||
* The default audio attributes, where 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.
|
||||
*/
|
||||
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}.
|
||||
*
|
||||
* <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.
|
||||
*/
|
||||
public Builder() {
|
||||
contentType = C.CONTENT_TYPE_UNKNOWN;
|
||||
contentType = C.AUDIO_CONTENT_TYPE_UNKNOWN;
|
||||
flags = 0;
|
||||
usage = C.USAGE_MEDIA;
|
||||
allowedCapturePolicy = C.ALLOW_CAPTURE_BY_ALL;
|
||||
|
|
@ -98,9 +118,7 @@ public final class AudioAttributes implements Bundleable {
|
|||
return this;
|
||||
}
|
||||
|
||||
// TODO[b/190759307] Update javadoc to link to AudioAttributes.Builder#setSpatializationBehavior
|
||||
// once compile SDK target is set to 32.
|
||||
/** See {@code android.media.AudioAttributes.Builder.setSpatializationBehavior(int)}. */
|
||||
/** See {@link android.media.AudioAttributes.Builder#setSpatializationBehavior(int)}. */
|
||||
public Builder setSpatializationBehavior(@C.SpatializationBehavior int spatializationBehavior) {
|
||||
this.spatializationBehavior = spatializationBehavior;
|
||||
return this;
|
||||
|
|
@ -124,7 +142,7 @@ public final class AudioAttributes implements Bundleable {
|
|||
/** The {@link C.SpatializationBehavior}. */
|
||||
public final @C.SpatializationBehavior int spatializationBehavior;
|
||||
|
||||
@Nullable private android.media.AudioAttributes audioAttributesV21;
|
||||
@Nullable private AudioAttributesV21 audioAttributesV21;
|
||||
|
||||
private AudioAttributes(
|
||||
@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)
|
||||
public android.media.AudioAttributes getAudioAttributesV21() {
|
||||
public AudioAttributesV21 getAudioAttributesV21() {
|
||||
if (audioAttributesV21 == null) {
|
||||
android.media.AudioAttributes.Builder builder =
|
||||
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();
|
||||
audioAttributesV21 = new AudioAttributesV21(this);
|
||||
}
|
||||
return audioAttributesV21;
|
||||
}
|
||||
|
|
@ -250,8 +258,6 @@ public final class AudioAttributes implements Bundleable {
|
|||
|
||||
@RequiresApi(29)
|
||||
private static final class Api29 {
|
||||
private Api29() {}
|
||||
|
||||
@DoNotInline
|
||||
public static void setAllowedCapturePolicy(
|
||||
android.media.AudioAttributes.Builder builder,
|
||||
|
|
@ -262,20 +268,11 @@ public final class AudioAttributes implements Bundleable {
|
|||
|
||||
@RequiresApi(32)
|
||||
private static final class Api32 {
|
||||
private Api32() {}
|
||||
|
||||
@DoNotInline
|
||||
public static void setSpatializationBehavior(
|
||||
android.media.AudioAttributes.Builder builder,
|
||||
@C.SpatializationBehavior int spatializationBehavior) {
|
||||
try {
|
||||
// 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.
|
||||
}
|
||||
builder.setSpatializationBehavior(spatializationBehavior);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -91,7 +91,9 @@ public final class DrmInitData implements Comparator<SchemeData>, Parcelable {
|
|||
/** Number of {@link SchemeData}s. */
|
||||
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) {
|
||||
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]));
|
||||
}
|
||||
|
||||
/** @param schemeDatas Scheme initialization data for possibly multiple DRM schemes. */
|
||||
/**
|
||||
* @param schemeDatas Scheme initialization data for possibly multiple DRM schemes.
|
||||
*/
|
||||
public DrmInitData(SchemeData... schemeDatas) {
|
||||
this(null, schemeDatas);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -62,12 +62,16 @@ public final class Metadata implements Parcelable {
|
|||
|
||||
private final Entry[] entries;
|
||||
|
||||
/** @param entries The metadata entries. */
|
||||
/**
|
||||
* @param entries The metadata entries.
|
||||
*/
|
||||
public Metadata(Entry... entries) {
|
||||
this.entries = entries;
|
||||
}
|
||||
|
||||
/** @param entries The metadata entries. */
|
||||
/**
|
||||
* @param entries The metadata entries.
|
||||
*/
|
||||
public Metadata(List<? extends Entry> entries) {
|
||||
this.entries = entries.toArray(new Entry[0]);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -42,7 +42,9 @@ public final class StreamKey implements Comparable<StreamKey>, Parcelable {
|
|||
/** The stream index. */
|
||||
public final int streamIndex;
|
||||
|
||||
/** @deprecated Use {@link #streamIndex}. */
|
||||
/**
|
||||
* @deprecated Use {@link #streamIndex}.
|
||||
*/
|
||||
@Deprecated public final int trackIndex;
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -25,8 +25,10 @@ import androidx.annotation.Nullable;
|
|||
import com.google.android.exoplayer2.Bundleable;
|
||||
import com.google.android.exoplayer2.C;
|
||||
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.Log;
|
||||
import com.google.android.exoplayer2.util.MimeTypes;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.Lists;
|
||||
import java.lang.annotation.Documented;
|
||||
|
|
@ -36,7 +38,25 @@ import java.lang.annotation.Target;
|
|||
import java.util.Arrays;
|
||||
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 {
|
||||
|
||||
private static final String TAG = "TrackGroup";
|
||||
|
|
@ -45,6 +65,8 @@ public final class TrackGroup implements Bundleable {
|
|||
public final int length;
|
||||
/** An identifier for the track group. */
|
||||
public final String id;
|
||||
/** The type of tracks in the group. */
|
||||
public final @C.TrackType int type;
|
||||
|
||||
private final Format[] formats;
|
||||
|
||||
|
|
@ -71,6 +93,11 @@ public final class TrackGroup implements Bundleable {
|
|||
this.id = id;
|
||||
this.formats = formats;
|
||||
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();
|
||||
}
|
||||
|
||||
|
|
@ -133,7 +160,7 @@ public final class TrackGroup implements Bundleable {
|
|||
return false;
|
||||
}
|
||||
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.
|
||||
|
|
@ -159,11 +186,12 @@ public final class TrackGroup implements Bundleable {
|
|||
/** Object that can restore {@code TrackGroup} from a {@link Bundle}. */
|
||||
public static final Creator<TrackGroup> CREATOR =
|
||||
bundle -> {
|
||||
@Nullable
|
||||
List<Bundle> formatBundles = bundle.getParcelableArrayList(keyForField(FIELD_FORMATS));
|
||||
List<Format> formats =
|
||||
BundleableUtil.fromBundleNullableList(
|
||||
Format.CREATOR,
|
||||
bundle.getParcelableArrayList(keyForField(FIELD_FORMATS)),
|
||||
ImmutableList.of());
|
||||
formatBundles == null
|
||||
? ImmutableList.of()
|
||||
: BundleableUtil.fromBundleList(Format.CREATOR, formatBundles);
|
||||
String id = bundle.getString(keyForField(FIELD_ID), /* defaultValue= */ "");
|
||||
return new TrackGroup(id, formats.toArray(new Format[0]));
|
||||
};
|
||||
|
|
|
|||
|
|
@ -828,6 +828,36 @@ public final class AdPlaybackState implements Bundleable {
|
|||
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
|
||||
public boolean equals(@Nullable Object o) {
|
||||
if (this == o) {
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -16,38 +16,41 @@
|
|||
package com.google.android.exoplayer2.trackselection;
|
||||
|
||||
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 java.lang.annotation.ElementType.TYPE_USE;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Point;
|
||||
import android.os.Bundle;
|
||||
import android.os.Looper;
|
||||
import android.view.accessibility.CaptioningManager;
|
||||
import androidx.annotation.IntDef;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.RequiresApi;
|
||||
import com.google.android.exoplayer2.Bundleable;
|
||||
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.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
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.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Set;
|
||||
import org.checkerframework.checker.initialization.qual.UnknownInitialization;
|
||||
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
|
||||
* SD, and to select a German audio track if there is one:
|
||||
* <p>Parameters can be queried and set on a {@link Player}. For example the following code modifies
|
||||
* the parameters to restrict video track selections to SD, and to select a German audio track if
|
||||
* there is one:
|
||||
*
|
||||
* <pre>{@code
|
||||
* // Build on the current parameters.
|
||||
|
|
@ -92,12 +95,13 @@ public class TrackSelectionParameters implements Bundleable {
|
|||
// Text
|
||||
private ImmutableList<String> preferredTextLanguages;
|
||||
private @C.RoleFlags int preferredTextRoleFlags;
|
||||
private @C.SelectionFlags int ignoredTextSelectionFlags;
|
||||
private boolean selectUndeterminedTextLanguage;
|
||||
// General
|
||||
private boolean forceLowestBitrate;
|
||||
private boolean forceHighestSupportedBitrate;
|
||||
private TrackSelectionOverrides trackSelectionOverrides;
|
||||
private ImmutableSet<@C.TrackType Integer> disabledTrackTypes;
|
||||
private HashMap<TrackGroup, TrackSelectionOverride> overrides;
|
||||
private HashSet<@C.TrackType Integer> disabledTrackTypes;
|
||||
|
||||
/**
|
||||
* @deprecated {@link Context} constraints will not be set using this constructor. Use {@link
|
||||
|
|
@ -124,12 +128,13 @@ public class TrackSelectionParameters implements Bundleable {
|
|||
// Text
|
||||
preferredTextLanguages = ImmutableList.of();
|
||||
preferredTextRoleFlags = 0;
|
||||
ignoredTextSelectionFlags = 0;
|
||||
selectUndeterminedTextLanguage = false;
|
||||
// General
|
||||
forceLowestBitrate = false;
|
||||
forceHighestSupportedBitrate = false;
|
||||
trackSelectionOverrides = TrackSelectionOverrides.EMPTY;
|
||||
disabledTrackTypes = ImmutableSet.of();
|
||||
overrides = new HashMap<>();
|
||||
disabledTrackTypes = new HashSet<>();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -222,6 +227,10 @@ public class TrackSelectionParameters implements Bundleable {
|
|||
bundle.getInt(
|
||||
keyForField(FIELD_PREFERRED_TEXT_ROLE_FLAGS),
|
||||
DEFAULT_WITHOUT_CONTEXT.preferredTextRoleFlags);
|
||||
ignoredTextSelectionFlags =
|
||||
bundle.getInt(
|
||||
keyForField(FIELD_IGNORED_TEXT_SELECTION_FLAGS),
|
||||
DEFAULT_WITHOUT_CONTEXT.ignoredTextSelectionFlags);
|
||||
selectUndeterminedTextLanguage =
|
||||
bundle.getBoolean(
|
||||
keyForField(FIELD_SELECT_UNDETERMINED_TEXT_LANGUAGE),
|
||||
|
|
@ -234,16 +243,24 @@ public class TrackSelectionParameters implements Bundleable {
|
|||
bundle.getBoolean(
|
||||
keyForField(FIELD_FORCE_HIGHEST_SUPPORTED_BITRATE),
|
||||
DEFAULT_WITHOUT_CONTEXT.forceHighestSupportedBitrate);
|
||||
trackSelectionOverrides =
|
||||
fromNullableBundle(
|
||||
TrackSelectionOverrides.CREATOR,
|
||||
bundle.getBundle(keyForField(FIELD_SELECTION_OVERRIDE_KEYS)),
|
||||
TrackSelectionOverrides.EMPTY);
|
||||
disabledTrackTypes =
|
||||
ImmutableSet.copyOf(
|
||||
Ints.asList(
|
||||
firstNonNull(
|
||||
bundle.getIntArray(keyForField(FIELD_DISABLED_TRACK_TYPE)), new int[0])));
|
||||
@Nullable
|
||||
List<Bundle> overrideBundleList =
|
||||
bundle.getParcelableArrayList(keyForField(FIELD_SELECTION_OVERRIDES));
|
||||
List<TrackSelectionOverride> overrideList =
|
||||
overrideBundleList == null
|
||||
? ImmutableList.of()
|
||||
: BundleableUtil.fromBundleList(TrackSelectionOverride.CREATOR, overrideBundleList);
|
||||
overrides = new HashMap<>();
|
||||
for (int i = 0; i < overrideList.size(); i++) {
|
||||
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}. */
|
||||
|
|
@ -252,7 +269,7 @@ public class TrackSelectionParameters implements Bundleable {
|
|||
"preferredAudioLanguages",
|
||||
"preferredAudioMimeTypes",
|
||||
"preferredTextLanguages",
|
||||
"trackSelectionOverrides",
|
||||
"overrides",
|
||||
"disabledTrackTypes",
|
||||
})
|
||||
private void init(@UnknownInitialization Builder this, TrackSelectionParameters parameters) {
|
||||
|
|
@ -279,12 +296,13 @@ public class TrackSelectionParameters implements Bundleable {
|
|||
// Text
|
||||
preferredTextLanguages = parameters.preferredTextLanguages;
|
||||
preferredTextRoleFlags = parameters.preferredTextRoleFlags;
|
||||
ignoredTextSelectionFlags = parameters.ignoredTextSelectionFlags;
|
||||
selectUndeterminedTextLanguage = parameters.selectUndeterminedTextLanguage;
|
||||
// General
|
||||
forceLowestBitrate = parameters.forceLowestBitrate;
|
||||
forceHighestSupportedBitrate = parameters.forceHighestSupportedBitrate;
|
||||
trackSelectionOverrides = parameters.trackSelectionOverrides;
|
||||
disabledTrackTypes = parameters.disabledTrackTypes;
|
||||
disabledTrackTypes = new HashSet<>(parameters.disabledTrackTypes);
|
||||
overrides = new HashMap<>(parameters.overrides);
|
||||
}
|
||||
|
||||
/** Overrides the value of the builder with the value of {@link TrackSelectionParameters}. */
|
||||
|
|
@ -601,6 +619,18 @@ public class TrackSelectionParameters implements Bundleable {
|
|||
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
|
||||
* {@link #setPreferredTextLanguages(String...) a preferred language} is available, or if the
|
||||
|
|
@ -643,26 +673,72 @@ public class TrackSelectionParameters implements Bundleable {
|
|||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the selection overrides.
|
||||
*
|
||||
* @param trackSelectionOverrides The track selection overrides.
|
||||
* @return This builder.
|
||||
*/
|
||||
public Builder setTrackSelectionOverrides(TrackSelectionOverrides trackSelectionOverrides) {
|
||||
this.trackSelectionOverrides = trackSelectionOverrides;
|
||||
/** Adds an override, replacing any override for the same {@link TrackGroup}. */
|
||||
public Builder addOverride(TrackSelectionOverride override) {
|
||||
overrides.put(override.mediaTrackGroup, override);
|
||||
return this;
|
||||
}
|
||||
|
||||
/** Sets an override, replacing all existing overrides with the same track type. */
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* @return This builder.
|
||||
* @deprecated Use {@link #setTrackTypeDisabled(int, boolean)}.
|
||||
*/
|
||||
@Deprecated
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
@ -837,6 +913,11 @@ public class TrackSelectionParameters implements Bundleable {
|
|||
* is enabled.
|
||||
*/
|
||||
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
|
||||
* #preferredTextLanguages} is available, or if {@link #preferredTextLanguages} is unset. The
|
||||
|
|
@ -855,8 +936,9 @@ public class TrackSelectionParameters implements Bundleable {
|
|||
*/
|
||||
public final boolean forceHighestSupportedBitrate;
|
||||
|
||||
/** Overrides to force tracks to be selected. */
|
||||
public final TrackSelectionOverrides trackSelectionOverrides;
|
||||
/** Overrides to force selection of specific tracks. */
|
||||
public final ImmutableMap<TrackGroup, TrackSelectionOverride> overrides;
|
||||
|
||||
/**
|
||||
* 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
|
||||
|
|
@ -888,12 +970,13 @@ public class TrackSelectionParameters implements Bundleable {
|
|||
// Text
|
||||
this.preferredTextLanguages = builder.preferredTextLanguages;
|
||||
this.preferredTextRoleFlags = builder.preferredTextRoleFlags;
|
||||
this.ignoredTextSelectionFlags = builder.ignoredTextSelectionFlags;
|
||||
this.selectUndeterminedTextLanguage = builder.selectUndeterminedTextLanguage;
|
||||
// General
|
||||
this.forceLowestBitrate = builder.forceLowestBitrate;
|
||||
this.forceHighestSupportedBitrate = builder.forceHighestSupportedBitrate;
|
||||
this.trackSelectionOverrides = builder.trackSelectionOverrides;
|
||||
this.disabledTrackTypes = builder.disabledTrackTypes;
|
||||
this.overrides = ImmutableMap.copyOf(builder.overrides);
|
||||
this.disabledTrackTypes = ImmutableSet.copyOf(builder.disabledTrackTypes);
|
||||
}
|
||||
|
||||
/** Creates a new {@link Builder}, copying the initial values from this instance. */
|
||||
|
|
@ -931,13 +1014,15 @@ public class TrackSelectionParameters implements Bundleable {
|
|||
&& maxAudioChannelCount == other.maxAudioChannelCount
|
||||
&& maxAudioBitrate == other.maxAudioBitrate
|
||||
&& preferredAudioMimeTypes.equals(other.preferredAudioMimeTypes)
|
||||
// Text
|
||||
&& preferredTextLanguages.equals(other.preferredTextLanguages)
|
||||
&& preferredTextRoleFlags == other.preferredTextRoleFlags
|
||||
&& ignoredTextSelectionFlags == other.ignoredTextSelectionFlags
|
||||
&& selectUndeterminedTextLanguage == other.selectUndeterminedTextLanguage
|
||||
// General
|
||||
&& forceLowestBitrate == other.forceLowestBitrate
|
||||
&& forceHighestSupportedBitrate == other.forceHighestSupportedBitrate
|
||||
&& trackSelectionOverrides.equals(other.trackSelectionOverrides)
|
||||
&& overrides.equals(other.overrides)
|
||||
&& disabledTrackTypes.equals(other.disabledTrackTypes);
|
||||
}
|
||||
|
||||
|
|
@ -967,50 +1052,18 @@ public class TrackSelectionParameters implements Bundleable {
|
|||
// Text
|
||||
result = 31 * result + preferredTextLanguages.hashCode();
|
||||
result = 31 * result + preferredTextRoleFlags;
|
||||
result = 31 * result + ignoredTextSelectionFlags;
|
||||
result = 31 * result + (selectUndeterminedTextLanguage ? 1 : 0);
|
||||
// General
|
||||
result = 31 * result + (forceLowestBitrate ? 1 : 0);
|
||||
result = 31 * result + (forceHighestSupportedBitrate ? 1 : 0);
|
||||
result = 31 * result + trackSelectionOverrides.hashCode();
|
||||
result = 31 * result + overrides.hashCode();
|
||||
result = 31 * result + disabledTrackTypes.hashCode();
|
||||
return result;
|
||||
}
|
||||
|
||||
// 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_ROLE_FLAGS = 2;
|
||||
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_FORCE_LOWEST_BITRATE = 21;
|
||||
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_OVERRIDE_VALUES = 24;
|
||||
private static final int FIELD_DISABLED_TRACK_TYPE = 25;
|
||||
private static final int FIELD_PREFERRED_VIDEO_ROLE_FLAGS = 26;
|
||||
private static final int FIELD_SELECTION_OVERRIDES = 23;
|
||||
private static final int FIELD_DISABLED_TRACK_TYPE = 24;
|
||||
private static final int FIELD_PREFERRED_VIDEO_ROLE_FLAGS = 25;
|
||||
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
|
||||
public Bundle toBundle() {
|
||||
|
|
@ -1073,24 +1135,40 @@ public class TrackSelectionParameters implements Bundleable {
|
|||
bundle.putStringArray(
|
||||
keyForField(FIELD_PREFERRED_TEXT_LANGUAGES), preferredTextLanguages.toArray(new String[0]));
|
||||
bundle.putInt(keyForField(FIELD_PREFERRED_TEXT_ROLE_FLAGS), preferredTextRoleFlags);
|
||||
bundle.putInt(keyForField(FIELD_IGNORED_TEXT_SELECTION_FLAGS), ignoredTextSelectionFlags);
|
||||
bundle.putBoolean(
|
||||
keyForField(FIELD_SELECT_UNDETERMINED_TEXT_LANGUAGE), selectUndeterminedTextLanguage);
|
||||
// General
|
||||
bundle.putBoolean(keyForField(FIELD_FORCE_LOWEST_BITRATE), forceLowestBitrate);
|
||||
bundle.putBoolean(
|
||||
keyForField(FIELD_FORCE_HIGHEST_SUPPORTED_BITRATE), forceHighestSupportedBitrate);
|
||||
bundle.putBundle(
|
||||
keyForField(FIELD_SELECTION_OVERRIDE_KEYS), trackSelectionOverrides.toBundle());
|
||||
bundle.putParcelableArrayList(
|
||||
keyForField(FIELD_SELECTION_OVERRIDES), toBundleArrayList(overrides.values()));
|
||||
bundle.putIntArray(keyForField(FIELD_DISABLED_TRACK_TYPE), Ints.toArray(disabledTrackTypes));
|
||||
|
||||
return bundle;
|
||||
}
|
||||
|
||||
/** Object that can restore {@code TrackSelectionParameters} from a {@link Bundle}. */
|
||||
public static final Creator<TrackSelectionParameters> CREATOR =
|
||||
bundle -> new Builder(bundle).build();
|
||||
/** Construct an instance from a {@link Bundle} produced by {@link #toBundle()}. */
|
||||
public static TrackSelectionParameters fromBundle(Bundle bundle) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -96,13 +96,17 @@ public final class AdOverlayInfo {
|
|||
/** An optional, detailed reason that the overlay view is needed. */
|
||||
@Nullable public final String reasonDetail;
|
||||
|
||||
/** @deprecated Use {@link Builder} instead. */
|
||||
/**
|
||||
* @deprecated Use {@link Builder} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public AdOverlayInfo(View view, @Purpose int purpose) {
|
||||
this(view, purpose, /* detailedReason= */ null);
|
||||
}
|
||||
|
||||
/** @deprecated Use {@link Builder} instead. */
|
||||
/**
|
||||
* @deprecated Use {@link Builder} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public AdOverlayInfo(View view, @Purpose int purpose, @Nullable String detailedReason) {
|
||||
this.view = view;
|
||||
|
|
|
|||
|
|
@ -30,34 +30,6 @@ import java.util.List;
|
|||
/** Utilities for {@link Bundleable}. */
|
||||
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}. */
|
||||
public static <T extends Bundleable> ImmutableList<Bundle> toBundleList(List<T> bundleableList) {
|
||||
ImmutableList.Builder<Bundle> builder = ImmutableList.builder();
|
||||
|
|
@ -80,34 +52,6 @@ public final class BundleableUtil {
|
|||
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
|
||||
* the returned list can be put to {@link Bundle} using {@link Bundle#putParcelableArrayList}
|
||||
|
|
@ -122,6 +66,19 @@ public final class BundleableUtil {
|
|||
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
|
||||
* Bundle} so that the returned {@link SparseArray} can be put to {@link Bundle} using {@link
|
||||
|
|
|
|||
|
|
@ -35,10 +35,14 @@ public interface Clock {
|
|||
*/
|
||||
long currentTimeMillis();
|
||||
|
||||
/** @see android.os.SystemClock#elapsedRealtime() */
|
||||
/**
|
||||
* @see android.os.SystemClock#elapsedRealtime()
|
||||
*/
|
||||
long elapsedRealtime();
|
||||
|
||||
/** @see android.os.SystemClock#uptimeMillis() */
|
||||
/**
|
||||
* @see android.os.SystemClock#uptimeMillis()
|
||||
*/
|
||||
long uptimeMillis();
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -15,6 +15,8 @@
|
|||
*/
|
||||
package com.google.android.exoplayer2.util;
|
||||
|
||||
import static com.google.android.exoplayer2.util.Assertions.checkArgument;
|
||||
|
||||
import android.util.Pair;
|
||||
import androidx.annotation.Nullable;
|
||||
import com.google.android.exoplayer2.C;
|
||||
|
|
@ -29,6 +31,12 @@ public final class CodecSpecificDataUtil {
|
|||
private static final String[] HEVC_GENERAL_PROFILE_SPACE_STRINGS =
|
||||
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
|
||||
* 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue