Commit fc3c57ec by Rakesh Kumar Committed by GitHub

Merge branch 'main' into rtp-h263

parents dfc424db a105d033
Showing with 1772 additions and 869 deletions
...@@ -17,7 +17,7 @@ buildscript { ...@@ -17,7 +17,7 @@ buildscript {
mavenCentral() mavenCentral()
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:7.0.3' classpath 'com.android.tools.build:gradle:7.2.1'
classpath 'com.google.android.gms:strict-version-matcher-plugin:1.2.2' classpath 'com.google.android.gms:strict-version-matcher-plugin:1.2.2'
classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.5.21' classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.5.21'
} }
......
...@@ -29,5 +29,10 @@ android { ...@@ -29,5 +29,10 @@ android {
targetCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8
} }
testOptions.unitTests.includeAndroidResources = true testOptions {
unitTests.all {
jvmArgs "-Xmx2g"
}
unitTests.includeAndroidResources true
}
} }
...@@ -26,7 +26,7 @@ project.ext { ...@@ -26,7 +26,7 @@ project.ext {
// https://cs.android.com/android/platform/superproject/+/master:external/guava/METADATA // https://cs.android.com/android/platform/superproject/+/master:external/guava/METADATA
guavaVersion = '31.0.1-android' guavaVersion = '31.0.1-android'
mockitoVersion = '3.12.4' mockitoVersion = '3.12.4'
robolectricVersion = '4.8-alpha-1' robolectricVersion = '4.8.1'
// Keep this in sync with Google's internal Checker Framework version. // Keep this in sync with Google's internal Checker Framework version.
checkerframeworkVersion = '3.13.0' checkerframeworkVersion = '3.13.0'
checkerframeworkCompatVersion = '2.5.5' checkerframeworkCompatVersion = '2.5.5'
......
...@@ -230,8 +230,8 @@ public class MainActivity extends AppCompatActivity ...@@ -230,8 +230,8 @@ public class MainActivity extends AppCompatActivity
@Override @Override
public boolean onMove( public boolean onMove(
RecyclerView list, RecyclerView.ViewHolder origin, RecyclerView.ViewHolder target) { RecyclerView list, RecyclerView.ViewHolder origin, RecyclerView.ViewHolder target) {
int fromPosition = origin.getAdapterPosition(); int fromPosition = origin.getBindingAdapterPosition();
int toPosition = target.getAdapterPosition(); int toPosition = target.getBindingAdapterPosition();
if (draggingFromPosition == C.INDEX_UNSET) { if (draggingFromPosition == C.INDEX_UNSET) {
// A drag has started, but changes to the media queue will be reflected in clearView(). // A drag has started, but changes to the media queue will be reflected in clearView().
draggingFromPosition = fromPosition; draggingFromPosition = fromPosition;
...@@ -243,7 +243,7 @@ public class MainActivity extends AppCompatActivity ...@@ -243,7 +243,7 @@ public class MainActivity extends AppCompatActivity
@Override @Override
public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) { public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
int position = viewHolder.getAdapterPosition(); int position = viewHolder.getBindingAdapterPosition();
QueueItemViewHolder queueItemHolder = (QueueItemViewHolder) viewHolder; QueueItemViewHolder queueItemHolder = (QueueItemViewHolder) viewHolder;
if (playerManager.removeItem(queueItemHolder.item)) { if (playerManager.removeItem(queueItemHolder.item)) {
mediaQueueListAdapter.notifyItemRemoved(position); mediaQueueListAdapter.notifyItemRemoved(position);
...@@ -282,7 +282,7 @@ public class MainActivity extends AppCompatActivity ...@@ -282,7 +282,7 @@ public class MainActivity extends AppCompatActivity
@Override @Override
public void onClick(View v) { public void onClick(View v) {
playerManager.selectQueueItem(getAdapterPosition()); playerManager.selectQueueItem(getBindingAdapterPosition());
} }
} }
......
...@@ -29,6 +29,7 @@ import android.opengl.GLUtils; ...@@ -29,6 +29,7 @@ import android.opengl.GLUtils;
import androidx.media3.common.C; import androidx.media3.common.C;
import androidx.media3.common.util.GlProgram; import androidx.media3.common.util.GlProgram;
import androidx.media3.common.util.GlUtil; import androidx.media3.common.util.GlUtil;
import androidx.media3.common.util.Log;
import java.io.IOException; import java.io.IOException;
import java.util.Locale; import java.util.Locale;
import javax.microedition.khronos.opengles.GL10; import javax.microedition.khronos.opengles.GL10;
...@@ -41,6 +42,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; ...@@ -41,6 +42,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
/* package */ final class BitmapOverlayVideoProcessor /* package */ final class BitmapOverlayVideoProcessor
implements VideoProcessingGLSurfaceView.VideoProcessor { implements VideoProcessingGLSurfaceView.VideoProcessor {
private static final String TAG = "BitmapOverlayVP";
private static final int OVERLAY_WIDTH = 512; private static final int OVERLAY_WIDTH = 512;
private static final int OVERLAY_HEIGHT = 256; private static final int OVERLAY_HEIGHT = 256;
...@@ -85,11 +87,18 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; ...@@ -85,11 +87,18 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
/* fragmentShaderFilePath= */ "bitmap_overlay_video_processor_fragment.glsl"); /* fragmentShaderFilePath= */ "bitmap_overlay_video_processor_fragment.glsl");
} catch (IOException e) { } catch (IOException e) {
throw new IllegalStateException(e); throw new IllegalStateException(e);
} catch (GlUtil.GlException e) {
Log.e(TAG, "Failed to initialize the shader program", e);
return;
} }
program.setBufferAttribute( program.setBufferAttribute(
"aFramePosition", GlUtil.getNormalizedCoordinateBounds(), GlUtil.RECTANGLE_VERTICES_COUNT); "aFramePosition",
GlUtil.getNormalizedCoordinateBounds(),
GlUtil.HOMOGENEOUS_COORDINATE_VECTOR_SIZE);
program.setBufferAttribute( program.setBufferAttribute(
"aTexCoords", GlUtil.getTextureCoordinateBounds(), GlUtil.RECTANGLE_VERTICES_COUNT); "aTexCoords",
GlUtil.getTextureCoordinateBounds(),
GlUtil.HOMOGENEOUS_COORDINATE_VECTOR_SIZE);
GLES20.glGenTextures(1, textures, 0); GLES20.glGenTextures(1, textures, 0);
GLES20.glBindTexture(GL10.GL_TEXTURE_2D, textures[0]); GLES20.glBindTexture(GL10.GL_TEXTURE_2D, textures[0]);
GLES20.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_NEAREST); GLES20.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_NEAREST);
...@@ -115,7 +124,11 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; ...@@ -115,7 +124,11 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
GLES20.glBindTexture(GL10.GL_TEXTURE_2D, textures[0]); GLES20.glBindTexture(GL10.GL_TEXTURE_2D, textures[0]);
GLUtils.texSubImage2D( GLUtils.texSubImage2D(
GL10.GL_TEXTURE_2D, /* level= */ 0, /* xoffset= */ 0, /* yoffset= */ 0, overlayBitmap); GL10.GL_TEXTURE_2D, /* level= */ 0, /* xoffset= */ 0, /* yoffset= */ 0, overlayBitmap);
try {
GlUtil.checkGlError(); GlUtil.checkGlError();
} catch (GlUtil.GlException e) {
Log.e(TAG, "Failed to populate the texture", e);
}
// Run the shader program. // Run the shader program.
GlProgram program = checkNotNull(this.program); GlProgram program = checkNotNull(this.program);
...@@ -124,16 +137,28 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; ...@@ -124,16 +137,28 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
program.setFloatUniform("uScaleX", bitmapScaleX); program.setFloatUniform("uScaleX", bitmapScaleX);
program.setFloatUniform("uScaleY", bitmapScaleY); program.setFloatUniform("uScaleY", bitmapScaleY);
program.setFloatsUniform("uTexTransform", transformMatrix); program.setFloatsUniform("uTexTransform", transformMatrix);
try {
program.bindAttributesAndUniforms(); program.bindAttributesAndUniforms();
} catch (GlUtil.GlException e) {
Log.e(TAG, "Failed to update the shader program", e);
}
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT); GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, /* first= */ 0, /* count= */ 4); GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, /* first= */ 0, /* count= */ 4);
try {
GlUtil.checkGlError(); GlUtil.checkGlError();
} catch (GlUtil.GlException e) {
Log.e(TAG, "Failed to draw a frame", e);
}
} }
@Override @Override
public void release() { public void release() {
if (program != null) { if (program != null) {
try {
program.delete(); program.delete();
} catch (GlUtil.GlException e) {
Log.e(TAG, "Failed to delete the shader program", e);
}
} }
} }
} }
...@@ -20,6 +20,7 @@ import android.content.Context; ...@@ -20,6 +20,7 @@ import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.net.Uri; import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.text.TextUtils;
import android.widget.FrameLayout; import android.widget.FrameLayout;
import android.widget.Toast; import android.widget.Toast;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
...@@ -156,13 +157,18 @@ public final class MainActivity extends Activity { ...@@ -156,13 +157,18 @@ public final class MainActivity extends Activity {
DataSource.Factory dataSourceFactory = new DefaultDataSource.Factory(this); DataSource.Factory dataSourceFactory = new DefaultDataSource.Factory(this);
MediaSource mediaSource; MediaSource mediaSource;
@C.ContentType int type = Util.inferContentType(uri, intent.getStringExtra(EXTENSION_EXTRA)); @Nullable String fileExtension = intent.getStringExtra(EXTENSION_EXTRA);
if (type == C.TYPE_DASH) { @C.ContentType
int type =
TextUtils.isEmpty(fileExtension)
? Util.inferContentType(uri)
: Util.inferContentTypeForExtension(fileExtension);
if (type == C.CONTENT_TYPE_DASH) {
mediaSource = mediaSource =
new DashMediaSource.Factory(dataSourceFactory) new DashMediaSource.Factory(dataSourceFactory)
.setDrmSessionManagerProvider(unusedMediaItem -> drmSessionManager) .setDrmSessionManagerProvider(unusedMediaItem -> drmSessionManager)
.createMediaSource(MediaItem.fromUri(uri)); .createMediaSource(MediaItem.fromUri(uri));
} else if (type == C.TYPE_OTHER) { } else if (type == C.CONTENT_TYPE_OTHER) {
mediaSource = mediaSource =
new ProgressiveMediaSource.Factory(dataSourceFactory) new ProgressiveMediaSource.Factory(dataSourceFactory)
.setDrmSessionManagerProvider(unusedMediaItem -> drmSessionManager) .setDrmSessionManagerProvider(unusedMediaItem -> drmSessionManager)
......
...@@ -28,6 +28,7 @@ import androidx.media3.common.C; ...@@ -28,6 +28,7 @@ import androidx.media3.common.C;
import androidx.media3.common.Format; import androidx.media3.common.Format;
import androidx.media3.common.util.Assertions; import androidx.media3.common.util.Assertions;
import androidx.media3.common.util.GlUtil; import androidx.media3.common.util.GlUtil;
import androidx.media3.common.util.Log;
import androidx.media3.common.util.TimedValueQueue; import androidx.media3.common.util.TimedValueQueue;
import androidx.media3.exoplayer.ExoPlayer; import androidx.media3.exoplayer.ExoPlayer;
import androidx.media3.exoplayer.video.VideoFrameMetadataListener; import androidx.media3.exoplayer.video.VideoFrameMetadataListener;
...@@ -70,6 +71,7 @@ public final class VideoProcessingGLSurfaceView extends GLSurfaceView { ...@@ -70,6 +71,7 @@ public final class VideoProcessingGLSurfaceView extends GLSurfaceView {
} }
private static final int EGL_PROTECTED_CONTENT_EXT = 0x32C0; private static final int EGL_PROTECTED_CONTENT_EXT = 0x32C0;
private static final String TAG = "VPGlSurfaceView";
private final VideoRenderer renderer; private final VideoRenderer renderer;
private final Handler mainHandler; private final Handler mainHandler;
...@@ -239,7 +241,11 @@ public final class VideoProcessingGLSurfaceView extends GLSurfaceView { ...@@ -239,7 +241,11 @@ public final class VideoProcessingGLSurfaceView extends GLSurfaceView {
@Override @Override
public synchronized void onSurfaceCreated(GL10 gl, EGLConfig config) { public synchronized void onSurfaceCreated(GL10 gl, EGLConfig config) {
try {
texture = GlUtil.createExternalTexture(); texture = GlUtil.createExternalTexture();
} catch (GlUtil.GlException e) {
Log.e(TAG, "Failed to create an external texture", e);
}
surfaceTexture = new SurfaceTexture(texture); surfaceTexture = new SurfaceTexture(texture);
surfaceTexture.setOnFrameAvailableListener( surfaceTexture.setOnFrameAvailableListener(
surfaceTexture -> { surfaceTexture -> {
......
...@@ -79,6 +79,12 @@ ...@@ -79,6 +79,12 @@
<data android:scheme="ssai"/> <data android:scheme="ssai"/>
</intent-filter> </intent-filter>
<intent-filter> <intent-filter>
<action android:name="androidx.media3.demo.main.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<data android:scheme="content"/>
<data android:mimeType="*/*"/>
</intent-filter>
<intent-filter>
<action android:name="androidx.media3.demo.main.action.VIEW_LIST"/> <action android:name="androidx.media3.demo.main.action.VIEW_LIST"/>
<category android:name="android.intent.category.DEFAULT"/> <category android:name="android.intent.category.DEFAULT"/>
</intent-filter> </intent-filter>
......
...@@ -177,7 +177,7 @@ public class IntentUtil { ...@@ -177,7 +177,7 @@ public class IntentUtil {
headers.put(keyRequestPropertiesArray[i], keyRequestPropertiesArray[i + 1]); headers.put(keyRequestPropertiesArray[i], keyRequestPropertiesArray[i + 1]);
} }
} }
@Nullable UUID drmUuid = Util.getDrmUuid(Util.castNonNull(drmSchemeExtra)); @Nullable UUID drmUuid = Util.getDrmUuid(drmSchemeExtra);
if (drmUuid != null) { if (drmUuid != null) {
builder.setDrmConfiguration( builder.setDrmConfiguration(
new MediaItem.DrmConfiguration.Builder(drmUuid) new MediaItem.DrmConfiguration.Builder(drmUuid)
...@@ -188,7 +188,7 @@ public class IntentUtil { ...@@ -188,7 +188,7 @@ public class IntentUtil {
intent.getBooleanExtra( intent.getBooleanExtra(
DRM_FORCE_DEFAULT_LICENSE_URI_EXTRA + extrasKeySuffix, false)) DRM_FORCE_DEFAULT_LICENSE_URI_EXTRA + extrasKeySuffix, false))
.setLicenseRequestHeaders(headers) .setLicenseRequestHeaders(headers)
.forceSessionsForAudioAndVideoTracks( .setForceSessionsForAudioAndVideoTracks(
intent.getBooleanExtra(DRM_SESSION_FOR_CLEAR_CONTENT + extrasKeySuffix, false)) intent.getBooleanExtra(DRM_SESSION_FOR_CLEAR_CONTENT + extrasKeySuffix, false))
.build()); .build());
} }
......
...@@ -38,10 +38,12 @@ import androidx.media3.common.PlaybackException; ...@@ -38,10 +38,12 @@ import androidx.media3.common.PlaybackException;
import androidx.media3.common.Player; import androidx.media3.common.Player;
import androidx.media3.common.TrackSelectionParameters; import androidx.media3.common.TrackSelectionParameters;
import androidx.media3.common.Tracks; import androidx.media3.common.Tracks;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.common.util.Util; import androidx.media3.common.util.Util;
import androidx.media3.datasource.DataSource; import androidx.media3.datasource.DataSource;
import androidx.media3.exoplayer.ExoPlayer; import androidx.media3.exoplayer.ExoPlayer;
import androidx.media3.exoplayer.RenderersFactory; import androidx.media3.exoplayer.RenderersFactory;
import androidx.media3.exoplayer.drm.DefaultDrmSessionManagerProvider;
import androidx.media3.exoplayer.drm.FrameworkMediaDrm; import androidx.media3.exoplayer.drm.FrameworkMediaDrm;
import androidx.media3.exoplayer.ima.ImaAdsLoader; import androidx.media3.exoplayer.ima.ImaAdsLoader;
import androidx.media3.exoplayer.ima.ImaServerSideAdInsertionMediaSource; import androidx.media3.exoplayer.ima.ImaServerSideAdInsertionMediaSource;
...@@ -53,7 +55,6 @@ import androidx.media3.exoplayer.source.MediaSource; ...@@ -53,7 +55,6 @@ import androidx.media3.exoplayer.source.MediaSource;
import androidx.media3.exoplayer.source.ads.AdsLoader; import androidx.media3.exoplayer.source.ads.AdsLoader;
import androidx.media3.exoplayer.util.DebugTextViewHelper; import androidx.media3.exoplayer.util.DebugTextViewHelper;
import androidx.media3.exoplayer.util.EventLogger; import androidx.media3.exoplayer.util.EventLogger;
import androidx.media3.ui.PlayerControlView;
import androidx.media3.ui.PlayerView; import androidx.media3.ui.PlayerView;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
...@@ -62,7 +63,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; ...@@ -62,7 +63,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
/** An activity that plays media using {@link ExoPlayer}. */ /** An activity that plays media using {@link ExoPlayer}. */
public class PlayerActivity extends AppCompatActivity public class PlayerActivity extends AppCompatActivity
implements OnClickListener, PlayerControlView.VisibilityListener { implements OnClickListener, PlayerView.ControllerVisibilityListener {
// Saved instance state keys. // Saved instance state keys.
...@@ -91,7 +92,12 @@ public class PlayerActivity extends AppCompatActivity ...@@ -91,7 +92,12 @@ public class PlayerActivity extends AppCompatActivity
// For ad playback only. // For ad playback only.
@Nullable private AdsLoader clientSideAdsLoader; @Nullable private AdsLoader clientSideAdsLoader;
// TODO: Annotate this and serverSideAdsLoaderState below with @OptIn when it can be applied to
// fields (needs http://r.android.com/2004032 to be released into a version of
// androidx.annotation:annotation-experimental).
@Nullable private ImaServerSideAdInsertionMediaSource.AdsLoader serverSideAdsLoader; @Nullable private ImaServerSideAdInsertionMediaSource.AdsLoader serverSideAdsLoader;
private ImaServerSideAdInsertionMediaSource.AdsLoader.@MonotonicNonNull State private ImaServerSideAdInsertionMediaSource.AdsLoader.@MonotonicNonNull State
serverSideAdsLoaderState; serverSideAdsLoaderState;
...@@ -115,17 +121,12 @@ public class PlayerActivity extends AppCompatActivity ...@@ -115,17 +121,12 @@ public class PlayerActivity extends AppCompatActivity
if (savedInstanceState != null) { if (savedInstanceState != null) {
trackSelectionParameters = trackSelectionParameters =
TrackSelectionParameters.CREATOR.fromBundle( TrackSelectionParameters.fromBundle(
savedInstanceState.getBundle(KEY_TRACK_SELECTION_PARAMETERS)); savedInstanceState.getBundle(KEY_TRACK_SELECTION_PARAMETERS));
startAutoPlay = savedInstanceState.getBoolean(KEY_AUTO_PLAY); startAutoPlay = savedInstanceState.getBoolean(KEY_AUTO_PLAY);
startItemIndex = savedInstanceState.getInt(KEY_ITEM_INDEX); startItemIndex = savedInstanceState.getInt(KEY_ITEM_INDEX);
startPosition = savedInstanceState.getLong(KEY_POSITION); startPosition = savedInstanceState.getLong(KEY_POSITION);
Bundle adsLoaderStateBundle = savedInstanceState.getBundle(KEY_SERVER_SIDE_ADS_LOADER_STATE); restoreServerSideAdsLoaderState(savedInstanceState);
if (adsLoaderStateBundle != null) {
serverSideAdsLoaderState =
ImaServerSideAdInsertionMediaSource.AdsLoader.State.CREATOR.fromBundle(
adsLoaderStateBundle);
}
} else { } else {
trackSelectionParameters = new TrackSelectionParameters.Builder(/* context= */ this).build(); trackSelectionParameters = new TrackSelectionParameters.Builder(/* context= */ this).build();
clearStartPosition(); clearStartPosition();
...@@ -217,9 +218,7 @@ public class PlayerActivity extends AppCompatActivity ...@@ -217,9 +218,7 @@ public class PlayerActivity extends AppCompatActivity
outState.putBoolean(KEY_AUTO_PLAY, startAutoPlay); outState.putBoolean(KEY_AUTO_PLAY, startAutoPlay);
outState.putInt(KEY_ITEM_INDEX, startItemIndex); outState.putInt(KEY_ITEM_INDEX, startItemIndex);
outState.putLong(KEY_POSITION, startPosition); outState.putLong(KEY_POSITION, startPosition);
if (serverSideAdsLoaderState != null) { saveServerSideAdsLoaderState(outState);
outState.putBundle(KEY_SERVER_SIDE_ADS_LOADER_STATE, serverSideAdsLoaderState.toBundle());
}
} }
// Activity input // Activity input
...@@ -246,10 +245,10 @@ public class PlayerActivity extends AppCompatActivity ...@@ -246,10 +245,10 @@ public class PlayerActivity extends AppCompatActivity
} }
} }
// PlayerControlView.VisibilityListener implementation // PlayerView.ControllerVisibilityListener implementation
@Override @Override
public void onVisibilityChange(int visibility) { public void onVisibilityChanged(int visibility) {
debugRootView.setVisibility(visibility); debugRootView.setVisibility(visibility);
} }
...@@ -271,24 +270,20 @@ public class PlayerActivity extends AppCompatActivity ...@@ -271,24 +270,20 @@ public class PlayerActivity extends AppCompatActivity
return false; return false;
} }
boolean preferExtensionDecoders =
intent.getBooleanExtra(IntentUtil.PREFER_EXTENSION_DECODERS_EXTRA, false);
RenderersFactory renderersFactory =
DemoUtil.buildRenderersFactory(/* context= */ this, preferExtensionDecoders);
lastSeenTracks = Tracks.EMPTY; lastSeenTracks = Tracks.EMPTY;
player = ExoPlayer.Builder playerBuilder =
new ExoPlayer.Builder(/* context= */ this) new ExoPlayer.Builder(/* context= */ this)
.setRenderersFactory(renderersFactory) .setMediaSourceFactory(createMediaSourceFactory());
.setMediaSourceFactory(createMediaSourceFactory()) setRenderersFactory(
.build(); playerBuilder, intent.getBooleanExtra(IntentUtil.PREFER_EXTENSION_DECODERS_EXTRA, false));
player = playerBuilder.build();
player.setTrackSelectionParameters(trackSelectionParameters); player.setTrackSelectionParameters(trackSelectionParameters);
player.addListener(new PlayerEventListener()); player.addListener(new PlayerEventListener());
player.addAnalyticsListener(new EventLogger()); player.addAnalyticsListener(new EventLogger());
player.setAudioAttributes(AudioAttributes.DEFAULT, /* handleAudioFocus= */ true); player.setAudioAttributes(AudioAttributes.DEFAULT, /* handleAudioFocus= */ true);
player.setPlayWhenReady(startAutoPlay); player.setPlayWhenReady(startAutoPlay);
playerView.setPlayer(player); playerView.setPlayer(player);
serverSideAdsLoader.setPlayer(player); configurePlayerWithServerSideAdsLoader();
debugViewHelper = new DebugTextViewHelper(player, debugTextView); debugViewHelper = new DebugTextViewHelper(player, debugTextView);
debugViewHelper.start(); debugViewHelper.start();
} }
...@@ -302,7 +297,12 @@ public class PlayerActivity extends AppCompatActivity ...@@ -302,7 +297,12 @@ public class PlayerActivity extends AppCompatActivity
return true; return true;
} }
@OptIn(markerClass = UnstableApi.class) // SSAI configuration
private MediaSource.Factory createMediaSourceFactory() { private MediaSource.Factory createMediaSourceFactory() {
DefaultDrmSessionManagerProvider drmSessionManagerProvider =
new DefaultDrmSessionManagerProvider();
drmSessionManagerProvider.setDrmHttpDataSourceFactory(
DemoUtil.getHttpDataSourceFactory(/* context= */ this));
ImaServerSideAdInsertionMediaSource.AdsLoader.Builder serverSideAdLoaderBuilder = ImaServerSideAdInsertionMediaSource.AdsLoader.Builder serverSideAdLoaderBuilder =
new ImaServerSideAdInsertionMediaSource.AdsLoader.Builder(/* context= */ this, playerView); new ImaServerSideAdInsertionMediaSource.AdsLoader.Builder(/* context= */ this, playerView);
if (serverSideAdsLoaderState != null) { if (serverSideAdsLoaderState != null) {
...@@ -311,13 +311,30 @@ public class PlayerActivity extends AppCompatActivity ...@@ -311,13 +311,30 @@ public class PlayerActivity extends AppCompatActivity
serverSideAdsLoader = serverSideAdLoaderBuilder.build(); serverSideAdsLoader = serverSideAdLoaderBuilder.build();
ImaServerSideAdInsertionMediaSource.Factory imaServerSideAdInsertionMediaSourceFactory = ImaServerSideAdInsertionMediaSource.Factory imaServerSideAdInsertionMediaSourceFactory =
new ImaServerSideAdInsertionMediaSource.Factory( new ImaServerSideAdInsertionMediaSource.Factory(
serverSideAdsLoader, new DefaultMediaSourceFactory(dataSourceFactory)); serverSideAdsLoader,
return new DefaultMediaSourceFactory(dataSourceFactory) new DefaultMediaSourceFactory(/* context= */ this)
.setAdsLoaderProvider(this::getClientSideAdsLoader) .setDataSourceFactory(dataSourceFactory));
.setAdViewProvider(playerView) return new DefaultMediaSourceFactory(/* context= */ this)
.setDataSourceFactory(dataSourceFactory)
.setDrmSessionManagerProvider(drmSessionManagerProvider)
.setLocalAdInsertionComponents(
this::getClientSideAdsLoader, /* adViewProvider= */ playerView)
.setServerSideAdInsertionMediaSourceFactory(imaServerSideAdInsertionMediaSourceFactory); .setServerSideAdInsertionMediaSourceFactory(imaServerSideAdInsertionMediaSourceFactory);
} }
@OptIn(markerClass = UnstableApi.class)
private void setRenderersFactory(
ExoPlayer.Builder playerBuilder, boolean preferExtensionDecoders) {
RenderersFactory renderersFactory =
DemoUtil.buildRenderersFactory(/* context= */ this, preferExtensionDecoders);
playerBuilder.setRenderersFactory(renderersFactory);
}
@OptIn(markerClass = UnstableApi.class)
private void configurePlayerWithServerSideAdsLoader() {
serverSideAdsLoader.setPlayer(player);
}
private List<MediaItem> createMediaItems(Intent intent) { private List<MediaItem> createMediaItems(Intent intent) {
String action = intent.getAction(); String action = intent.getAction();
boolean actionIsListView = IntentUtil.ACTION_VIEW_LIST.equals(action); boolean actionIsListView = IntentUtil.ACTION_VIEW_LIST.equals(action);
...@@ -371,8 +388,7 @@ public class PlayerActivity extends AppCompatActivity ...@@ -371,8 +388,7 @@ public class PlayerActivity extends AppCompatActivity
if (player != null) { if (player != null) {
updateTrackSelectorParameters(); updateTrackSelectorParameters();
updateStartPosition(); updateStartPosition();
serverSideAdsLoaderState = serverSideAdsLoader.release(); releaseServerSideAdsLoader();
serverSideAdsLoader = null;
debugViewHelper.stop(); debugViewHelper.stop();
debugViewHelper = null; debugViewHelper = null;
player.release(); player.release();
...@@ -387,6 +403,12 @@ public class PlayerActivity extends AppCompatActivity ...@@ -387,6 +403,12 @@ public class PlayerActivity extends AppCompatActivity
} }
} }
@OptIn(markerClass = UnstableApi.class)
private void releaseServerSideAdsLoader() {
serverSideAdsLoaderState = serverSideAdsLoader.release();
serverSideAdsLoader = null;
}
private void releaseClientSideAdsLoader() { private void releaseClientSideAdsLoader() {
if (clientSideAdsLoader != null) { if (clientSideAdsLoader != null) {
clientSideAdsLoader.release(); clientSideAdsLoader.release();
...@@ -395,6 +417,23 @@ public class PlayerActivity extends AppCompatActivity ...@@ -395,6 +417,23 @@ public class PlayerActivity extends AppCompatActivity
} }
} }
@OptIn(markerClass = UnstableApi.class)
private void saveServerSideAdsLoaderState(Bundle outState) {
if (serverSideAdsLoaderState != null) {
outState.putBundle(KEY_SERVER_SIDE_ADS_LOADER_STATE, serverSideAdsLoaderState.toBundle());
}
}
@OptIn(markerClass = UnstableApi.class)
private void restoreServerSideAdsLoaderState(Bundle savedInstanceState) {
Bundle adsLoaderStateBundle = savedInstanceState.getBundle(KEY_SERVER_SIDE_ADS_LOADER_STATE);
if (adsLoaderStateBundle != null) {
serverSideAdsLoaderState =
ImaServerSideAdInsertionMediaSource.AdsLoader.State.CREATOR.fromBundle(
adsLoaderStateBundle);
}
}
private void updateTrackSelectorParameters() { private void updateTrackSelectorParameters() {
if (player != null) { if (player != null) {
trackSelectionParameters = player.getTrackSelectionParameters(); trackSelectionParameters = player.getTrackSelectionParameters();
......
...@@ -27,6 +27,7 @@ import android.content.res.AssetManager; ...@@ -27,6 +27,7 @@ import android.content.res.AssetManager;
import android.net.Uri; import android.net.Uri;
import android.os.AsyncTask; import android.os.AsyncTask;
import android.os.Bundle; import android.os.Bundle;
import android.text.TextUtils;
import android.util.JsonReader; import android.util.JsonReader;
import android.view.Menu; import android.view.Menu;
import android.view.MenuInflater; import android.view.MenuInflater;
...@@ -443,7 +444,10 @@ public class SampleChooserActivity extends AppCompatActivity ...@@ -443,7 +444,10 @@ public class SampleChooserActivity extends AppCompatActivity
} else { } else {
@Nullable @Nullable
String adaptiveMimeType = String adaptiveMimeType =
Util.getAdaptiveMimeTypeForContentType(Util.inferContentType(uri, extension)); Util.getAdaptiveMimeTypeForContentType(
TextUtils.isEmpty(extension)
? Util.inferContentType(uri)
: Util.inferContentTypeForExtension(extension));
mediaItem mediaItem
.setUri(uri) .setUri(uri)
.setMediaMetadata(new MediaMetadata.Builder().setTitle(title).build()) .setMediaMetadata(new MediaMetadata.Builder().setTitle(title).build())
...@@ -454,7 +458,7 @@ public class SampleChooserActivity extends AppCompatActivity ...@@ -454,7 +458,7 @@ public class SampleChooserActivity extends AppCompatActivity
new MediaItem.DrmConfiguration.Builder(drmUuid) new MediaItem.DrmConfiguration.Builder(drmUuid)
.setLicenseUri(drmLicenseUri) .setLicenseUri(drmLicenseUri)
.setLicenseRequestHeaders(drmLicenseRequestHeaders) .setLicenseRequestHeaders(drmLicenseRequestHeaders)
.forceSessionsForAudioAndVideoTracks(drmSessionForClearContent) .setForceSessionsForAudioAndVideoTracks(drmSessionForClearContent)
.setMultiSession(drmMultiSession) .setMultiSession(drmMultiSession)
.setForceDefaultLicenseUri(drmForceDefaultLicenseUri) .setForceDefaultLicenseUri(drmForceDefaultLicenseUri)
.build()); .build());
......
...@@ -25,6 +25,7 @@ import android.view.View; ...@@ -25,6 +25,7 @@ import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.Button; import android.widget.Button;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.annotation.OptIn;
import androidx.appcompat.app.AppCompatDialog; import androidx.appcompat.app.AppCompatDialog;
import androidx.fragment.app.DialogFragment; import androidx.fragment.app.DialogFragment;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
...@@ -36,6 +37,7 @@ import androidx.media3.common.TrackGroup; ...@@ -36,6 +37,7 @@ import androidx.media3.common.TrackGroup;
import androidx.media3.common.TrackSelectionOverride; import androidx.media3.common.TrackSelectionOverride;
import androidx.media3.common.TrackSelectionParameters; import androidx.media3.common.TrackSelectionParameters;
import androidx.media3.common.Tracks; import androidx.media3.common.Tracks;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.ui.TrackSelectionView; import androidx.media3.ui.TrackSelectionView;
import androidx.viewpager.widget.ViewPager; import androidx.viewpager.widget.ViewPager;
import com.google.android.material.tabs.TabLayout; import com.google.android.material.tabs.TabLayout;
...@@ -47,6 +49,7 @@ import java.util.List; ...@@ -47,6 +49,7 @@ import java.util.List;
import java.util.Map; import java.util.Map;
/** Dialog to select tracks. */ /** Dialog to select tracks. */
@OptIn(markerClass = UnstableApi.class)
public final class TrackSelectionDialog extends DialogFragment { public final class TrackSelectionDialog extends DialogFragment {
/** Called when tracks are selected. */ /** Called when tracks are selected. */
......
...@@ -45,6 +45,7 @@ ...@@ -45,6 +45,7 @@
<service <service
android:name=".PlaybackService" android:name=".PlaybackService"
android:foregroundServiceType="mediaPlayback"
android:exported="true"> android:exported="true">
<intent-filter> <intent-filter>
<action android:name="androidx.media3.session.MediaSessionService"/> <action android:name="androidx.media3.session.MediaSessionService"/>
......
...@@ -43,6 +43,12 @@ ...@@ -43,6 +43,12 @@
<data android:scheme="asset"/> <data android:scheme="asset"/>
<data android:scheme="file"/> <data android:scheme="file"/>
</intent-filter> </intent-filter>
<intent-filter>
<action android:name="androidx.media3.demo.surface.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<data android:scheme="content"/>
<data android:mimeType="*/*"/>
</intent-filter>
</activity> </activity>
</application> </application>
......
...@@ -19,6 +19,7 @@ import android.app.Activity; ...@@ -19,6 +19,7 @@ import android.app.Activity;
import android.content.Intent; import android.content.Intent;
import android.net.Uri; import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.text.TextUtils;
import android.view.Surface; import android.view.Surface;
import android.view.SurfaceControl; import android.view.SurfaceControl;
import android.view.SurfaceHolder; import android.view.SurfaceHolder;
...@@ -201,13 +202,18 @@ public final class MainActivity extends Activity { ...@@ -201,13 +202,18 @@ public final class MainActivity extends Activity {
DataSource.Factory dataSourceFactory = new DefaultDataSource.Factory(this); DataSource.Factory dataSourceFactory = new DefaultDataSource.Factory(this);
MediaSource mediaSource; MediaSource mediaSource;
@C.ContentType int type = Util.inferContentType(uri, intent.getStringExtra(EXTENSION_EXTRA)); @Nullable String fileExtension = intent.getStringExtra(EXTENSION_EXTRA);
if (type == C.TYPE_DASH) { @C.ContentType
int type =
TextUtils.isEmpty(fileExtension)
? Util.inferContentType(uri)
: Util.inferContentTypeForExtension(fileExtension);
if (type == C.CONTENT_TYPE_DASH) {
mediaSource = mediaSource =
new DashMediaSource.Factory(dataSourceFactory) new DashMediaSource.Factory(dataSourceFactory)
.setDrmSessionManagerProvider(unusedMediaItem -> drmSessionManager) .setDrmSessionManagerProvider(unusedMediaItem -> drmSessionManager)
.createMediaSource(MediaItem.fromUri(uri)); .createMediaSource(MediaItem.fromUri(uri));
} else if (type == C.TYPE_OTHER) { } else if (type == C.CONTENT_TYPE_OTHER) {
mediaSource = mediaSource =
new ProgressiveMediaSource.Factory(dataSourceFactory) new ProgressiveMediaSource.Factory(dataSourceFactory)
.setDrmSessionManagerProvider(unusedMediaItem -> drmSessionManager) .setDrmSessionManagerProvider(unusedMediaItem -> drmSessionManager)
......
# 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. ...@@ -6,4 +6,61 @@ example by removing audio or video.
See the [demos README](../README.md) for instructions on how to build and run See the [demos README](../README.md) for instructions on how to build and run
this demo. this demo.
## MediaPipe frame processing demo
Building the demo app with [MediaPipe][] integration enabled requires some extra
manual steps.
1. Follow the
[instructions](https://google.github.io/mediapipe/getting_started/install.html)
to install MediaPipe.
1. Copy the Transformer demo's build configuration and MediaPipe graph text
protocol buffer under the MediaPipe source tree. This makes it easy to
[build an AAR][] with bazel by reusing MediaPipe's workspace.
```sh
cd "<path to MediaPipe checkout>"
MEDIAPIPE_ROOT="$(pwd)"
MEDIAPIPE_TRANSFORMER_ROOT="${MEDIAPIPE_ROOT}/mediapipe/java/com/google/mediapipe/transformer"
cd "<path to the transformer demo (containing this readme)>"
TRANSFORMER_DEMO_ROOT="$(pwd)"
mkdir -p "${MEDIAPIPE_TRANSFORMER_ROOT}"
mkdir -p "${TRANSFORMER_DEMO_ROOT}/libs"
cp ${TRANSFORMER_DEMO_ROOT}/BUILD.bazel ${MEDIAPIPE_TRANSFORMER_ROOT}/BUILD
cp ${TRANSFORMER_DEMO_ROOT}/src/withMediaPipe/assets/edge_detector_mediapipe_graph.pbtxt \
${MEDIAPIPE_TRANSFORMER_ROOT}
```
1. Build the AAR and the binary proto for the demo's MediaPipe graph, then copy
them to Transformer.
```sh
cd ${MEDIAPIPE_ROOT}
bazel build -c opt --strip=ALWAYS \
--host_crosstool_top=@bazel_tools//tools/cpp:toolchain \
--fat_apk_cpu=arm64-v8a,armeabi-v7a \
--legacy_whole_archive=0 \
--features=-legacy_whole_archive \
--copt=-fvisibility=hidden \
--copt=-ffunction-sections \
--copt=-fdata-sections \
--copt=-fstack-protector \
--copt=-Oz \
--copt=-fomit-frame-pointer \
--copt=-DABSL_MIN_LOG_LEVEL=2 \
--linkopt=-Wl,--gc-sections,--strip-all \
mediapipe/java/com/google/mediapipe/transformer:edge_detector_mediapipe_aar.aar
cp bazel-bin/mediapipe/java/com/google/mediapipe/transformer/edge_detector_mediapipe_aar.aar \
${TRANSFORMER_DEMO_ROOT}/libs
bazel build mediapipe/java/com/google/mediapipe/transformer:edge_detector_binary_graph
cp bazel-bin/mediapipe/java/com/google/mediapipe/transformer/edge_detector_mediapipe_graph.binarypb \
${TRANSFORMER_DEMO_ROOT}/src/withMediaPipe/assets
```
1. In Android Studio, gradle sync and select the `withMediaPipe` build variant
(this will only appear if the AAR is present), then build and run the demo
app and select a MediaPipe-based effect.
[Transformer]: https://exoplayer.dev/transforming-media.html [Transformer]: https://exoplayer.dev/transforming-media.html
[MediaPipe]: https://google.github.io/mediapipe/
[build an AAR]: https://google.github.io/mediapipe/getting_started/android_archive_library.html
...@@ -45,6 +45,27 @@ android { ...@@ -45,6 +45,27 @@ android {
// This demo app isn't indexed and doesn't have translations. // This demo app isn't indexed and doesn't have translations.
disable 'GoogleAppIndexingWarning','MissingTranslation' disable 'GoogleAppIndexingWarning','MissingTranslation'
} }
flavorDimensions "mediaPipe"
productFlavors {
noMediaPipe {
dimension "mediaPipe"
}
withMediaPipe {
dimension "mediaPipe"
}
}
// Ignore the withMediaPipe variant if the MediaPipe AAR is not present.
if (!project.file("libs/edge_detector_mediapipe_aar.aar").exists()) {
variantFilter { variant ->
def names = variant.flavors*.name
if (names.contains("withMediaPipe")) {
setIgnore(true)
}
}
}
} }
dependencies { dependencies {
...@@ -56,6 +77,14 @@ dependencies { ...@@ -56,6 +77,14 @@ dependencies {
implementation 'androidx.multidex:multidex:' + androidxMultidexVersion implementation 'androidx.multidex:multidex:' + androidxMultidexVersion
implementation 'com.google.android.material:material:' + androidxMaterialVersion implementation 'com.google.android.material:material:' + androidxMaterialVersion
implementation project(modulePrefix + 'lib-exoplayer') implementation project(modulePrefix + 'lib-exoplayer')
implementation project(modulePrefix + 'lib-exoplayer-dash')
implementation project(modulePrefix + 'lib-transformer') implementation project(modulePrefix + 'lib-transformer')
implementation project(modulePrefix + 'lib-ui') implementation project(modulePrefix + 'lib-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'
} }
...@@ -49,6 +49,12 @@ ...@@ -49,6 +49,12 @@
<data android:scheme="asset"/> <data android:scheme="asset"/>
<data android:scheme="file"/> <data android:scheme="file"/>
</intent-filter> </intent-filter>
<intent-filter>
<action android:name="androidx.media3.demo.transformer.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<data android:scheme="content"/>
<data android:mimeType="*/*"/>
</intent-filter>
</activity> </activity>
<activity android:name=".TransformerActivity" <activity android:name=".TransformerActivity"
android:configChanges="keyboard|keyboardHidden|orientation|screenSize|screenLayout|smallestScreenSize|uiMode" android:configChanges="keyboard|keyboardHidden|orientation|screenSize|screenLayout|smallestScreenSize|uiMode"
......
...@@ -16,9 +16,8 @@ ...@@ -16,9 +16,8 @@
// ES 2 vertex shader that leaves the coordinates unchanged. // ES 2 vertex shader that leaves the coordinates unchanged.
attribute vec4 aFramePosition; attribute vec4 aFramePosition;
attribute vec4 aTexSamplingCoord;
varying vec2 vTexSamplingCoord; varying vec2 vTexSamplingCoord;
void main() { void main() {
gl_Position = aFramePosition; gl_Position = aFramePosition;
vTexSamplingCoord = aTexSamplingCoord.xy; vTexSamplingCoord = vec2(aFramePosition.x * 0.5 + 0.5, aFramePosition.y * 0.5 + 0.5);
} }
...@@ -31,22 +31,20 @@ import android.util.Size; ...@@ -31,22 +31,20 @@ import android.util.Size;
import androidx.media3.common.C; import androidx.media3.common.C;
import androidx.media3.common.util.GlProgram; import androidx.media3.common.util.GlProgram;
import androidx.media3.common.util.GlUtil; import androidx.media3.common.util.GlUtil;
import androidx.media3.transformer.GlFrameProcessor; import androidx.media3.transformer.FrameProcessingException;
import androidx.media3.transformer.SingleFrameGlTextureProcessor;
import java.io.IOException; import java.io.IOException;
import java.util.Locale; import java.util.Locale;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
/** /**
* A {@link GlFrameProcessor} that overlays a bitmap with a logo and timer on each frame. * 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}. * <p>The bitmap is drawn using an Android {@link Canvas}.
*/ */
// TODO(b/227625365): Delete this class and use a frame processor from the Transformer library, once // TODO(b/227625365): Delete this class and use a texture processor from the Transformer library,
// overlaying a bitmap and text is supported in Transformer. // once overlaying a bitmap and text is supported in Transformer.
/* package */ final class BitmapOverlayFrameProcessor implements GlFrameProcessor { /* package */ final class BitmapOverlayProcessor extends SingleFrameGlTextureProcessor {
static {
GlUtil.glAssertionsEnabled = true;
}
private static final String VERTEX_SHADER_PATH = "vertex_shader_copy_es2.glsl"; 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 String FRAGMENT_SHADER_PATH = "fragment_shader_bitmap_overlay_es2.glsl";
...@@ -55,16 +53,20 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; ...@@ -55,16 +53,20 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
private final Paint paint; private final Paint paint;
private final Bitmap overlayBitmap; private final Bitmap overlayBitmap;
private final Bitmap logoBitmap;
private final Canvas overlayCanvas; private final Canvas overlayCanvas;
private final GlProgram glProgram;
private float bitmapScaleX; private float bitmapScaleX;
private float bitmapScaleY; private float bitmapScaleY;
private int bitmapTexId; private int bitmapTexId;
private @MonotonicNonNull Size outputSize;
private @MonotonicNonNull Bitmap logoBitmap;
private @MonotonicNonNull GlProgram glProgram;
public BitmapOverlayFrameProcessor() { /**
* Creates a new instance.
*
* @throws FrameProcessingException If a problem occurs while reading shader files.
*/
public BitmapOverlayProcessor(Context context) throws FrameProcessingException {
paint = new Paint(); paint = new Paint();
paint.setTextSize(64); paint.setTextSize(64);
paint.setAntiAlias(true); paint.setAntiAlias(true);
...@@ -73,19 +75,6 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; ...@@ -73,19 +75,6 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
overlayBitmap = overlayBitmap =
Bitmap.createBitmap(BITMAP_WIDTH_HEIGHT, BITMAP_WIDTH_HEIGHT, Bitmap.Config.ARGB_8888); Bitmap.createBitmap(BITMAP_WIDTH_HEIGHT, BITMAP_WIDTH_HEIGHT, Bitmap.Config.ARGB_8888);
overlayCanvas = new Canvas(overlayBitmap); overlayCanvas = new Canvas(overlayBitmap);
}
@Override
public void initialize(Context context, int inputTexId, int inputWidth, int inputHeight)
throws IOException {
if (inputWidth > inputHeight) {
bitmapScaleX = inputWidth / (float) inputHeight;
bitmapScaleY = 1f;
} else {
bitmapScaleX = 1f;
bitmapScaleY = inputHeight / (float) inputWidth;
}
outputSize = new Size(inputWidth, inputHeight);
try { try {
logoBitmap = logoBitmap =
...@@ -95,30 +84,42 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; ...@@ -95,30 +84,42 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
} catch (PackageManager.NameNotFoundException e) { } catch (PackageManager.NameNotFoundException e) {
throw new IllegalStateException(e); throw new IllegalStateException(e);
} }
try {
bitmapTexId = GlUtil.createTexture(BITMAP_WIDTH_HEIGHT, BITMAP_WIDTH_HEIGHT); bitmapTexId = GlUtil.createTexture(BITMAP_WIDTH_HEIGHT, BITMAP_WIDTH_HEIGHT);
GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, /* level= */ 0, overlayBitmap, /* border= */ 0); GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, /* level= */ 0, overlayBitmap, /* border= */ 0);
glProgram = new GlProgram(context, VERTEX_SHADER_PATH, FRAGMENT_SHADER_PATH); glProgram = new GlProgram(context, VERTEX_SHADER_PATH, FRAGMENT_SHADER_PATH);
} catch (GlUtil.GlException | IOException e) {
throw new FrameProcessingException(e);
}
// Draw the frame on the entire normalized device coordinate space, from -1 to 1, for x and y. // Draw the frame on the entire normalized device coordinate space, from -1 to 1, for x and y.
glProgram.setBufferAttribute( glProgram.setBufferAttribute(
"aFramePosition", GlUtil.getNormalizedCoordinateBounds(), GlUtil.RECTANGLE_VERTICES_COUNT); "aFramePosition",
glProgram.setBufferAttribute( GlUtil.getNormalizedCoordinateBounds(),
"aTexSamplingCoord", GlUtil.getTextureCoordinateBounds(), GlUtil.RECTANGLE_VERTICES_COUNT); GlUtil.HOMOGENEOUS_COORDINATE_VECTOR_SIZE);
glProgram.setSamplerTexIdUniform("uTexSampler0", inputTexId, /* texUnitIndex= */ 0);
glProgram.setSamplerTexIdUniform("uTexSampler1", bitmapTexId, /* texUnitIndex= */ 1); glProgram.setSamplerTexIdUniform("uTexSampler1", bitmapTexId, /* texUnitIndex= */ 1);
glProgram.setFloatUniform("uScaleX", bitmapScaleX);
glProgram.setFloatUniform("uScaleY", bitmapScaleY);
} }
@Override @Override
public Size getOutputSize() { public Size configure(int inputWidth, int inputHeight) {
return checkStateNotNull(outputSize); 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 @Override
public void drawFrame(long presentationTimeUs) { public void drawFrame(int inputTexId, long presentationTimeUs) throws FrameProcessingException {
checkStateNotNull(glProgram); try {
glProgram.use(); checkStateNotNull(glProgram).use();
// Draw to the canvas and store it in a texture. // Draw to the canvas and store it in a texture.
String text = String text =
...@@ -135,15 +136,25 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; ...@@ -135,15 +136,25 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
flipBitmapVertically(overlayBitmap)); flipBitmapVertically(overlayBitmap));
GlUtil.checkGlError(); GlUtil.checkGlError();
glProgram.setSamplerTexIdUniform("uTexSampler0", inputTexId, /* texUnitIndex= */ 0);
glProgram.bindAttributesAndUniforms(); glProgram.bindAttributesAndUniforms();
// The four-vertex triangle strip forms a quad. // The four-vertex triangle strip forms a quad.
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, /* first= */ 0, /* count= */ 4); GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, /* first= */ 0, /* count= */ 4);
GlUtil.checkGlError();
} catch (GlUtil.GlException e) {
throw new FrameProcessingException(e, presentationTimeUs);
}
} }
@Override @Override
public void release() { public void release() throws FrameProcessingException {
super.release();
if (glProgram != null) { if (glProgram != null) {
try {
glProgram.delete(); glProgram.delete();
} catch (GlUtil.GlException e) {
throw new FrameProcessingException(e);
}
} }
} }
......
...@@ -18,39 +18,38 @@ package androidx.media3.demo.transformer; ...@@ -18,39 +18,38 @@ package androidx.media3.demo.transformer;
import android.graphics.Matrix; import android.graphics.Matrix;
import androidx.media3.common.C; import androidx.media3.common.C;
import androidx.media3.common.util.Util; import androidx.media3.common.util.Util;
import androidx.media3.transformer.AdvancedFrameProcessor; import androidx.media3.transformer.GlMatrixTransformation;
import androidx.media3.transformer.GlFrameProcessor; import androidx.media3.transformer.MatrixTransformation;
/** /**
* Factory for {@link GlFrameProcessor GlFrameProcessors} that create video effects by applying * Factory for {@link GlMatrixTransformation GlMatrixTransformations} and {@link
* transformation matrices to the individual video frames using {@link AdvancedFrameProcessor}. * MatrixTransformation MatrixTransformations} that create video effects by applying transformation
* matrices to the individual video frames.
*/ */
/* package */ final class AdvancedFrameProcessorFactory { /* package */ final class MatrixTransformationFactory {
/** /**
* Returns a {@link GlFrameProcessor} that rescales the frames over the first {@value * 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 * #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. * linearly in size from a single point to filling the full output frame.
*/ */
public static GlFrameProcessor createZoomInTransitionFrameProcessor() { public static MatrixTransformation createZoomInTransition() {
return new AdvancedFrameProcessor( return MatrixTransformationFactory::calculateZoomInTransitionMatrix;
/* matrixProvider= */ AdvancedFrameProcessorFactory::calculateZoomInTransitionMatrix);
} }
/** /**
* Returns a {@link GlFrameProcessor} that crops frames to a rectangle that moves on an ellipse. * Returns a {@link MatrixTransformation} that crops frames to a rectangle that moves on an
* ellipse.
*/ */
public static GlFrameProcessor createDizzyCropFrameProcessor() { public static MatrixTransformation createDizzyCropEffect() {
return new AdvancedFrameProcessor( return MatrixTransformationFactory::calculateDizzyCropMatrix;
/* matrixProvider= */ AdvancedFrameProcessorFactory::calculateDizzyCropMatrix);
} }
/** /**
* Returns a {@link GlFrameProcessor} that rotates a frame in 3D around the y-axis and applies * Returns a {@link GlMatrixTransformation} that rotates a frame in 3D around the y-axis and
* perspective projection to 2D. * applies perspective projection to 2D.
*/ */
public static GlFrameProcessor createSpin3dFrameProcessor() { public static GlMatrixTransformation createSpin3dEffect() {
return new AdvancedFrameProcessor( return MatrixTransformationFactory::calculate3dSpinMatrix;
/* matrixProvider= */ AdvancedFrameProcessorFactory::calculate3dSpinMatrix);
} }
private static final float ZOOM_DURATION_SECONDS = 2f; private static final float ZOOM_DURATION_SECONDS = 2f;
......
...@@ -16,38 +16,29 @@ ...@@ -16,38 +16,29 @@
package androidx.media3.demo.transformer; package androidx.media3.demo.transformer;
import static androidx.media3.common.util.Assertions.checkArgument; import static androidx.media3.common.util.Assertions.checkArgument;
import static androidx.media3.common.util.Assertions.checkStateNotNull;
import android.content.Context; import android.content.Context;
import android.opengl.GLES20; import android.opengl.GLES20;
import android.util.Size; import android.util.Size;
import androidx.media3.common.util.GlProgram; import androidx.media3.common.util.GlProgram;
import androidx.media3.common.util.GlUtil; import androidx.media3.common.util.GlUtil;
import androidx.media3.transformer.GlFrameProcessor; import androidx.media3.transformer.FrameProcessingException;
import androidx.media3.transformer.SingleFrameGlTextureProcessor;
import java.io.IOException; import java.io.IOException;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
/** /**
* A {@link GlFrameProcessor} that periodically dims the frames such that pixels are darker the * A {@link SingleFrameGlTextureProcessor} that periodically dims the frames such that pixels are
* further they are away from the frame center. * darker the further they are away from the frame center.
*/ */
/* package */ final class PeriodicVignetteFrameProcessor implements GlFrameProcessor { /* package */ final class PeriodicVignetteProcessor extends SingleFrameGlTextureProcessor {
static {
GlUtil.glAssertionsEnabled = true;
}
private static final String VERTEX_SHADER_PATH = "vertex_shader_copy_es2.glsl"; 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 String FRAGMENT_SHADER_PATH = "fragment_shader_vignette_es2.glsl";
private static final float DIMMING_PERIOD_US = 5_600_000f; private static final float DIMMING_PERIOD_US = 5_600_000f;
private float centerX; private final GlProgram glProgram;
private float centerY; private final float minInnerRadius;
private float minInnerRadius; private final float deltaInnerRadius;
private float deltaInnerRadius;
private float outerRadius;
private @MonotonicNonNull Size outputSize;
private @MonotonicNonNull GlProgram glProgram;
/** /**
* Creates a new instance. * Creates a new instance.
...@@ -66,53 +57,65 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; ...@@ -66,53 +57,65 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
* @param minInnerRadius The lower bound of the radius that is unaffected by 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 maxInnerRadius The upper bound of the radius that is unaffected by the effect.
* @param outerRadius The radius after which all pixels are black. * @param outerRadius The radius after which all pixels are black.
* @throws FrameProcessingException If a problem occurs while reading shader files.
*/ */
public PeriodicVignetteFrameProcessor( public PeriodicVignetteProcessor(
float centerX, float centerY, float minInnerRadius, float maxInnerRadius, float outerRadius) { Context context,
float centerX,
float centerY,
float minInnerRadius,
float maxInnerRadius,
float outerRadius)
throws FrameProcessingException {
checkArgument(minInnerRadius <= maxInnerRadius); checkArgument(minInnerRadius <= maxInnerRadius);
checkArgument(maxInnerRadius <= outerRadius); checkArgument(maxInnerRadius <= outerRadius);
this.centerX = centerX;
this.centerY = centerY;
this.minInnerRadius = minInnerRadius; this.minInnerRadius = minInnerRadius;
this.deltaInnerRadius = maxInnerRadius - minInnerRadius; this.deltaInnerRadius = maxInnerRadius - minInnerRadius;
this.outerRadius = outerRadius; try {
}
@Override
public void initialize(Context context, int inputTexId, int inputWidth, int inputHeight)
throws IOException {
outputSize = new Size(inputWidth, inputHeight);
glProgram = new GlProgram(context, VERTEX_SHADER_PATH, FRAGMENT_SHADER_PATH); glProgram = new GlProgram(context, VERTEX_SHADER_PATH, FRAGMENT_SHADER_PATH);
glProgram.setSamplerTexIdUniform("uTexSampler", inputTexId, /* texUnitIndex= */ 0); } catch (IOException | GlUtil.GlException e) {
throw new FrameProcessingException(e);
}
glProgram.setFloatsUniform("uCenter", new float[] {centerX, centerY}); glProgram.setFloatsUniform("uCenter", new float[] {centerX, centerY});
glProgram.setFloatsUniform("uOuterRadius", new float[] {outerRadius}); glProgram.setFloatsUniform("uOuterRadius", new float[] {outerRadius});
// Draw the frame on the entire normalized device coordinate space, from -1 to 1, for x and y. // Draw the frame on the entire normalized device coordinate space, from -1 to 1, for x and y.
glProgram.setBufferAttribute( glProgram.setBufferAttribute(
"aFramePosition", GlUtil.getNormalizedCoordinateBounds(), GlUtil.RECTANGLE_VERTICES_COUNT); "aFramePosition",
glProgram.setBufferAttribute( GlUtil.getNormalizedCoordinateBounds(),
"aTexSamplingCoord", GlUtil.getTextureCoordinateBounds(), GlUtil.RECTANGLE_VERTICES_COUNT); GlUtil.HOMOGENEOUS_COORDINATE_VECTOR_SIZE);
} }
@Override @Override
public Size getOutputSize() { public Size configure(int inputWidth, int inputHeight) {
return checkStateNotNull(outputSize); return new Size(inputWidth, inputHeight);
} }
@Override @Override
public void drawFrame(long presentationTimeUs) { public void drawFrame(int inputTexId, long presentationTimeUs) throws FrameProcessingException {
checkStateNotNull(glProgram).use(); try {
glProgram.use();
glProgram.setSamplerTexIdUniform("uTexSampler", inputTexId, /* texUnitIndex= */ 0);
double theta = presentationTimeUs * 2 * Math.PI / DIMMING_PERIOD_US; double theta = presentationTimeUs * 2 * Math.PI / DIMMING_PERIOD_US;
float innerRadius = minInnerRadius + deltaInnerRadius * (0.5f - 0.5f * (float) Math.cos(theta)); float innerRadius =
minInnerRadius + deltaInnerRadius * (0.5f - 0.5f * (float) Math.cos(theta));
glProgram.setFloatsUniform("uInnerRadius", new float[] {innerRadius}); glProgram.setFloatsUniform("uInnerRadius", new float[] {innerRadius});
glProgram.bindAttributesAndUniforms(); glProgram.bindAttributesAndUniforms();
// The four-vertex triangle strip forms a quad. // The four-vertex triangle strip forms a quad.
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, /* first= */ 0, /* count= */ 4); GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, /* first= */ 0, /* count= */ 4);
} catch (GlUtil.GlException e) {
throw new FrameProcessingException(e, presentationTimeUs);
}
} }
@Override @Override
public void release() { public void release() throws FrameProcessingException {
super.release();
if (glProgram != null) { if (glProgram != null) {
try {
glProgram.delete(); glProgram.delete();
} catch (GlUtil.GlException e) {
throw new FrameProcessingException(e);
}
} }
} }
} }
...@@ -19,6 +19,7 @@ import static android.Manifest.permission.READ_EXTERNAL_STORAGE; ...@@ -19,6 +19,7 @@ import static android.Manifest.permission.READ_EXTERNAL_STORAGE;
import static androidx.media3.common.util.Assertions.checkNotNull; import static androidx.media3.common.util.Assertions.checkNotNull;
import android.app.Activity; import android.app.Activity;
import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.net.Uri; import android.net.Uri;
...@@ -31,6 +32,7 @@ import android.view.ViewGroup; ...@@ -31,6 +32,7 @@ import android.view.ViewGroup;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.app.AppCompatActivity;
import androidx.media3.common.C; import androidx.media3.common.C;
import androidx.media3.common.MediaItem; import androidx.media3.common.MediaItem;
...@@ -40,8 +42,9 @@ import androidx.media3.exoplayer.ExoPlayer; ...@@ -40,8 +42,9 @@ import androidx.media3.exoplayer.ExoPlayer;
import androidx.media3.exoplayer.util.DebugTextViewHelper; import androidx.media3.exoplayer.util.DebugTextViewHelper;
import androidx.media3.transformer.DefaultEncoderFactory; import androidx.media3.transformer.DefaultEncoderFactory;
import androidx.media3.transformer.EncoderSelector; import androidx.media3.transformer.EncoderSelector;
import androidx.media3.transformer.GlFrameProcessor; import androidx.media3.transformer.GlEffect;
import androidx.media3.transformer.ProgressHolder; import androidx.media3.transformer.ProgressHolder;
import androidx.media3.transformer.SingleFrameGlTextureProcessor;
import androidx.media3.transformer.TransformationException; import androidx.media3.transformer.TransformationException;
import androidx.media3.transformer.TransformationRequest; import androidx.media3.transformer.TransformationRequest;
import androidx.media3.transformer.TransformationResult; import androidx.media3.transformer.TransformationResult;
...@@ -54,6 +57,7 @@ import com.google.common.base.Ticker; ...@@ -54,6 +57,7 @@ import com.google.common.base.Ticker;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.lang.reflect.Constructor;
import java.util.concurrent.CountDownLatch; import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull; import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
...@@ -149,9 +153,10 @@ public final class TransformerActivity extends AppCompatActivity { ...@@ -149,9 +153,10 @@ public final class TransformerActivity extends AppCompatActivity {
externalCacheFile = createExternalCacheFile("transformer-output.mp4"); externalCacheFile = createExternalCacheFile("transformer-output.mp4");
String filePath = externalCacheFile.getAbsolutePath(); String filePath = externalCacheFile.getAbsolutePath();
@Nullable Bundle bundle = intent.getExtras(); @Nullable Bundle bundle = intent.getExtras();
MediaItem mediaItem = createMediaItem(bundle, uri);
Transformer transformer = createTransformer(bundle, filePath); Transformer transformer = createTransformer(bundle, filePath);
transformationStopwatch.start(); transformationStopwatch.start();
transformer.startTransformation(MediaItem.fromUri(uri), filePath); transformer.startTransformation(mediaItem, filePath);
this.transformer = transformer; this.transformer = transformer;
} catch (IOException e) { } catch (IOException e) {
throw new IllegalStateException(e); throw new IllegalStateException(e);
...@@ -178,6 +183,24 @@ public final class TransformerActivity extends AppCompatActivity { ...@@ -178,6 +183,24 @@ public final class TransformerActivity extends AppCompatActivity {
}); });
} }
private MediaItem createMediaItem(@Nullable Bundle bundle, Uri uri) {
MediaItem.Builder mediaItemBuilder = new MediaItem.Builder().setUri(uri);
if (bundle != null) {
long trimStartMs =
bundle.getLong(ConfigurationActivity.TRIM_START_MS, /* defaultValue= */ C.TIME_UNSET);
long trimEndMs =
bundle.getLong(ConfigurationActivity.TRIM_END_MS, /* defaultValue= */ C.TIME_UNSET);
if (trimStartMs != C.TIME_UNSET && trimEndMs != C.TIME_UNSET) {
mediaItemBuilder.setClippingConfiguration(
new MediaItem.ClippingConfiguration.Builder()
.setStartPositionMs(trimStartMs)
.setEndPositionMs(trimEndMs)
.build());
}
}
return mediaItemBuilder.build();
}
// Create a cache file, resetting it if it already exists. // Create a cache file, resetting it if it already exists.
private File createExternalCacheFile(String fileName) throws IOException { private File createExternalCacheFile(String fileName) throws IOException {
File file = new File(getExternalCacheDir(), fileName); File file = new File(getExternalCacheDir(), fileName);
...@@ -237,20 +260,46 @@ public final class TransformerActivity extends AppCompatActivity { ...@@ -237,20 +260,46 @@ public final class TransformerActivity extends AppCompatActivity {
.setRemoveVideo(bundle.getBoolean(ConfigurationActivity.SHOULD_REMOVE_VIDEO)) .setRemoveVideo(bundle.getBoolean(ConfigurationActivity.SHOULD_REMOVE_VIDEO))
.setEncoderFactory( .setEncoderFactory(
new DefaultEncoderFactory( new DefaultEncoderFactory(
/* context= */ this,
EncoderSelector.DEFAULT, EncoderSelector.DEFAULT,
/* enableFallback= */ bundle.getBoolean(ConfigurationActivity.ENABLE_FALLBACK))); /* enableFallback= */ bundle.getBoolean(ConfigurationActivity.ENABLE_FALLBACK)));
ImmutableList.Builder<GlFrameProcessor> frameProcessors = new ImmutableList.Builder<>(); ImmutableList.Builder<GlEffect> effects = new ImmutableList.Builder<>();
@Nullable @Nullable
boolean[] selectedFrameProcessors = boolean[] selectedEffects =
bundle.getBooleanArray(ConfigurationActivity.DEMO_FRAME_PROCESSORS_SELECTIONS); bundle.getBooleanArray(ConfigurationActivity.DEMO_EFFECTS_SELECTIONS);
if (selectedFrameProcessors != null) { if (selectedEffects != null) {
if (selectedFrameProcessors[0]) { if (selectedEffects[0]) {
frameProcessors.add(AdvancedFrameProcessorFactory.createDizzyCropFrameProcessor()); effects.add(MatrixTransformationFactory.createDizzyCropEffect());
} }
if (selectedFrameProcessors[1]) { if (selectedEffects[1]) {
frameProcessors.add( try {
new PeriodicVignetteFrameProcessor( Class<?> clazz = Class.forName("androidx.media3.demo.transformer.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_X),
bundle.getFloat(ConfigurationActivity.PERIODIC_VIGNETTE_CENTER_Y), bundle.getFloat(ConfigurationActivity.PERIODIC_VIGNETTE_CENTER_Y),
/* minInnerRadius= */ bundle.getFloat( /* minInnerRadius= */ bundle.getFloat(
...@@ -259,16 +308,16 @@ public final class TransformerActivity extends AppCompatActivity { ...@@ -259,16 +308,16 @@ public final class TransformerActivity extends AppCompatActivity {
ConfigurationActivity.PERIODIC_VIGNETTE_OUTER_RADIUS), ConfigurationActivity.PERIODIC_VIGNETTE_OUTER_RADIUS),
bundle.getFloat(ConfigurationActivity.PERIODIC_VIGNETTE_OUTER_RADIUS))); bundle.getFloat(ConfigurationActivity.PERIODIC_VIGNETTE_OUTER_RADIUS)));
} }
if (selectedFrameProcessors[2]) { if (selectedEffects[3]) {
frameProcessors.add(AdvancedFrameProcessorFactory.createSpin3dFrameProcessor()); effects.add(MatrixTransformationFactory.createSpin3dEffect());
} }
if (selectedFrameProcessors[3]) { if (selectedEffects[4]) {
frameProcessors.add(new BitmapOverlayFrameProcessor()); effects.add(BitmapOverlayProcessor::new);
} }
if (selectedFrameProcessors[4]) { if (selectedEffects[5]) {
frameProcessors.add(AdvancedFrameProcessorFactory.createZoomInTransitionFrameProcessor()); effects.add(MatrixTransformationFactory.createZoomInTransition());
} }
transformerBuilder.setFrameProcessors(frameProcessors.build()); transformerBuilder.setVideoFrameEffects(effects.build());
} }
} }
return transformerBuilder return transformerBuilder
...@@ -362,6 +411,10 @@ public final class TransformerActivity extends AppCompatActivity { ...@@ -362,6 +411,10 @@ public final class TransformerActivity extends AppCompatActivity {
} }
} }
private void showToast(@StringRes int messageResource) {
Toast.makeText(getApplicationContext(), getString(messageResource), Toast.LENGTH_LONG).show();
}
private final class DemoDebugViewProvider implements Transformer.DebugViewProvider { private final class DemoDebugViewProvider implements Transformer.DebugViewProvider {
@Nullable @Nullable
......
...@@ -64,7 +64,7 @@ ...@@ -64,7 +64,7 @@
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/selected_file_text_view" app:layout_constraintTop_toBottomOf="@+id/selected_file_text_view"
app:layout_constraintBottom_toTopOf="@+id/select_demo_frameprocessors_button"> app:layout_constraintBottom_toTopOf="@+id/select_demo_effects_button">
<TableLayout <TableLayout
android:layout_width="fill_parent" android:layout_width="fill_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
...@@ -163,6 +163,16 @@ ...@@ -163,6 +163,16 @@
android:layout_weight="1" android:layout_weight="1"
android:gravity="center_vertical" > android:gravity="center_vertical" >
<TextView <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" /> android:text="@string/enable_fallback" />
<CheckBox <CheckBox
android:id="@+id/enable_fallback_checkbox" android:id="@+id/enable_fallback_checkbox"
...@@ -192,13 +202,13 @@ ...@@ -192,13 +202,13 @@
</TableLayout> </TableLayout>
</androidx.core.widget.NestedScrollView> </androidx.core.widget.NestedScrollView>
<Button <Button
android:id="@+id/select_demo_frameprocessors_button" android:id="@+id/select_demo_effects_button"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="32dp" android:layout_marginTop="32dp"
android:layout_marginStart="32dp" android:layout_marginStart="32dp"
android:layout_marginEnd="32dp" android:layout_marginEnd="32dp"
android:text="@string/select_demo_frameprocessors" android:text="@string/select_demo_effects"
app:layout_constraintBottom_toTopOf="@+id/transform_button" app:layout_constraintBottom_toTopOf="@+id/transform_button"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" /> app:layout_constraintStart_toStartOf="parent" />
......
<?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>
...@@ -27,10 +27,12 @@ ...@@ -27,10 +27,12 @@
<string name="scale" translatable="false">Scale video</string> <string name="scale" translatable="false">Scale video</string>
<string name="rotate" translatable="false">Rotate video (degrees)</string> <string name="rotate" translatable="false">Rotate video (degrees)</string>
<string name="enable_fallback" translatable="false">Enable fallback</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="request_sdr_tone_mapping" translatable="false">Request SDR tone-mapping (API 31+)</string>
<string name="hdr_editing" translatable="false">[Experimental] HDR editing</string> <string name="hdr_editing" translatable="false">[Experimental] HDR editing</string>
<string name="select_demo_frameprocessors" translatable="false">Add demo effects</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="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="transform" translatable="false">Transform</string>
<string name="debug_preview" translatable="false">Debug preview:</string> <string name="debug_preview" translatable="false">Debug preview:</string>
<string name="debug_preview_not_available" translatable="false">No debug preview available.</string> <string name="debug_preview_not_available" translatable="false">No debug preview available.</string>
...@@ -41,4 +43,5 @@ ...@@ -41,4 +43,5 @@
<string name="center_x">Center X</string> <string name="center_x">Center X</string>
<string name="center_y">Center Y</string> <string name="center_y">Center Y</string>
<string name="radius_range">Radius range</string> <string name="radius_range">Radius range</string>
<string name="trim_range">Bounds in seconds</string>
</resources> </resources>
<?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="androidx.media3.demo.transformer">
<uses-sdk />
</manifest>
# 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"
}
/*
* 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 androidx.media3.demo.transformer;
import static androidx.media3.common.util.Assertions.checkState;
import static androidx.media3.common.util.Assertions.checkStateNotNull;
import android.content.Context;
import android.opengl.EGL14;
import android.opengl.GLES20;
import android.util.Size;
import androidx.media3.common.util.ConditionVariable;
import androidx.media3.common.util.GlProgram;
import androidx.media3.common.util.GlUtil;
import androidx.media3.common.util.LibraryLoader;
import androidx.media3.transformer.FrameProcessingException;
import androidx.media3.transformer.SingleFrameGlTextureProcessor;
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 extends 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 FrameProcessingException If a problem occurs while reading shader files or initializing
* MediaPipe resources.
*/
public MediaPipeProcessor(
Context context, String graphName, String inputStreamName, String outputStreamName)
throws FrameProcessingException {
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();
});
try {
glProgram = new GlProgram(context, COPY_VERTEX_SHADER_NAME, COPY_FRAGMENT_SHADER_NAME);
} catch (IOException | GlUtil.GlException e) {
throw new FrameProcessingException(e);
}
}
@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, presentationTimeUs);
} finally {
checkStateNotNull(outputFrame).release();
}
}
@Override
public void release() throws FrameProcessingException {
super.release();
checkStateNotNull(frameProcessor).close();
}
}
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-all.zip distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-all.zip
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
...@@ -48,9 +48,21 @@ class CombinedJavadocPlugin implements Plugin<Project> { ...@@ -48,9 +48,21 @@ class CombinedJavadocPlugin implements Plugin<Project> {
libraryModule.android.libraryVariants.all { variant -> libraryModule.android.libraryVariants.all { variant ->
def name = variant.buildType.name def name = variant.buildType.name
if (name == "release") { if (name == "release") {
// Works around b/234569640 that causes different versions of the androidx.media
// jar to be on the classpath.
def allJarFiles = []
allJarFiles.addAll(variant.javaCompileProvider.get().classpath.files)
def filteredJarFiles = allJarFiles.findAll { file ->
if (file ==~ /.*media-.\..\..-api.jar$/
&& !file.path.endsWith(
"media-" + project.ext.androidxMediaVersion + "-api.jar")) {
return false;
}
return true;
}
classpath += classpath +=
libraryModule.project.files( libraryModule.project.files(
variant.javaCompileProvider.get().classpath.files, filteredJarFiles,
libraryModule.project.android.getBootClasspath()) libraryModule.project.android.getBootClasspath())
} }
} }
......
...@@ -42,7 +42,7 @@ import androidx.media3.common.TrackGroup; ...@@ -42,7 +42,7 @@ import androidx.media3.common.TrackGroup;
import androidx.media3.common.TrackSelectionParameters; import androidx.media3.common.TrackSelectionParameters;
import androidx.media3.common.Tracks; import androidx.media3.common.Tracks;
import androidx.media3.common.VideoSize; import androidx.media3.common.VideoSize;
import androidx.media3.common.text.Cue; import androidx.media3.common.text.CueGroup;
import androidx.media3.common.util.Assertions; import androidx.media3.common.util.Assertions;
import androidx.media3.common.util.Clock; import androidx.media3.common.util.Clock;
import androidx.media3.common.util.ListenerSet; import androidx.media3.common.util.ListenerSet;
...@@ -102,7 +102,8 @@ public final class CastPlayer extends BasePlayer { ...@@ -102,7 +102,8 @@ public final class CastPlayer extends BasePlayer {
COMMAND_GET_MEDIA_ITEMS_METADATA, COMMAND_GET_MEDIA_ITEMS_METADATA,
COMMAND_SET_MEDIA_ITEMS_METADATA, COMMAND_SET_MEDIA_ITEMS_METADATA,
COMMAND_CHANGE_MEDIA_ITEMS, COMMAND_CHANGE_MEDIA_ITEMS,
COMMAND_GET_TRACKS) COMMAND_GET_TRACKS,
COMMAND_SET_MEDIA_ITEM)
.build(); .build();
public static final float MIN_SPEED_SUPPORTED = 0.5f; public static final float MIN_SPEED_SUPPORTED = 0.5f;
...@@ -458,6 +459,11 @@ public final class CastPlayer extends BasePlayer { ...@@ -458,6 +459,11 @@ public final class CastPlayer extends BasePlayer {
stop(/* reset= */ false); stop(/* reset= */ false);
} }
/**
* @deprecated Use {@link #stop()} and {@link #clearMediaItems()} (if {@code reset} is true) or
* just {@link #stop()} (if {@code reset} is false). Any player error will be cleared when
* {@link #prepare() re-preparing} the player.
*/
@Deprecated @Deprecated
@Override @Override
public void stop(boolean reset) { public void stop(boolean reset) {
...@@ -705,10 +711,10 @@ public final class CastPlayer extends BasePlayer { ...@@ -705,10 +711,10 @@ public final class CastPlayer extends BasePlayer {
return VideoSize.UNKNOWN; return VideoSize.UNKNOWN;
} }
/** This method is not supported and returns an empty list. */ /** This method is not supported and returns an empty {@link CueGroup}. */
@Override @Override
public ImmutableList<Cue> getCurrentCues() { public CueGroup getCurrentCues() {
return ImmutableList.of(); return CueGroup.EMPTY;
} }
/** This method is not supported and always returns {@link DeviceInfo#UNKNOWN}. */ /** This method is not supported and always returns {@link DeviceInfo#UNKNOWN}. */
......
...@@ -36,6 +36,7 @@ import static androidx.media3.common.Player.COMMAND_SEEK_TO_NEXT_MEDIA_ITEM; ...@@ -36,6 +36,7 @@ import static androidx.media3.common.Player.COMMAND_SEEK_TO_NEXT_MEDIA_ITEM;
import static androidx.media3.common.Player.COMMAND_SEEK_TO_PREVIOUS; import static androidx.media3.common.Player.COMMAND_SEEK_TO_PREVIOUS;
import static androidx.media3.common.Player.COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM; import static androidx.media3.common.Player.COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM;
import static androidx.media3.common.Player.COMMAND_SET_DEVICE_VOLUME; import static androidx.media3.common.Player.COMMAND_SET_DEVICE_VOLUME;
import static androidx.media3.common.Player.COMMAND_SET_MEDIA_ITEM;
import static androidx.media3.common.Player.COMMAND_SET_MEDIA_ITEMS_METADATA; import static androidx.media3.common.Player.COMMAND_SET_MEDIA_ITEMS_METADATA;
import static androidx.media3.common.Player.COMMAND_SET_REPEAT_MODE; import static androidx.media3.common.Player.COMMAND_SET_REPEAT_MODE;
import static androidx.media3.common.Player.COMMAND_SET_SHUFFLE_MODE; import static androidx.media3.common.Player.COMMAND_SET_SHUFFLE_MODE;
...@@ -1359,6 +1360,7 @@ public class CastPlayerTest { ...@@ -1359,6 +1360,7 @@ public class CastPlayerTest {
assertThat(castPlayer.isCommandAvailable(COMMAND_GET_MEDIA_ITEMS_METADATA)).isTrue(); assertThat(castPlayer.isCommandAvailable(COMMAND_GET_MEDIA_ITEMS_METADATA)).isTrue();
assertThat(castPlayer.isCommandAvailable(COMMAND_SET_MEDIA_ITEMS_METADATA)).isTrue(); assertThat(castPlayer.isCommandAvailable(COMMAND_SET_MEDIA_ITEMS_METADATA)).isTrue();
assertThat(castPlayer.isCommandAvailable(COMMAND_CHANGE_MEDIA_ITEMS)).isTrue(); assertThat(castPlayer.isCommandAvailable(COMMAND_CHANGE_MEDIA_ITEMS)).isTrue();
assertThat(castPlayer.isCommandAvailable(COMMAND_SET_MEDIA_ITEM)).isTrue();
assertThat(castPlayer.isCommandAvailable(COMMAND_GET_AUDIO_ATTRIBUTES)).isFalse(); assertThat(castPlayer.isCommandAvailable(COMMAND_GET_AUDIO_ATTRIBUTES)).isFalse();
assertThat(castPlayer.isCommandAvailable(COMMAND_GET_VOLUME)).isFalse(); assertThat(castPlayer.isCommandAvailable(COMMAND_GET_VOLUME)).isFalse();
assertThat(castPlayer.isCommandAvailable(COMMAND_GET_DEVICE_VOLUME)).isFalse(); assertThat(castPlayer.isCommandAvailable(COMMAND_GET_DEVICE_VOLUME)).isFalse();
......
...@@ -28,7 +28,6 @@ import java.lang.annotation.Documented; ...@@ -28,7 +28,6 @@ import java.lang.annotation.Documented;
import java.lang.annotation.Retention; import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; import java.lang.annotation.Target;
import java.lang.reflect.Method;
/** /**
* Attributes for audio playback, which configure the underlying platform {@link * Attributes for audio playback, which configure the underlying platform {@link
...@@ -43,10 +42,31 @@ import java.lang.reflect.Method; ...@@ -43,10 +42,31 @@ import java.lang.reflect.Method;
*/ */
public final class AudioAttributes implements Bundleable { public final class AudioAttributes implements Bundleable {
/** A direct wrapper around {@link android.media.AudioAttributes}. */
@RequiresApi(21)
public static final class AudioAttributesV21 {
public final android.media.AudioAttributes audioAttributes;
private AudioAttributesV21(AudioAttributes audioAttributes) {
android.media.AudioAttributes.Builder builder =
new android.media.AudioAttributes.Builder()
.setContentType(audioAttributes.contentType)
.setFlags(audioAttributes.flags)
.setUsage(audioAttributes.usage);
if (Util.SDK_INT >= 29) {
Api29.setAllowedCapturePolicy(builder, audioAttributes.allowedCapturePolicy);
}
if (Util.SDK_INT >= 32) {
Api32.setSpatializationBehavior(builder, audioAttributes.spatializationBehavior);
}
this.audioAttributes = builder.build();
}
}
/** /**
* The default audio attributes, where the content type is {@link C#CONTENT_TYPE_UNKNOWN}, usage * The default audio attributes, where the content type is {@link C#AUDIO_CONTENT_TYPE_UNKNOWN},
* is {@link C#USAGE_MEDIA}, capture policy is {@link C#ALLOW_CAPTURE_BY_ALL} and no flags are * usage is {@link C#USAGE_MEDIA}, capture policy is {@link C#ALLOW_CAPTURE_BY_ALL} and no flags
* set. * are set.
*/ */
public static final AudioAttributes DEFAULT = new Builder().build(); public static final AudioAttributes DEFAULT = new Builder().build();
...@@ -62,11 +82,11 @@ public final class AudioAttributes implements Bundleable { ...@@ -62,11 +82,11 @@ public final class AudioAttributes implements Bundleable {
/** /**
* Creates a new builder for {@link AudioAttributes}. * Creates a new builder for {@link AudioAttributes}.
* *
* <p>By default the content type is {@link C#CONTENT_TYPE_UNKNOWN}, usage is {@link * <p>By default the content type is {@link C#AUDIO_CONTENT_TYPE_UNKNOWN}, usage is {@link
* C#USAGE_MEDIA}, capture policy is {@link C#ALLOW_CAPTURE_BY_ALL} and no flags are set. * C#USAGE_MEDIA}, capture policy is {@link C#ALLOW_CAPTURE_BY_ALL} and no flags are set.
*/ */
public Builder() { public Builder() {
contentType = C.CONTENT_TYPE_UNKNOWN; contentType = C.AUDIO_CONTENT_TYPE_UNKNOWN;
flags = 0; flags = 0;
usage = C.USAGE_MEDIA; usage = C.USAGE_MEDIA;
allowedCapturePolicy = C.ALLOW_CAPTURE_BY_ALL; allowedCapturePolicy = C.ALLOW_CAPTURE_BY_ALL;
...@@ -97,9 +117,7 @@ public final class AudioAttributes implements Bundleable { ...@@ -97,9 +117,7 @@ public final class AudioAttributes implements Bundleable {
return this; return this;
} }
// TODO[b/190759307] Update javadoc to link to AudioAttributes.Builder#setSpatializationBehavior /** See {@link android.media.AudioAttributes.Builder#setSpatializationBehavior(int)}. */
// once compile SDK target is set to 32.
/** See {@code android.media.AudioAttributes.Builder.setSpatializationBehavior(int)}. */
public Builder setSpatializationBehavior(@C.SpatializationBehavior int spatializationBehavior) { public Builder setSpatializationBehavior(@C.SpatializationBehavior int spatializationBehavior) {
this.spatializationBehavior = spatializationBehavior; this.spatializationBehavior = spatializationBehavior;
return this; return this;
...@@ -123,7 +141,7 @@ public final class AudioAttributes implements Bundleable { ...@@ -123,7 +141,7 @@ public final class AudioAttributes implements Bundleable {
/** The {@link C.SpatializationBehavior}. */ /** The {@link C.SpatializationBehavior}. */
public final @C.SpatializationBehavior int spatializationBehavior; public final @C.SpatializationBehavior int spatializationBehavior;
@Nullable private android.media.AudioAttributes audioAttributesV21; @Nullable private AudioAttributesV21 audioAttributesV21;
private AudioAttributes( private AudioAttributes(
@C.AudioContentType int contentType, @C.AudioContentType int contentType,
...@@ -139,25 +157,15 @@ public final class AudioAttributes implements Bundleable { ...@@ -139,25 +157,15 @@ public final class AudioAttributes implements Bundleable {
} }
/** /**
* Returns a {@link android.media.AudioAttributes} from this instance. * Returns a {@link AudioAttributesV21} from this instance.
* *
* <p>Field {@link AudioAttributes#allowedCapturePolicy} is ignored for API levels prior to 29. * <p>Some fields are ignored if the corresponding {@link android.media.AudioAttributes.Builder}
* setter is not available on the current API level.
*/ */
@RequiresApi(21) @RequiresApi(21)
public android.media.AudioAttributes getAudioAttributesV21() { public AudioAttributesV21 getAudioAttributesV21() {
if (audioAttributesV21 == null) { if (audioAttributesV21 == null) {
android.media.AudioAttributes.Builder builder = audioAttributesV21 = new AudioAttributesV21(this);
new android.media.AudioAttributes.Builder()
.setContentType(contentType)
.setFlags(flags)
.setUsage(usage);
if (Util.SDK_INT >= 29) {
Api29.setAllowedCapturePolicy(builder, allowedCapturePolicy);
}
if (Util.SDK_INT >= 32) {
Api32.setSpatializationBehavior(builder, spatializationBehavior);
}
audioAttributesV21 = builder.build();
} }
return audioAttributesV21; return audioAttributesV21;
} }
...@@ -251,8 +259,6 @@ public final class AudioAttributes implements Bundleable { ...@@ -251,8 +259,6 @@ public final class AudioAttributes implements Bundleable {
@RequiresApi(29) @RequiresApi(29)
private static final class Api29 { private static final class Api29 {
private Api29() {}
@DoNotInline @DoNotInline
public static void setAllowedCapturePolicy( public static void setAllowedCapturePolicy(
android.media.AudioAttributes.Builder builder, android.media.AudioAttributes.Builder builder,
...@@ -263,20 +269,11 @@ public final class AudioAttributes implements Bundleable { ...@@ -263,20 +269,11 @@ public final class AudioAttributes implements Bundleable {
@RequiresApi(32) @RequiresApi(32)
private static final class Api32 { private static final class Api32 {
private Api32() {}
@DoNotInline @DoNotInline
public static void setSpatializationBehavior( public static void setSpatializationBehavior(
android.media.AudioAttributes.Builder builder, android.media.AudioAttributes.Builder builder,
@C.SpatializationBehavior int spatializationBehavior) { @C.SpatializationBehavior int spatializationBehavior) {
try { builder.setSpatializationBehavior(spatializationBehavior);
// TODO[b/190759307]: Remove reflection once compile SDK target is set to 32.
Method setSpatializationBehavior =
builder.getClass().getMethod("setSpatializationBehavior", Integer.TYPE);
setSpatializationBehavior.invoke(builder, spatializationBehavior);
} catch (Exception e) {
// Do nothing if reflection fails.
}
} }
} }
} }
...@@ -94,7 +94,7 @@ public abstract class BasePlayer implements Player { ...@@ -94,7 +94,7 @@ public abstract class BasePlayer implements Player {
/** /**
* {@inheritDoc} * {@inheritDoc}
* *
* <p>BasePlayer and its descendents will return {@code true}. * <p>BasePlayer and its descendants will return {@code true}.
*/ */
@Override @Override
public final boolean canAdvertiseSession() { public final boolean canAdvertiseSession() {
...@@ -143,12 +143,18 @@ public abstract class BasePlayer implements Player { ...@@ -143,12 +143,18 @@ public abstract class BasePlayer implements Player {
seekToOffset(getSeekForwardIncrement()); seekToOffset(getSeekForwardIncrement());
} }
/**
* @deprecated Use {@link #hasPreviousMediaItem()} instead.
*/
@Deprecated @Deprecated
@Override @Override
public final boolean hasPrevious() { public final boolean hasPrevious() {
return hasPreviousMediaItem(); return hasPreviousMediaItem();
} }
/**
* @deprecated Use {@link #hasPreviousMediaItem()} instead.
*/
@Deprecated @Deprecated
@Override @Override
public final boolean hasPreviousWindow() { public final boolean hasPreviousWindow() {
...@@ -160,12 +166,18 @@ public abstract class BasePlayer implements Player { ...@@ -160,12 +166,18 @@ public abstract class BasePlayer implements Player {
return getPreviousMediaItemIndex() != C.INDEX_UNSET; return getPreviousMediaItemIndex() != C.INDEX_UNSET;
} }
/**
* @deprecated Use {@link #seekToPreviousMediaItem()} instead.
*/
@Deprecated @Deprecated
@Override @Override
public final void previous() { public final void previous() {
seekToPreviousMediaItem(); seekToPreviousMediaItem();
} }
/**
* @deprecated Use {@link #seekToPreviousMediaItem()} instead.
*/
@Deprecated @Deprecated
@Override @Override
public final void seekToPreviousWindow() { public final void seekToPreviousWindow() {
...@@ -198,12 +210,18 @@ public abstract class BasePlayer implements Player { ...@@ -198,12 +210,18 @@ public abstract class BasePlayer implements Player {
} }
} }
/**
* @deprecated Use {@link #hasNextMediaItem()} instead.
*/
@Deprecated @Deprecated
@Override @Override
public final boolean hasNext() { public final boolean hasNext() {
return hasNextMediaItem(); return hasNextMediaItem();
} }
/**
* @deprecated Use {@link #hasNextMediaItem()} instead.
*/
@Deprecated @Deprecated
@Override @Override
public final boolean hasNextWindow() { public final boolean hasNextWindow() {
...@@ -215,12 +233,18 @@ public abstract class BasePlayer implements Player { ...@@ -215,12 +233,18 @@ public abstract class BasePlayer implements Player {
return getNextMediaItemIndex() != C.INDEX_UNSET; return getNextMediaItemIndex() != C.INDEX_UNSET;
} }
/**
* @deprecated Use {@link #seekToNextMediaItem()} instead.
*/
@Deprecated @Deprecated
@Override @Override
public final void next() { public final void next() {
seekToNextMediaItem(); seekToNextMediaItem();
} }
/**
* @deprecated Use {@link #seekToNextMediaItem()} instead.
*/
@Deprecated @Deprecated
@Override @Override
public final void seekToNextWindow() { public final void seekToNextWindow() {
...@@ -253,12 +277,18 @@ public abstract class BasePlayer implements Player { ...@@ -253,12 +277,18 @@ public abstract class BasePlayer implements Player {
setPlaybackParameters(getPlaybackParameters().withSpeed(speed)); setPlaybackParameters(getPlaybackParameters().withSpeed(speed));
} }
/**
* @deprecated Use {@link #getCurrentMediaItemIndex()} instead.
*/
@Deprecated @Deprecated
@Override @Override
public final int getCurrentWindowIndex() { public final int getCurrentWindowIndex() {
return getCurrentMediaItemIndex(); return getCurrentMediaItemIndex();
} }
/**
* @deprecated Use {@link #getNextMediaItemIndex()} instead.
*/
@Deprecated @Deprecated
@Override @Override
public final int getNextWindowIndex() { public final int getNextWindowIndex() {
...@@ -274,6 +304,9 @@ public abstract class BasePlayer implements Player { ...@@ -274,6 +304,9 @@ public abstract class BasePlayer implements Player {
getCurrentMediaItemIndex(), getRepeatModeForNavigation(), getShuffleModeEnabled()); getCurrentMediaItemIndex(), getRepeatModeForNavigation(), getShuffleModeEnabled());
} }
/**
* @deprecated Use {@link #getPreviousMediaItemIndex()} instead.
*/
@Deprecated @Deprecated
@Override @Override
public final int getPreviousWindowIndex() { public final int getPreviousWindowIndex() {
...@@ -326,6 +359,9 @@ public abstract class BasePlayer implements Player { ...@@ -326,6 +359,9 @@ public abstract class BasePlayer implements Player {
: duration == 0 ? 100 : Util.constrainValue((int) ((position * 100) / duration), 0, 100); : duration == 0 ? 100 : Util.constrainValue((int) ((position * 100) / duration), 0, 100);
} }
/**
* @deprecated Use {@link #isCurrentMediaItemDynamic()} instead.
*/
@Deprecated @Deprecated
@Override @Override
public final boolean isCurrentWindowDynamic() { public final boolean isCurrentWindowDynamic() {
...@@ -338,6 +374,9 @@ public abstract class BasePlayer implements Player { ...@@ -338,6 +374,9 @@ public abstract class BasePlayer implements Player {
return !timeline.isEmpty() && timeline.getWindow(getCurrentMediaItemIndex(), window).isDynamic; return !timeline.isEmpty() && timeline.getWindow(getCurrentMediaItemIndex(), window).isDynamic;
} }
/**
* @deprecated Use {@link #isCurrentMediaItemLive()} instead.
*/
@Deprecated @Deprecated
@Override @Override
public final boolean isCurrentWindowLive() { public final boolean isCurrentWindowLive() {
...@@ -364,6 +403,9 @@ public abstract class BasePlayer implements Player { ...@@ -364,6 +403,9 @@ public abstract class BasePlayer implements Player {
return window.getCurrentUnixTimeMs() - window.windowStartTimeMs - getContentPosition(); return window.getCurrentUnixTimeMs() - window.windowStartTimeMs - getContentPosition();
} }
/**
* @deprecated Use {@link #isCurrentMediaItemSeekable()} instead.
*/
@Deprecated @Deprecated
@Override @Override
public final boolean isCurrentWindowSeekable() { public final boolean isCurrentWindowSeekable() {
......
...@@ -333,12 +333,16 @@ public final class C { ...@@ -333,12 +333,16 @@ public final class C {
@IntDef({SPATIALIZATION_BEHAVIOR_AUTO, SPATIALIZATION_BEHAVIOR_NEVER}) @IntDef({SPATIALIZATION_BEHAVIOR_AUTO, SPATIALIZATION_BEHAVIOR_NEVER})
public @interface SpatializationBehavior {} public @interface SpatializationBehavior {}
// TODO[b/190759307]: Update constant values and javadoc to use SDK once compile SDK target is set /**
// to 32. * @see AudioAttributes#SPATIALIZATION_BEHAVIOR_AUTO
/** See AudioAttributes#SPATIALIZATION_BEHAVIOR_AUTO */ */
public static final int SPATIALIZATION_BEHAVIOR_AUTO = 0; public static final int SPATIALIZATION_BEHAVIOR_AUTO =
/** See AudioAttributes#SPATIALIZATION_BEHAVIOR_NEVER */ AudioAttributes.SPATIALIZATION_BEHAVIOR_AUTO;
public static final int SPATIALIZATION_BEHAVIOR_NEVER = 1; /**
* @see AudioAttributes#SPATIALIZATION_BEHAVIOR_NEVER
*/
public static final int SPATIALIZATION_BEHAVIOR_NEVER =
AudioAttributes.SPATIALIZATION_BEHAVIOR_NEVER;
/** /**
* Stream types for an {@link android.media.AudioTrack}. One of {@link #STREAM_TYPE_ALARM}, {@link * Stream types for an {@link android.media.AudioTrack}. One of {@link #STREAM_TYPE_ALARM}, {@link
...@@ -396,9 +400,15 @@ public final class C { ...@@ -396,9 +400,15 @@ public final class C {
@UnstableApi public static final int STREAM_TYPE_DEFAULT = STREAM_TYPE_MUSIC; @UnstableApi public static final int STREAM_TYPE_DEFAULT = STREAM_TYPE_MUSIC;
/** /**
* Content types for audio attributes. One of {@link #CONTENT_TYPE_MOVIE}, {@link * Content types for audio attributes. One of:
* #CONTENT_TYPE_MUSIC}, {@link #CONTENT_TYPE_SONIFICATION}, {@link #CONTENT_TYPE_SPEECH} or *
* {@link #CONTENT_TYPE_UNKNOWN}. * <ul>
* <li>{@link #AUDIO_CONTENT_TYPE_MOVIE}
* <li>{@link #AUDIO_CONTENT_TYPE_MUSIC}
* <li>{@link #AUDIO_CONTENT_TYPE_SONIFICATION}
* <li>{@link #AUDIO_CONTENT_TYPE_SPEECH}
* <li>{@link #AUDIO_CONTENT_TYPE_UNKNOWN}
* </ul>
*/ */
// @Target list includes both 'default' targets and TYPE_USE, to ensure backwards compatibility // @Target list includes both 'default' targets and TYPE_USE, to ensure backwards compatibility
// with Kotlin usages from before TYPE_USE was added. // with Kotlin usages from before TYPE_USE was added.
...@@ -406,34 +416,46 @@ public final class C { ...@@ -406,34 +416,46 @@ public final class C {
@Retention(RetentionPolicy.SOURCE) @Retention(RetentionPolicy.SOURCE)
@Target({FIELD, METHOD, PARAMETER, LOCAL_VARIABLE, TYPE_USE}) @Target({FIELD, METHOD, PARAMETER, LOCAL_VARIABLE, TYPE_USE})
@IntDef({ @IntDef({
CONTENT_TYPE_MOVIE, AUDIO_CONTENT_TYPE_MOVIE,
CONTENT_TYPE_MUSIC, AUDIO_CONTENT_TYPE_MUSIC,
CONTENT_TYPE_SONIFICATION, AUDIO_CONTENT_TYPE_SONIFICATION,
CONTENT_TYPE_SPEECH, AUDIO_CONTENT_TYPE_SPEECH,
CONTENT_TYPE_UNKNOWN AUDIO_CONTENT_TYPE_UNKNOWN
}) })
public @interface AudioContentType {} public @interface AudioContentType {}
/** See {@link AudioAttributes#CONTENT_TYPE_MOVIE}. */
public static final int AUDIO_CONTENT_TYPE_MOVIE = AudioAttributes.CONTENT_TYPE_MOVIE;
/** /**
* @see android.media.AudioAttributes#CONTENT_TYPE_MOVIE * @deprecated Use {@link #AUDIO_CONTENT_TYPE_MOVIE} instead.
*/ */
public static final int CONTENT_TYPE_MOVIE = android.media.AudioAttributes.CONTENT_TYPE_MOVIE; @UnstableApi @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;
/** /**
* @see android.media.AudioAttributes#CONTENT_TYPE_MUSIC * @deprecated Use {@link #AUDIO_CONTENT_TYPE_MUSIC} instead.
*/ */
public static final int CONTENT_TYPE_MUSIC = android.media.AudioAttributes.CONTENT_TYPE_MUSIC; @UnstableApi @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;
/** /**
* @see android.media.AudioAttributes#CONTENT_TYPE_SONIFICATION * @deprecated Use {@link #AUDIO_CONTENT_TYPE_SONIFICATION} instead.
*/ */
public static final int CONTENT_TYPE_SONIFICATION = @UnstableApi @Deprecated
android.media.AudioAttributes.CONTENT_TYPE_SONIFICATION; 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;
/** /**
* @see android.media.AudioAttributes#CONTENT_TYPE_SPEECH * @deprecated Use {@link #AUDIO_CONTENT_TYPE_SPEECH} instead.
*/ */
public static final int CONTENT_TYPE_SPEECH = android.media.AudioAttributes.CONTENT_TYPE_SPEECH; @UnstableApi @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;
/** /**
* @see android.media.AudioAttributes#CONTENT_TYPE_UNKNOWN * @deprecated Use {@link #AUDIO_CONTENT_TYPE_UNKNOWN} instead.
*/ */
public static final int CONTENT_TYPE_UNKNOWN = android.media.AudioAttributes.CONTENT_TYPE_UNKNOWN; @UnstableApi @Deprecated
public static final int CONTENT_TYPE_UNKNOWN = AUDIO_CONTENT_TYPE_UNKNOWN;
/** /**
* Flags for audio attributes. Possible flag value is {@link #FLAG_AUDIBILITY_ENFORCED}. * Flags for audio attributes. Possible flag value is {@link #FLAG_AUDIBILITY_ENFORCED}.
...@@ -725,30 +747,59 @@ public final class C { ...@@ -725,30 +747,59 @@ public final class C {
public static final String LANGUAGE_UNDETERMINED = "und"; public static final String LANGUAGE_UNDETERMINED = "und";
/** /**
* Represents a streaming or other media type. One of {@link #TYPE_DASH}, {@link #TYPE_SS}, {@link * Represents a streaming or other media type. One of:
* #TYPE_HLS}, {@link #TYPE_RTSP} or {@link #TYPE_OTHER}. *
* <ul>
* <li>{@link #CONTENT_TYPE_DASH}
* <li>{@link #CONTENT_TYPE_SS}
* <li>{@link #CONTENT_TYPE_HLS}
* <li>{@link #CONTENT_TYPE_RTSP}
* <li>{@link #CONTENT_TYPE_OTHER}
* </ul>
*/ */
// @Target list includes both 'default' targets and TYPE_USE, to ensure backwards compatibility // @Target list includes both 'default' targets and TYPE_USE, to ensure backwards compatibility
// with Kotlin usages from before TYPE_USE was added. // with Kotlin usages from before TYPE_USE was added.
@UnstableApi
@Documented @Documented
@Retention(RetentionPolicy.SOURCE) @Retention(RetentionPolicy.SOURCE)
@Target({FIELD, METHOD, PARAMETER, LOCAL_VARIABLE, TYPE_USE}) @Target({FIELD, METHOD, PARAMETER, LOCAL_VARIABLE, TYPE_USE})
@IntDef({TYPE_DASH, TYPE_SS, TYPE_HLS, TYPE_RTSP, TYPE_OTHER}) @IntDef({
CONTENT_TYPE_DASH,
CONTENT_TYPE_SS,
CONTENT_TYPE_HLS,
CONTENT_TYPE_RTSP,
CONTENT_TYPE_OTHER
})
public @interface ContentType {} public @interface ContentType {}
/** Value returned by {@link Util#inferContentType} for DASH manifests. */ /** Value representing a DASH manifest. */
@UnstableApi public static final int TYPE_DASH = 0; public static final int CONTENT_TYPE_DASH = 0;
/** Value returned by {@link Util#inferContentType} for Smooth Streaming manifests. */ /**
@UnstableApi public static final int TYPE_SS = 1; * @deprecated Use {@link #CONTENT_TYPE_DASH} instead.
/** Value returned by {@link Util#inferContentType} for HLS manifests. */ */
@UnstableApi public static final int TYPE_HLS = 2; @Deprecated @UnstableApi public static final int TYPE_DASH = CONTENT_TYPE_DASH;
/** Value returned by {@link Util#inferContentType} for RTSP. */ /** Value representing a Smooth Streaming manifest. */
@UnstableApi public static final int TYPE_RTSP = 3; public static final int CONTENT_TYPE_SS = 1;
/** /**
* Value returned by {@link Util#inferContentType} for files other than DASH, HLS or Smooth * @deprecated Use {@link #CONTENT_TYPE_SS} instead.
* Streaming manifests, or RTSP URIs. */
*/ @Deprecated @UnstableApi public static final int TYPE_SS = CONTENT_TYPE_SS;
@UnstableApi public static final int TYPE_OTHER = 4; /** Value representing an HLS manifest. */
public static final int CONTENT_TYPE_HLS = 2;
/**
* @deprecated Use {@link #CONTENT_TYPE_HLS} instead.
*/
@Deprecated @UnstableApi 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 @UnstableApi 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 @UnstableApi public static final int TYPE_OTHER = CONTENT_TYPE_OTHER;
/** A return value for methods where the end of an input was encountered. */ /** A return value for methods where the end of an input was encountered. */
@UnstableApi public static final int RESULT_END_OF_INPUT = -1; @UnstableApi public static final int RESULT_END_OF_INPUT = -1;
......
...@@ -16,10 +16,8 @@ ...@@ -16,10 +16,8 @@
package androidx.media3.common; package androidx.media3.common;
import android.util.Pair; import android.util.Pair;
import androidx.media3.common.util.UnstableApi;
/** Converts throwables into error codes and user readable error messages. */ /** Converts throwables into error codes and user readable error messages. */
@UnstableApi
public interface ErrorMessageProvider<T extends Throwable> { public interface ErrorMessageProvider<T extends Throwable> {
/** /**
......
...@@ -37,13 +37,14 @@ public final class FileTypes { ...@@ -37,13 +37,14 @@ public final class FileTypes {
/** /**
* File types. One of {@link #UNKNOWN}, {@link #AC3}, {@link #AC4}, {@link #ADTS}, {@link #AMR}, * File types. One of {@link #UNKNOWN}, {@link #AC3}, {@link #AC4}, {@link #ADTS}, {@link #AMR},
* {@link #FLAC}, {@link #FLV}, {@link #MATROSKA}, {@link #MP3}, {@link #MP4}, {@link #OGG}, * {@link #FLAC}, {@link #FLV}, {@link #MATROSKA}, {@link #MP3}, {@link #MP4}, {@link #OGG},
* {@link #PS}, {@link #TS}, {@link #WAV}, {@link #WEBVTT} and {@link #JPEG}. * {@link #PS}, {@link #TS}, {@link #WAV}, {@link #WEBVTT}, {@link #JPEG} and {@link #MIDI}.
*/ */
@Documented @Documented
@Retention(RetentionPolicy.SOURCE) @Retention(RetentionPolicy.SOURCE)
@Target(TYPE_USE) @Target(TYPE_USE)
@IntDef({ @IntDef({
UNKNOWN, AC3, AC4, ADTS, AMR, FLAC, FLV, MATROSKA, MP3, MP4, OGG, PS, TS, WAV, WEBVTT, JPEG UNKNOWN, AC3, AC4, ADTS, AMR, FLAC, FLV, MATROSKA, MP3, MP4, OGG, PS, TS, WAV, WEBVTT, JPEG,
MIDI
}) })
public @interface Type {} public @interface Type {}
/** Unknown file type. */ /** Unknown file type. */
...@@ -78,6 +79,8 @@ public final class FileTypes { ...@@ -78,6 +79,8 @@ public final class FileTypes {
public static final int WEBVTT = 13; public static final int WEBVTT = 13;
/** File type for the JPEG format. */ /** File type for the JPEG format. */
public static final int JPEG = 14; public static final int JPEG = 14;
/** File type for the MIDI format. */
public static final int MIDI = 15;
@VisibleForTesting /* package */ static final String HEADER_CONTENT_TYPE = "Content-Type"; @VisibleForTesting /* package */ static final String HEADER_CONTENT_TYPE = "Content-Type";
...@@ -89,6 +92,9 @@ public final class FileTypes { ...@@ -89,6 +92,9 @@ public final class FileTypes {
private static final String EXTENSION_AMR = ".amr"; private static final String EXTENSION_AMR = ".amr";
private static final String EXTENSION_FLAC = ".flac"; private static final String EXTENSION_FLAC = ".flac";
private static final String EXTENSION_FLV = ".flv"; private static final String EXTENSION_FLV = ".flv";
private static final String EXTENSION_MID = ".mid";
private static final String EXTENSION_MIDI = ".midi";
private static final String EXTENSION_SMF = ".smf";
private static final String EXTENSION_PREFIX_MK = ".mk"; private static final String EXTENSION_PREFIX_MK = ".mk";
private static final String EXTENSION_WEBM = ".webm"; private static final String EXTENSION_WEBM = ".webm";
private static final String EXTENSION_PREFIX_OG = ".og"; private static final String EXTENSION_PREFIX_OG = ".og";
...@@ -147,6 +153,8 @@ public final class FileTypes { ...@@ -147,6 +153,8 @@ public final class FileTypes {
return FileTypes.FLAC; return FileTypes.FLAC;
case MimeTypes.VIDEO_FLV: case MimeTypes.VIDEO_FLV:
return FileTypes.FLV; return FileTypes.FLV;
case MimeTypes.AUDIO_MIDI:
return FileTypes.MIDI;
case MimeTypes.VIDEO_MATROSKA: case MimeTypes.VIDEO_MATROSKA:
case MimeTypes.AUDIO_MATROSKA: case MimeTypes.AUDIO_MATROSKA:
case MimeTypes.VIDEO_WEBM: case MimeTypes.VIDEO_WEBM:
...@@ -193,6 +201,10 @@ public final class FileTypes { ...@@ -193,6 +201,10 @@ public final class FileTypes {
return FileTypes.FLAC; return FileTypes.FLAC;
} else if (filename.endsWith(EXTENSION_FLV)) { } else if (filename.endsWith(EXTENSION_FLV)) {
return FileTypes.FLV; return FileTypes.FLV;
} else if (filename.endsWith(EXTENSION_MID)
|| filename.endsWith(EXTENSION_MIDI)
|| filename.endsWith(EXTENSION_SMF)) {
return FileTypes.MIDI;
} else if (filename.startsWith( } else if (filename.startsWith(
EXTENSION_PREFIX_MK, EXTENSION_PREFIX_MK,
/* toffset= */ filename.length() - (EXTENSION_PREFIX_MK.length() + 1)) /* toffset= */ filename.length() - (EXTENSION_PREFIX_MK.length() + 1))
......
...@@ -1548,7 +1548,9 @@ public final class Format implements Bundleable { ...@@ -1548,7 +1548,9 @@ public final class Format implements Bundleable {
bundle.putFloat(keyForField(FIELD_PIXEL_WIDTH_HEIGHT_RATIO), pixelWidthHeightRatio); bundle.putFloat(keyForField(FIELD_PIXEL_WIDTH_HEIGHT_RATIO), pixelWidthHeightRatio);
bundle.putByteArray(keyForField(FIELD_PROJECTION_DATA), projectionData); bundle.putByteArray(keyForField(FIELD_PROJECTION_DATA), projectionData);
bundle.putInt(keyForField(FIELD_STEREO_MODE), stereoMode); bundle.putInt(keyForField(FIELD_STEREO_MODE), stereoMode);
bundle.putBundle(keyForField(FIELD_COLOR_INFO), BundleableUtil.toNullableBundle(colorInfo)); if (colorInfo != null) {
bundle.putBundle(keyForField(FIELD_COLOR_INFO), colorInfo.toBundle());
}
// Audio specific. // Audio specific.
bundle.putInt(keyForField(FIELD_CHANNEL_COUNT), channelCount); bundle.putInt(keyForField(FIELD_CHANNEL_COUNT), channelCount);
bundle.putInt(keyForField(FIELD_SAMPLE_RATE), sampleRate); bundle.putInt(keyForField(FIELD_SAMPLE_RATE), sampleRate);
...@@ -1615,11 +1617,13 @@ public final class Format implements Bundleable { ...@@ -1615,11 +1617,13 @@ public final class Format implements Bundleable {
bundle.getFloat( bundle.getFloat(
keyForField(FIELD_PIXEL_WIDTH_HEIGHT_RATIO), DEFAULT.pixelWidthHeightRatio)) keyForField(FIELD_PIXEL_WIDTH_HEIGHT_RATIO), DEFAULT.pixelWidthHeightRatio))
.setProjectionData(bundle.getByteArray(keyForField(FIELD_PROJECTION_DATA))) .setProjectionData(bundle.getByteArray(keyForField(FIELD_PROJECTION_DATA)))
.setStereoMode(bundle.getInt(keyForField(FIELD_STEREO_MODE), DEFAULT.stereoMode)) .setStereoMode(bundle.getInt(keyForField(FIELD_STEREO_MODE), DEFAULT.stereoMode));
.setColorInfo( Bundle colorInfoBundle = bundle.getBundle(keyForField(FIELD_COLOR_INFO));
BundleableUtil.fromNullableBundle( if (colorInfoBundle != null) {
ColorInfo.CREATOR, bundle.getBundle(keyForField(FIELD_COLOR_INFO)))) builder.setColorInfo(ColorInfo.CREATOR.fromBundle(colorInfoBundle));
}
// Audio specific. // Audio specific.
builder
.setChannelCount(bundle.getInt(keyForField(FIELD_CHANNEL_COUNT), DEFAULT.channelCount)) .setChannelCount(bundle.getInt(keyForField(FIELD_CHANNEL_COUNT), DEFAULT.channelCount))
.setSampleRate(bundle.getInt(keyForField(FIELD_SAMPLE_RATE), DEFAULT.sampleRate)) .setSampleRate(bundle.getInt(keyForField(FIELD_SAMPLE_RATE), DEFAULT.sampleRate))
.setPcmEncoding(bundle.getInt(keyForField(FIELD_PCM_ENCODING), DEFAULT.pcmEncoding)) .setPcmEncoding(bundle.getInt(keyForField(FIELD_PCM_ENCODING), DEFAULT.pcmEncoding))
......
...@@ -22,6 +22,7 @@ import android.view.SurfaceView; ...@@ -22,6 +22,7 @@ import android.view.SurfaceView;
import android.view.TextureView; import android.view.TextureView;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.media3.common.text.Cue; import androidx.media3.common.text.Cue;
import androidx.media3.common.text.CueGroup;
import androidx.media3.common.util.UnstableApi; import androidx.media3.common.util.UnstableApi;
import java.util.List; import java.util.List;
...@@ -298,7 +299,11 @@ public class ForwardingPlayer implements Player { ...@@ -298,7 +299,11 @@ public class ForwardingPlayer implements Player {
player.seekForward(); player.seekForward();
} }
/** Calls {@link Player#hasPrevious()} on the delegate and returns the result. */ /**
* Calls {@link Player#hasPrevious()} on the delegate and returns the result.
*
* @deprecated Use {@link #hasPreviousMediaItem()} instead.
*/
@SuppressWarnings("deprecation") // Forwarding to deprecated method @SuppressWarnings("deprecation") // Forwarding to deprecated method
@Deprecated @Deprecated
@Override @Override
...@@ -306,7 +311,11 @@ public class ForwardingPlayer implements Player { ...@@ -306,7 +311,11 @@ public class ForwardingPlayer implements Player {
return player.hasPrevious(); return player.hasPrevious();
} }
/** Calls {@link Player#hasPreviousWindow()} on the delegate and returns the result. */ /**
* Calls {@link Player#hasPreviousWindow()} on the delegate and returns the result.
*
* @deprecated Use {@link #hasPreviousMediaItem()} instead.
*/
@SuppressWarnings("deprecation") // Forwarding to deprecated method @SuppressWarnings("deprecation") // Forwarding to deprecated method
@Deprecated @Deprecated
@Override @Override
...@@ -320,7 +329,11 @@ public class ForwardingPlayer implements Player { ...@@ -320,7 +329,11 @@ public class ForwardingPlayer implements Player {
return player.hasPreviousMediaItem(); return player.hasPreviousMediaItem();
} }
/** Calls {@link Player#previous()} on the delegate. */ /**
* Calls {@link Player#previous()} on the delegate.
*
* @deprecated Use {@link #seekToPreviousMediaItem()} instead.
*/
@SuppressWarnings("deprecation") // Forwarding to deprecated method @SuppressWarnings("deprecation") // Forwarding to deprecated method
@Deprecated @Deprecated
@Override @Override
...@@ -328,7 +341,11 @@ public class ForwardingPlayer implements Player { ...@@ -328,7 +341,11 @@ public class ForwardingPlayer implements Player {
player.previous(); player.previous();
} }
/** Calls {@link Player#seekToPreviousWindow()} on the delegate. */ /**
* Calls {@link Player#seekToPreviousWindow()} on the delegate.
*
* @deprecated Use {@link #seekToPreviousMediaItem()} instead.
*/
@SuppressWarnings("deprecation") // Forwarding to deprecated method @SuppressWarnings("deprecation") // Forwarding to deprecated method
@Deprecated @Deprecated
@Override @Override
...@@ -354,7 +371,11 @@ public class ForwardingPlayer implements Player { ...@@ -354,7 +371,11 @@ public class ForwardingPlayer implements Player {
return player.getMaxSeekToPreviousPosition(); return player.getMaxSeekToPreviousPosition();
} }
/** Calls {@link Player#hasNext()} on the delegate and returns the result. */ /**
* Calls {@link Player#hasNext()} on the delegate and returns the result.
*
* @deprecated Use {@link #hasNextMediaItem()} instead.
*/
@SuppressWarnings("deprecation") // Forwarding to deprecated method @SuppressWarnings("deprecation") // Forwarding to deprecated method
@Deprecated @Deprecated
@Override @Override
...@@ -362,7 +383,11 @@ public class ForwardingPlayer implements Player { ...@@ -362,7 +383,11 @@ public class ForwardingPlayer implements Player {
return player.hasNext(); return player.hasNext();
} }
/** Calls {@link Player#hasNextWindow()} on the delegate and returns the result. */ /**
* Calls {@link Player#hasNextWindow()} on the delegate and returns the result.
*
* @deprecated Use {@link #hasNextMediaItem()} instead.
*/
@SuppressWarnings("deprecation") // Forwarding to deprecated method @SuppressWarnings("deprecation") // Forwarding to deprecated method
@Deprecated @Deprecated
@Override @Override
...@@ -376,7 +401,11 @@ public class ForwardingPlayer implements Player { ...@@ -376,7 +401,11 @@ public class ForwardingPlayer implements Player {
return player.hasNextMediaItem(); return player.hasNextMediaItem();
} }
/** Calls {@link Player#next()} on the delegate. */ /**
* Calls {@link Player#next()} on the delegate.
*
* @deprecated Use {@link #seekToNextMediaItem()} instead.
*/
@SuppressWarnings("deprecation") // Forwarding to deprecated method @SuppressWarnings("deprecation") // Forwarding to deprecated method
@Deprecated @Deprecated
@Override @Override
...@@ -384,7 +413,11 @@ public class ForwardingPlayer implements Player { ...@@ -384,7 +413,11 @@ public class ForwardingPlayer implements Player {
player.next(); player.next();
} }
/** Calls {@link Player#seekToNextWindow()} on the delegate. */ /**
* Calls {@link Player#seekToNextWindow()} on the delegate.
*
* @deprecated Use {@link #seekToNextMediaItem()} instead.
*/
@SuppressWarnings("deprecation") // Forwarding to deprecated method @SuppressWarnings("deprecation") // Forwarding to deprecated method
@Deprecated @Deprecated
@Override @Override
...@@ -428,7 +461,13 @@ public class ForwardingPlayer implements Player { ...@@ -428,7 +461,13 @@ public class ForwardingPlayer implements Player {
player.stop(); player.stop();
} }
/** Calls {@link Player#stop(boolean)} on the delegate. */ /**
* Calls {@link Player#stop(boolean)} on the delegate.
*
* @deprecated Use {@link #stop()} and {@link #clearMediaItems()} (if {@code reset} is true) or
* just {@link #stop()} (if {@code reset} is false). Any player error will be cleared when
* {@link #prepare() re-preparing} the player.
*/
@SuppressWarnings("deprecation") // Forwarding to deprecated method @SuppressWarnings("deprecation") // Forwarding to deprecated method
@Deprecated @Deprecated
@Override @Override
...@@ -497,7 +536,11 @@ public class ForwardingPlayer implements Player { ...@@ -497,7 +536,11 @@ public class ForwardingPlayer implements Player {
return player.getCurrentPeriodIndex(); return player.getCurrentPeriodIndex();
} }
/** Calls {@link Player#getCurrentWindowIndex()} on the delegate and returns the result. */ /**
* Calls {@link Player#getCurrentWindowIndex()} on the delegate and returns the result.
*
* @deprecated Use {@link #getCurrentMediaItemIndex()} instead.
*/
@SuppressWarnings("deprecation") // Forwarding to deprecated method @SuppressWarnings("deprecation") // Forwarding to deprecated method
@Deprecated @Deprecated
@Override @Override
...@@ -511,7 +554,11 @@ public class ForwardingPlayer implements Player { ...@@ -511,7 +554,11 @@ public class ForwardingPlayer implements Player {
return player.getCurrentMediaItemIndex(); return player.getCurrentMediaItemIndex();
} }
/** Calls {@link Player#getNextWindowIndex()} on the delegate and returns the result. */ /**
* Calls {@link Player#getNextWindowIndex()} on the delegate and returns the result.
*
* @deprecated Use {@link #getNextMediaItemIndex()} instead.
*/
@SuppressWarnings("deprecation") // Forwarding to deprecated method @SuppressWarnings("deprecation") // Forwarding to deprecated method
@Deprecated @Deprecated
@Override @Override
...@@ -525,7 +572,11 @@ public class ForwardingPlayer implements Player { ...@@ -525,7 +572,11 @@ public class ForwardingPlayer implements Player {
return player.getNextMediaItemIndex(); return player.getNextMediaItemIndex();
} }
/** Calls {@link Player#getPreviousWindowIndex()} on the delegate and returns the result. */ /**
* Calls {@link Player#getPreviousWindowIndex()} on the delegate and returns the result.
*
* @deprecated Use {@link #getPreviousMediaItemIndex()} instead.
*/
@SuppressWarnings("deprecation") // Forwarding to deprecated method @SuppressWarnings("deprecation") // Forwarding to deprecated method
@Deprecated @Deprecated
@Override @Override
...@@ -588,7 +639,11 @@ public class ForwardingPlayer implements Player { ...@@ -588,7 +639,11 @@ public class ForwardingPlayer implements Player {
return player.getTotalBufferedDuration(); return player.getTotalBufferedDuration();
} }
/** Calls {@link Player#isCurrentWindowDynamic()} on the delegate and returns the result. */ /**
* Calls {@link Player#isCurrentWindowDynamic()} on the delegate and returns the result.
*
* @deprecated Use {@link #isCurrentMediaItemDynamic()} instead.
*/
@SuppressWarnings("deprecation") // Forwarding to deprecated method @SuppressWarnings("deprecation") // Forwarding to deprecated method
@Deprecated @Deprecated
@Override @Override
...@@ -602,7 +657,11 @@ public class ForwardingPlayer implements Player { ...@@ -602,7 +657,11 @@ public class ForwardingPlayer implements Player {
return player.isCurrentMediaItemDynamic(); return player.isCurrentMediaItemDynamic();
} }
/** Calls {@link Player#isCurrentWindowLive()} on the delegate and returns the result. */ /**
* Calls {@link Player#isCurrentWindowLive()} on the delegate and returns the result.
*
* @deprecated Use {@link #isCurrentMediaItemLive()} instead.
*/
@SuppressWarnings("deprecation") // Forwarding to deprecated method @SuppressWarnings("deprecation") // Forwarding to deprecated method
@Deprecated @Deprecated
@Override @Override
...@@ -622,7 +681,11 @@ public class ForwardingPlayer implements Player { ...@@ -622,7 +681,11 @@ public class ForwardingPlayer implements Player {
return player.getCurrentLiveOffset(); return player.getCurrentLiveOffset();
} }
/** Calls {@link Player#isCurrentWindowSeekable()} on the delegate and returns the result. */ /**
* Calls {@link Player#isCurrentWindowSeekable()} on the delegate and returns the result.
*
* @deprecated Use {@link #isCurrentMediaItemSeekable()} instead.
*/
@SuppressWarnings("deprecation") // Forwarding to deprecated method @SuppressWarnings("deprecation") // Forwarding to deprecated method
@Deprecated @Deprecated
@Override @Override
...@@ -752,7 +815,7 @@ public class ForwardingPlayer implements Player { ...@@ -752,7 +815,7 @@ public class ForwardingPlayer implements Player {
/** Calls {@link Player#getCurrentCues()} on the delegate and returns the result. */ /** Calls {@link Player#getCurrentCues()} on the delegate and returns the result. */
@Override @Override
public List<Cue> getCurrentCues() { public CueGroup getCurrentCues() {
return player.getCurrentCues(); return player.getCurrentCues();
} }
...@@ -993,6 +1056,11 @@ public class ForwardingPlayer implements Player { ...@@ -993,6 +1056,11 @@ public class ForwardingPlayer implements Player {
} }
@Override @Override
public void onCues(CueGroup cueGroup) {
listener.onCues(cueGroup);
}
@Override
public void onMetadata(Metadata metadata) { public void onMetadata(Metadata metadata) {
listener.onMetadata(metadata); listener.onMetadata(metadata);
} }
......
...@@ -52,7 +52,6 @@ public final class MediaMetadata implements Bundleable { ...@@ -52,7 +52,6 @@ public final class MediaMetadata implements Bundleable {
@Nullable private CharSequence displayTitle; @Nullable private CharSequence displayTitle;
@Nullable private CharSequence subtitle; @Nullable private CharSequence subtitle;
@Nullable private CharSequence description; @Nullable private CharSequence description;
@Nullable private Uri mediaUri;
@Nullable private Rating userRating; @Nullable private Rating userRating;
@Nullable private Rating overallRating; @Nullable private Rating overallRating;
@Nullable private byte[] artworkData; @Nullable private byte[] artworkData;
...@@ -88,7 +87,6 @@ public final class MediaMetadata implements Bundleable { ...@@ -88,7 +87,6 @@ public final class MediaMetadata implements Bundleable {
this.displayTitle = mediaMetadata.displayTitle; this.displayTitle = mediaMetadata.displayTitle;
this.subtitle = mediaMetadata.subtitle; this.subtitle = mediaMetadata.subtitle;
this.description = mediaMetadata.description; this.description = mediaMetadata.description;
this.mediaUri = mediaMetadata.mediaUri;
this.userRating = mediaMetadata.userRating; this.userRating = mediaMetadata.userRating;
this.overallRating = mediaMetadata.overallRating; this.overallRating = mediaMetadata.overallRating;
this.artworkData = mediaMetadata.artworkData; this.artworkData = mediaMetadata.artworkData;
...@@ -161,12 +159,6 @@ public final class MediaMetadata implements Bundleable { ...@@ -161,12 +159,6 @@ public final class MediaMetadata implements Bundleable {
return this; return this;
} }
/** Sets the media {@link Uri}. */
public Builder setMediaUri(@Nullable Uri mediaUri) {
this.mediaUri = mediaUri;
return this;
}
/** Sets the user {@link Rating}. */ /** Sets the user {@link Rating}. */
public Builder setUserRating(@Nullable Rating userRating) { public Builder setUserRating(@Nullable Rating userRating) {
this.userRating = userRating; this.userRating = userRating;
...@@ -431,9 +423,6 @@ public final class MediaMetadata implements Bundleable { ...@@ -431,9 +423,6 @@ public final class MediaMetadata implements Bundleable {
if (mediaMetadata.description != null) { if (mediaMetadata.description != null) {
setDescription(mediaMetadata.description); setDescription(mediaMetadata.description);
} }
if (mediaMetadata.mediaUri != null) {
setMediaUri(mediaMetadata.mediaUri);
}
if (mediaMetadata.userRating != null) { if (mediaMetadata.userRating != null) {
setUserRating(mediaMetadata.userRating); setUserRating(mediaMetadata.userRating);
} }
...@@ -636,8 +625,6 @@ public final class MediaMetadata implements Bundleable { ...@@ -636,8 +625,6 @@ public final class MediaMetadata implements Bundleable {
@Nullable public final CharSequence subtitle; @Nullable public final CharSequence subtitle;
/** Optional description. */ /** Optional description. */
@Nullable public final CharSequence description; @Nullable public final CharSequence description;
/** Optional media {@link Uri}. */
@Nullable public final Uri mediaUri;
/** Optional user {@link Rating}. */ /** Optional user {@link Rating}. */
@Nullable public final Rating userRating; @Nullable public final Rating userRating;
/** Optional overall {@link Rating}. */ /** Optional overall {@link Rating}. */
...@@ -722,7 +709,6 @@ public final class MediaMetadata implements Bundleable { ...@@ -722,7 +709,6 @@ public final class MediaMetadata implements Bundleable {
this.displayTitle = builder.displayTitle; this.displayTitle = builder.displayTitle;
this.subtitle = builder.subtitle; this.subtitle = builder.subtitle;
this.description = builder.description; this.description = builder.description;
this.mediaUri = builder.mediaUri;
this.userRating = builder.userRating; this.userRating = builder.userRating;
this.overallRating = builder.overallRating; this.overallRating = builder.overallRating;
this.artworkData = builder.artworkData; this.artworkData = builder.artworkData;
...@@ -771,7 +757,6 @@ public final class MediaMetadata implements Bundleable { ...@@ -771,7 +757,6 @@ public final class MediaMetadata implements Bundleable {
&& Util.areEqual(displayTitle, that.displayTitle) && Util.areEqual(displayTitle, that.displayTitle)
&& Util.areEqual(subtitle, that.subtitle) && Util.areEqual(subtitle, that.subtitle)
&& Util.areEqual(description, that.description) && Util.areEqual(description, that.description)
&& Util.areEqual(mediaUri, that.mediaUri)
&& Util.areEqual(userRating, that.userRating) && Util.areEqual(userRating, that.userRating)
&& Util.areEqual(overallRating, that.overallRating) && Util.areEqual(overallRating, that.overallRating)
&& Arrays.equals(artworkData, that.artworkData) && Arrays.equals(artworkData, that.artworkData)
...@@ -807,7 +792,6 @@ public final class MediaMetadata implements Bundleable { ...@@ -807,7 +792,6 @@ public final class MediaMetadata implements Bundleable {
displayTitle, displayTitle,
subtitle, subtitle,
description, description,
mediaUri,
userRating, userRating,
overallRating, overallRating,
Arrays.hashCode(artworkData), Arrays.hashCode(artworkData),
...@@ -918,7 +902,6 @@ public final class MediaMetadata implements Bundleable { ...@@ -918,7 +902,6 @@ public final class MediaMetadata implements Bundleable {
bundle.putCharSequence(keyForField(FIELD_DISPLAY_TITLE), displayTitle); bundle.putCharSequence(keyForField(FIELD_DISPLAY_TITLE), displayTitle);
bundle.putCharSequence(keyForField(FIELD_SUBTITLE), subtitle); bundle.putCharSequence(keyForField(FIELD_SUBTITLE), subtitle);
bundle.putCharSequence(keyForField(FIELD_DESCRIPTION), description); bundle.putCharSequence(keyForField(FIELD_DESCRIPTION), description);
bundle.putParcelable(keyForField(FIELD_MEDIA_URI), mediaUri);
bundle.putByteArray(keyForField(FIELD_ARTWORK_DATA), artworkData); bundle.putByteArray(keyForField(FIELD_ARTWORK_DATA), artworkData);
bundle.putParcelable(keyForField(FIELD_ARTWORK_URI), artworkUri); bundle.putParcelable(keyForField(FIELD_ARTWORK_URI), artworkUri);
bundle.putCharSequence(keyForField(FIELD_WRITER), writer); bundle.putCharSequence(keyForField(FIELD_WRITER), writer);
...@@ -992,7 +975,6 @@ public final class MediaMetadata implements Bundleable { ...@@ -992,7 +975,6 @@ public final class MediaMetadata implements Bundleable {
.setDisplayTitle(bundle.getCharSequence(keyForField(FIELD_DISPLAY_TITLE))) .setDisplayTitle(bundle.getCharSequence(keyForField(FIELD_DISPLAY_TITLE)))
.setSubtitle(bundle.getCharSequence(keyForField(FIELD_SUBTITLE))) .setSubtitle(bundle.getCharSequence(keyForField(FIELD_SUBTITLE)))
.setDescription(bundle.getCharSequence(keyForField(FIELD_DESCRIPTION))) .setDescription(bundle.getCharSequence(keyForField(FIELD_DESCRIPTION)))
.setMediaUri(bundle.getParcelable(keyForField(FIELD_MEDIA_URI)))
.setArtworkData( .setArtworkData(
bundle.getByteArray(keyForField(FIELD_ARTWORK_DATA)), bundle.getByteArray(keyForField(FIELD_ARTWORK_DATA)),
bundle.containsKey(keyForField(FIELD_ARTWORK_DATA_TYPE)) bundle.containsKey(keyForField(FIELD_ARTWORK_DATA_TYPE))
......
...@@ -87,6 +87,7 @@ public final class MimeTypes { ...@@ -87,6 +87,7 @@ public final class MimeTypes {
public static final String AUDIO_AMR_NB = BASE_TYPE_AUDIO + "/3gpp"; public static final String AUDIO_AMR_NB = BASE_TYPE_AUDIO + "/3gpp";
public static final String AUDIO_AMR_WB = BASE_TYPE_AUDIO + "/amr-wb"; public static final String AUDIO_AMR_WB = BASE_TYPE_AUDIO + "/amr-wb";
public static final String AUDIO_FLAC = BASE_TYPE_AUDIO + "/flac"; public static final String AUDIO_FLAC = BASE_TYPE_AUDIO + "/flac";
public static final String AUDIO_MIDI = BASE_TYPE_AUDIO + "/midi";
public static final String AUDIO_ALAC = BASE_TYPE_AUDIO + "/alac"; public static final String AUDIO_ALAC = BASE_TYPE_AUDIO + "/alac";
public static final String AUDIO_MSGSM = BASE_TYPE_AUDIO + "/gsm"; public static final String AUDIO_MSGSM = BASE_TYPE_AUDIO + "/gsm";
public static final String AUDIO_OGG = BASE_TYPE_AUDIO + "/ogg"; public static final String AUDIO_OGG = BASE_TYPE_AUDIO + "/ogg";
......
...@@ -401,28 +401,6 @@ public class PlaybackException extends Exception implements Bundleable { ...@@ -401,28 +401,6 @@ public class PlaybackException extends Exception implements Bundleable {
// Bundleable implementation. // Bundleable implementation.
/**
* Identifiers for fields in a {@link Bundle} which represents a playback exception. Subclasses
* may use {@link #FIELD_CUSTOM_ID_BASE} to generate more keys using {@link #keyForField(int)}.
*
* <p>Note: Changes to the Bundleable implementation must be backwards compatible, so as to avoid
* breaking communication across different Bundleable implementation versions.
*/
@Documented
@Retention(RetentionPolicy.SOURCE)
@Target(TYPE_USE)
@IntDef(
open = true,
value = {
FIELD_INT_ERROR_CODE,
FIELD_LONG_TIMESTAMP_MS,
FIELD_STRING_MESSAGE,
FIELD_STRING_CAUSE_CLASS_NAME,
FIELD_STRING_CAUSE_MESSAGE,
})
@UnstableApi
protected @interface FieldNumber {}
private static final int FIELD_INT_ERROR_CODE = 0; private static final int FIELD_INT_ERROR_CODE = 0;
private static final int FIELD_LONG_TIMESTAMP_MS = 1; private static final int FIELD_LONG_TIMESTAMP_MS = 1;
private static final int FIELD_STRING_MESSAGE = 2; private static final int FIELD_STRING_MESSAGE = 2;
...@@ -430,7 +408,7 @@ public class PlaybackException extends Exception implements Bundleable { ...@@ -430,7 +408,7 @@ public class PlaybackException extends Exception implements Bundleable {
private static final int FIELD_STRING_CAUSE_MESSAGE = 4; private static final int FIELD_STRING_CAUSE_MESSAGE = 4;
/** /**
* Defines a minimum field id value for subclasses to use when implementing {@link #toBundle()} * Defines a minimum field ID value for subclasses to use when implementing {@link #toBundle()}
* and {@link Bundleable.Creator}. * and {@link Bundleable.Creator}.
* *
* <p>Subclasses should obtain their {@link Bundle Bundle's} field keys by applying a non-negative * <p>Subclasses should obtain their {@link Bundle Bundle's} field keys by applying a non-negative
...@@ -458,11 +436,14 @@ public class PlaybackException extends Exception implements Bundleable { ...@@ -458,11 +436,14 @@ public class PlaybackException extends Exception implements Bundleable {
} }
/** /**
* Converts the given {@link FieldNumber} to a string which can be used as a field key when * Converts the given field number to a string which can be used as a field key when implementing
* implementing {@link #toBundle()} and {@link Bundleable.Creator}. * {@link #toBundle()} and {@link Bundleable.Creator}.
*
* <p>Subclasses should use {@code field} values greater than or equal to {@link
* #FIELD_CUSTOM_ID_BASE}.
*/ */
@UnstableApi @UnstableApi
protected static String keyForField(@FieldNumber int field) { protected static String keyForField(int field) {
return Integer.toString(field, Character.MAX_RADIX); return Integer.toString(field, Character.MAX_RADIX);
} }
......
...@@ -32,7 +32,7 @@ import androidx.annotation.IntDef; ...@@ -32,7 +32,7 @@ import androidx.annotation.IntDef;
import androidx.annotation.IntRange; import androidx.annotation.IntRange;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.media3.common.text.Cue; import androidx.media3.common.text.Cue;
import androidx.media3.common.util.BundleableUtil; import androidx.media3.common.text.CueGroup;
import androidx.media3.common.util.UnstableApi; import androidx.media3.common.util.UnstableApi;
import androidx.media3.common.util.Util; import androidx.media3.common.util.Util;
import com.google.common.base.Objects; import com.google.common.base.Objects;
...@@ -294,7 +294,9 @@ public interface Player { ...@@ -294,7 +294,9 @@ public interface Player {
public Bundle toBundle() { public Bundle toBundle() {
Bundle bundle = new Bundle(); Bundle bundle = new Bundle();
bundle.putInt(keyForField(FIELD_MEDIA_ITEM_INDEX), mediaItemIndex); bundle.putInt(keyForField(FIELD_MEDIA_ITEM_INDEX), mediaItemIndex);
bundle.putBundle(keyForField(FIELD_MEDIA_ITEM), BundleableUtil.toNullableBundle(mediaItem)); if (mediaItem != null) {
bundle.putBundle(keyForField(FIELD_MEDIA_ITEM), mediaItem.toBundle());
}
bundle.putInt(keyForField(FIELD_PERIOD_INDEX), periodIndex); bundle.putInt(keyForField(FIELD_PERIOD_INDEX), periodIndex);
bundle.putLong(keyForField(FIELD_POSITION_MS), positionMs); bundle.putLong(keyForField(FIELD_POSITION_MS), positionMs);
bundle.putLong(keyForField(FIELD_CONTENT_POSITION_MS), contentPositionMs); bundle.putLong(keyForField(FIELD_CONTENT_POSITION_MS), contentPositionMs);
...@@ -309,10 +311,10 @@ public interface Player { ...@@ -309,10 +311,10 @@ public interface Player {
private static PositionInfo fromBundle(Bundle bundle) { private static PositionInfo fromBundle(Bundle bundle) {
int mediaItemIndex = int mediaItemIndex =
bundle.getInt(keyForField(FIELD_MEDIA_ITEM_INDEX), /* defaultValue= */ C.INDEX_UNSET); bundle.getInt(keyForField(FIELD_MEDIA_ITEM_INDEX), /* defaultValue= */ C.INDEX_UNSET);
@Nullable Bundle mediaItemBundle = bundle.getBundle(keyForField(FIELD_MEDIA_ITEM));
@Nullable @Nullable
MediaItem mediaItem = MediaItem mediaItem =
BundleableUtil.fromNullableBundle( mediaItemBundle == null ? null : MediaItem.CREATOR.fromBundle(mediaItemBundle);
MediaItem.CREATOR, bundle.getBundle(keyForField(FIELD_MEDIA_ITEM)));
int periodIndex = int periodIndex =
bundle.getInt(keyForField(FIELD_PERIOD_INDEX), /* defaultValue= */ C.INDEX_UNSET); bundle.getInt(keyForField(FIELD_PERIOD_INDEX), /* defaultValue= */ C.INDEX_UNSET);
long positionMs = long positionMs =
...@@ -382,6 +384,7 @@ public interface Player { ...@@ -382,6 +384,7 @@ public interface Player {
COMMAND_GET_TEXT, COMMAND_GET_TEXT,
COMMAND_SET_TRACK_SELECTION_PARAMETERS, COMMAND_SET_TRACK_SELECTION_PARAMETERS,
COMMAND_GET_TRACKS, COMMAND_GET_TRACKS,
COMMAND_SET_MEDIA_ITEM,
}; };
private final FlagSet.Builder flagsBuilder; private final FlagSet.Builder flagsBuilder;
...@@ -1024,17 +1027,30 @@ public interface Player { ...@@ -1024,17 +1027,30 @@ public interface Player {
/** /**
* Called when there is a change in the {@link Cue Cues}. * Called when there is a change in the {@link Cue Cues}.
* *
* <p>{@code cues} is in ascending order of priority. If any of the cue boxes overlap when * <p>Both {@link #onCues(List)} and {@link #onCues(CueGroup)} are called when there is a change
* displayed, the {@link Cue} nearer the end of the list should be shown on top. * in the cues. You should only implement one or the other.
* *
* <p>{@link #onEvents(Player, Events)} will also be called to report this event along with * <p>{@link #onEvents(Player, Events)} will also be called to report this event along with
* other events that happen in the same {@link Looper} message queue iteration. * other events that happen in the same {@link Looper} message queue iteration.
* *
* @param cues The {@link Cue Cues}. May be empty. * @deprecated Use {@link #onCues(CueGroup)} instead.
*/ */
@Deprecated
@UnstableApi
default void onCues(List<Cue> cues) {} default void onCues(List<Cue> cues) {}
/** /**
* Called when there is a change in the {@link CueGroup}.
*
* <p>Both {@link #onCues(List)} and {@link #onCues(CueGroup)} are called when there is a change
* in the cues. You should only implement one or the other.
*
* <p>{@link #onEvents(Player, Events)} will also be called to report this event along with
* other events that happen in the same {@link Looper} message queue iteration.
*/
default void onCues(CueGroup cueGroup) {}
/**
* Called when there is metadata associated with the current playback time. * Called when there is metadata associated with the current playback time.
* *
* <p>{@link #onEvents(Player, Events)} will also be called to report this event along with * <p>{@link #onEvents(Player, Events)} will also be called to report this event along with
...@@ -1387,7 +1403,8 @@ public interface Player { ...@@ -1387,7 +1403,8 @@ public interface Player {
* #COMMAND_GET_VOLUME}, {@link #COMMAND_GET_DEVICE_VOLUME}, {@link #COMMAND_SET_VOLUME}, {@link * #COMMAND_GET_VOLUME}, {@link #COMMAND_GET_DEVICE_VOLUME}, {@link #COMMAND_SET_VOLUME}, {@link
* #COMMAND_SET_DEVICE_VOLUME}, {@link #COMMAND_ADJUST_DEVICE_VOLUME}, {@link * #COMMAND_SET_DEVICE_VOLUME}, {@link #COMMAND_ADJUST_DEVICE_VOLUME}, {@link
* #COMMAND_SET_VIDEO_SURFACE}, {@link #COMMAND_GET_TEXT}, {@link * #COMMAND_SET_VIDEO_SURFACE}, {@link #COMMAND_GET_TEXT}, {@link
* #COMMAND_SET_TRACK_SELECTION_PARAMETERS} or {@link #COMMAND_GET_TRACKS}. * #COMMAND_SET_TRACK_SELECTION_PARAMETERS}, {@link #COMMAND_GET_TRACKS} or {@link
* #COMMAND_SET_MEDIA_ITEM}.
*/ */
// @Target list includes both 'default' targets and TYPE_USE, to ensure backwards compatibility // @Target list includes both 'default' targets and TYPE_USE, to ensure backwards compatibility
// with Kotlin usages from before TYPE_USE was added. // with Kotlin usages from before TYPE_USE was added.
...@@ -1426,6 +1443,7 @@ public interface Player { ...@@ -1426,6 +1443,7 @@ public interface Player {
COMMAND_GET_TEXT, COMMAND_GET_TEXT,
COMMAND_SET_TRACK_SELECTION_PARAMETERS, COMMAND_SET_TRACK_SELECTION_PARAMETERS,
COMMAND_GET_TRACKS, COMMAND_GET_TRACKS,
COMMAND_SET_MEDIA_ITEM,
}) })
@interface Command {} @interface Command {}
/** Command to start, pause or resume playback. */ /** Command to start, pause or resume playback. */
...@@ -1505,6 +1523,8 @@ public interface Player { ...@@ -1505,6 +1523,8 @@ public interface Player {
int COMMAND_SET_TRACK_SELECTION_PARAMETERS = 29; int COMMAND_SET_TRACK_SELECTION_PARAMETERS = 29;
/** Command to get details of the current track selection. */ /** Command to get details of the current track selection. */
int COMMAND_GET_TRACKS = 30; int COMMAND_GET_TRACKS = 30;
/** Command to set a {@link MediaItem MediaItem}. */
int COMMAND_SET_MEDIA_ITEM = 31;
/** Represents an invalid {@link Command}. */ /** Represents an invalid {@link Command}. */
int COMMAND_INVALID = -1; int COMMAND_INVALID = -1;
...@@ -2469,8 +2489,8 @@ public interface Player { ...@@ -2469,8 +2489,8 @@ public interface Player {
*/ */
VideoSize getVideoSize(); VideoSize getVideoSize();
/** Returns the current {@link Cue Cues}. This list may be empty. */ /** Returns the current {@link CueGroup}. */
List<Cue> getCurrentCues(); CueGroup getCurrentCues();
/** Gets the device information. */ /** Gets the device information. */
DeviceInfo getDeviceInfo(); DeviceInfo getDeviceInfo();
......
...@@ -32,7 +32,7 @@ import java.lang.annotation.Target; ...@@ -32,7 +32,7 @@ import java.lang.annotation.Target;
public abstract class Rating implements Bundleable { public abstract class Rating implements Bundleable {
/** A float value that denotes the rating is unset. */ /** A float value that denotes the rating is unset. */
public static final float RATING_UNSET = -1.0f; /* package */ static final float RATING_UNSET = -1.0f;
// Default package-private constructor to prevent extending Rating class outside this package. // Default package-private constructor to prevent extending Rating class outside this package.
/* package */ Rating() {} /* package */ Rating() {}
......
...@@ -189,11 +189,12 @@ public final class TrackGroup implements Bundleable { ...@@ -189,11 +189,12 @@ public final class TrackGroup implements Bundleable {
@UnstableApi @UnstableApi
public static final Creator<TrackGroup> CREATOR = public static final Creator<TrackGroup> CREATOR =
bundle -> { bundle -> {
@Nullable
List<Bundle> formatBundles = bundle.getParcelableArrayList(keyForField(FIELD_FORMATS));
List<Format> formats = List<Format> formats =
BundleableUtil.fromBundleNullableList( formatBundles == null
Format.CREATOR, ? ImmutableList.of()
bundle.getParcelableArrayList(keyForField(FIELD_FORMATS)), : BundleableUtil.fromBundleList(Format.CREATOR, formatBundles);
ImmutableList.of());
String id = bundle.getString(keyForField(FIELD_ID), /* defaultValue= */ ""); String id = bundle.getString(keyForField(FIELD_ID), /* defaultValue= */ "");
return new TrackGroup(id, formats.toArray(new Format[0])); return new TrackGroup(id, formats.toArray(new Format[0]));
}; };
......
...@@ -16,7 +16,6 @@ ...@@ -16,7 +16,6 @@
package androidx.media3.common; package androidx.media3.common;
import static androidx.media3.common.util.Assertions.checkNotNull; import static androidx.media3.common.util.Assertions.checkNotNull;
import static androidx.media3.common.util.BundleableUtil.fromBundleNullableList;
import static androidx.media3.common.util.BundleableUtil.toBundleArrayList; import static androidx.media3.common.util.BundleableUtil.toBundleArrayList;
import static com.google.common.base.MoreObjects.firstNonNull; import static com.google.common.base.MoreObjects.firstNonNull;
...@@ -25,18 +24,15 @@ import android.graphics.Point; ...@@ -25,18 +24,15 @@ import android.graphics.Point;
import android.os.Bundle; import android.os.Bundle;
import android.os.Looper; import android.os.Looper;
import android.view.accessibility.CaptioningManager; import android.view.accessibility.CaptioningManager;
import androidx.annotation.IntDef;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi; import androidx.annotation.RequiresApi;
import androidx.media3.common.util.BundleableUtil;
import androidx.media3.common.util.UnstableApi; import androidx.media3.common.util.UnstableApi;
import androidx.media3.common.util.Util; import androidx.media3.common.util.Util;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSet;
import com.google.common.primitives.Ints; import com.google.common.primitives.Ints;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.Iterator; import java.util.Iterator;
...@@ -247,11 +243,13 @@ public class TrackSelectionParameters implements Bundleable { ...@@ -247,11 +243,13 @@ public class TrackSelectionParameters implements Bundleable {
bundle.getBoolean( bundle.getBoolean(
keyForField(FIELD_FORCE_HIGHEST_SUPPORTED_BITRATE), keyForField(FIELD_FORCE_HIGHEST_SUPPORTED_BITRATE),
DEFAULT_WITHOUT_CONTEXT.forceHighestSupportedBitrate); DEFAULT_WITHOUT_CONTEXT.forceHighestSupportedBitrate);
@Nullable
List<Bundle> overrideBundleList =
bundle.getParcelableArrayList(keyForField(FIELD_SELECTION_OVERRIDES));
List<TrackSelectionOverride> overrideList = List<TrackSelectionOverride> overrideList =
fromBundleNullableList( overrideBundleList == null
TrackSelectionOverride.CREATOR, ? ImmutableList.of()
bundle.getParcelableArrayList(keyForField(FIELD_SELECTION_OVERRIDES)), : BundleableUtil.fromBundleList(TrackSelectionOverride.CREATOR, overrideBundleList);
ImmutableList.of());
overrides = new HashMap<>(); overrides = new HashMap<>();
for (int i = 0; i < overrideList.size(); i++) { for (int i = 0; i < overrideList.size(); i++) {
TrackSelectionOverride override = overrideList.get(i); TrackSelectionOverride override = overrideList.get(i);
...@@ -1071,42 +1069,6 @@ public class TrackSelectionParameters implements Bundleable { ...@@ -1071,42 +1069,6 @@ public class TrackSelectionParameters implements Bundleable {
// Bundleable implementation // Bundleable implementation
@Documented
@Retention(RetentionPolicy.SOURCE)
@IntDef({
// Video
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_PREFERRED_VIDEO_ROLE_FLAGS,
// Audio
FIELD_PREFERRED_AUDIO_LANGUAGES,
FIELD_PREFERRED_AUDIO_ROLE_FLAGS,
FIELD_MAX_AUDIO_CHANNEL_COUNT,
FIELD_MAX_AUDIO_BITRATE,
FIELD_PREFERRED_AUDIO_MIME_TYPES,
// Text
FIELD_PREFERRED_TEXT_LANGUAGES,
FIELD_PREFERRED_TEXT_ROLE_FLAGS,
FIELD_IGNORED_TEXT_SELECTION_FLAGS,
FIELD_SELECT_UNDETERMINED_TEXT_LANGUAGE,
// General
FIELD_FORCE_LOWEST_BITRATE,
FIELD_FORCE_HIGHEST_SUPPORTED_BITRATE,
FIELD_SELECTION_OVERRIDES,
FIELD_DISABLED_TRACK_TYPE,
})
private @interface FieldNumber {}
private static final int FIELD_PREFERRED_AUDIO_LANGUAGES = 1; private static final int FIELD_PREFERRED_AUDIO_LANGUAGES = 1;
private static final int FIELD_PREFERRED_AUDIO_ROLE_FLAGS = 2; private static final int FIELD_PREFERRED_AUDIO_ROLE_FLAGS = 2;
private static final int FIELD_PREFERRED_TEXT_LANGUAGES = 3; private static final int FIELD_PREFERRED_TEXT_LANGUAGES = 3;
...@@ -1134,7 +1096,15 @@ public class TrackSelectionParameters implements Bundleable { ...@@ -1134,7 +1096,15 @@ public class TrackSelectionParameters implements Bundleable {
private static final int FIELD_PREFERRED_VIDEO_ROLE_FLAGS = 25; private static final int FIELD_PREFERRED_VIDEO_ROLE_FLAGS = 25;
private static final int FIELD_IGNORED_TEXT_SELECTION_FLAGS = 26; private static final int FIELD_IGNORED_TEXT_SELECTION_FLAGS = 26;
@UnstableApi /**
* 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)}.
*/
@UnstableApi protected static final int FIELD_CUSTOM_ID_BASE = 1000;
@Override @Override
public Bundle toBundle() { public Bundle toBundle() {
Bundle bundle = new Bundle(); Bundle bundle = new Bundle();
...@@ -1184,12 +1154,27 @@ public class TrackSelectionParameters implements Bundleable { ...@@ -1184,12 +1154,27 @@ public class TrackSelectionParameters implements Bundleable {
return bundle; return bundle;
} }
/** Object that can restore {@code TrackSelectionParameters} from a {@link Bundle}. */ /** Construct an instance from a {@link Bundle} produced by {@link #toBundle()}. */
@UnstableApi public static TrackSelectionParameters fromBundle(Bundle bundle) {
return new Builder(bundle).build();
}
/**
* @deprecated Use {@link #fromBundle(Bundle)} instead.
*/
@UnstableApi @Deprecated
public static final Creator<TrackSelectionParameters> CREATOR = public static final Creator<TrackSelectionParameters> CREATOR =
bundle -> new Builder(bundle).build(); TrackSelectionParameters::fromBundle;
private static String keyForField(@FieldNumber int field) { /**
* 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}.
*/
@UnstableApi
protected static String keyForField(int field) {
return Integer.toString(field, Character.MAX_RADIX); return Integer.toString(field, Character.MAX_RADIX);
} }
} }
...@@ -17,14 +17,13 @@ package androidx.media3.common; ...@@ -17,14 +17,13 @@ package androidx.media3.common;
import static androidx.media3.common.util.Assertions.checkArgument; import static androidx.media3.common.util.Assertions.checkArgument;
import static androidx.media3.common.util.Assertions.checkNotNull; import static androidx.media3.common.util.Assertions.checkNotNull;
import static androidx.media3.common.util.BundleableUtil.fromBundleNullableList;
import static androidx.media3.common.util.BundleableUtil.fromNullableBundle;
import static androidx.media3.common.util.BundleableUtil.toBundleArrayList; import static androidx.media3.common.util.BundleableUtil.toBundleArrayList;
import static java.lang.annotation.ElementType.TYPE_USE; import static java.lang.annotation.ElementType.TYPE_USE;
import android.os.Bundle; import android.os.Bundle;
import androidx.annotation.IntDef; import androidx.annotation.IntDef;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.media3.common.util.BundleableUtil;
import androidx.media3.common.util.UnstableApi; import androidx.media3.common.util.UnstableApi;
import com.google.common.base.MoreObjects; import com.google.common.base.MoreObjects;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
...@@ -252,10 +251,10 @@ public final class Tracks implements Bundleable { ...@@ -252,10 +251,10 @@ public final class Tracks implements Bundleable {
@UnstableApi @UnstableApi
public static final Creator<Group> CREATOR = public static final Creator<Group> CREATOR =
bundle -> { bundle -> {
// Can't create a Tracks.Group without a TrackGroup
TrackGroup trackGroup = TrackGroup trackGroup =
fromNullableBundle( TrackGroup.CREATOR.fromBundle(
TrackGroup.CREATOR, bundle.getBundle(keyForField(FIELD_TRACK_GROUP))); checkNotNull(bundle.getBundle(keyForField(FIELD_TRACK_GROUP))));
checkNotNull(trackGroup); // Can't create a trackGroup info without a trackGroup
final @C.FormatSupport int[] trackSupport = final @C.FormatSupport int[] trackSupport =
MoreObjects.firstNonNull( MoreObjects.firstNonNull(
bundle.getIntArray(keyForField(FIELD_TRACK_SUPPORT)), new int[trackGroup.length]); bundle.getIntArray(keyForField(FIELD_TRACK_SUPPORT)), new int[trackGroup.length]);
...@@ -274,7 +273,7 @@ public final class Tracks implements Bundleable { ...@@ -274,7 +273,7 @@ public final class Tracks implements Bundleable {
} }
/** Empty tracks. */ /** Empty tracks. */
@UnstableApi public static final Tracks EMPTY = new Tracks(ImmutableList.of()); public static final Tracks EMPTY = new Tracks(ImmutableList.of());
private final ImmutableList<Group> groups; private final ImmutableList<Group> groups;
...@@ -408,11 +407,12 @@ public final class Tracks implements Bundleable { ...@@ -408,11 +407,12 @@ public final class Tracks implements Bundleable {
@UnstableApi @UnstableApi
public static final Creator<Tracks> CREATOR = public static final Creator<Tracks> CREATOR =
bundle -> { bundle -> {
@Nullable
List<Bundle> groupBundles = bundle.getParcelableArrayList(keyForField(FIELD_TRACK_GROUPS));
List<Group> groups = List<Group> groups =
fromBundleNullableList( groupBundles == null
Group.CREATOR, ? ImmutableList.of()
bundle.getParcelableArrayList(keyForField(FIELD_TRACK_GROUPS)), : BundleableUtil.fromBundleList(Group.CREATOR, groupBundles);
/* defaultValue= */ ImmutableList.of());
return new Tracks(groups); return new Tracks(groups);
}; };
......
/*
* 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 androidx.media3.common.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 androidx.media3.common.Bundleable;
import androidx.media3.common.util.BundleableUtil;
import androidx.media3.common.util.UnstableApi;
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}. */
@UnstableApi 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. */
@UnstableApi
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;
@UnstableApi
@Override
public Bundle toBundle() {
Bundle bundle = new Bundle();
bundle.putParcelableArrayList(
keyForField(FIELD_CUES), BundleableUtil.toBundleArrayList(filterOutBitmapCues(cues)));
return bundle;
}
@UnstableApi 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();
}
}
...@@ -31,34 +31,6 @@ import java.util.List; ...@@ -31,34 +31,6 @@ import java.util.List;
@UnstableApi @UnstableApi
public final class BundleableUtil { public final class BundleableUtil {
/**
* Converts a {@link Bundleable} to a {@link Bundle}. It's a convenience wrapper of {@link
* Bundleable#toBundle} that can take nullable values.
*/
@Nullable
public static Bundle toNullableBundle(@Nullable Bundleable bundleable) {
return bundleable == null ? null : bundleable.toBundle();
}
/**
* Converts a {@link Bundle} to a {@link Bundleable}. It's a convenience wrapper of {@link
* Bundleable.Creator#fromBundle} that can take nullable values.
*/
@Nullable
public static <T extends Bundleable> T fromNullableBundle(
Bundleable.Creator<T> creator, @Nullable Bundle bundle) {
return bundle == null ? null : creator.fromBundle(bundle);
}
/**
* Converts a {@link Bundle} to a {@link Bundleable}. It's a convenience wrapper of {@link
* Bundleable.Creator#fromBundle} that provides default value to ensure non-null.
*/
public static <T extends Bundleable> T fromNullableBundle(
Bundleable.Creator<T> creator, @Nullable Bundle bundle, T defaultValue) {
return bundle == null ? defaultValue : creator.fromBundle(bundle);
}
/** Converts a list of {@link Bundleable} to a list {@link Bundle}. */ /** Converts a list of {@link Bundleable} to a list {@link Bundle}. */
public static <T extends Bundleable> ImmutableList<Bundle> toBundleList(List<T> bundleableList) { public static <T extends Bundleable> ImmutableList<Bundle> toBundleList(List<T> bundleableList) {
ImmutableList.Builder<Bundle> builder = ImmutableList.builder(); ImmutableList.Builder<Bundle> builder = ImmutableList.builder();
...@@ -82,34 +54,6 @@ public final class BundleableUtil { ...@@ -82,34 +54,6 @@ public final class BundleableUtil {
} }
/** /**
* Converts a list of {@link Bundle} to a list of {@link Bundleable}. Returns {@code defaultValue}
* if {@code bundleList} is null.
*/
public static <T extends Bundleable> List<T> fromBundleNullableList(
Bundleable.Creator<T> creator, @Nullable List<Bundle> bundleList, List<T> defaultValue) {
return (bundleList == null) ? defaultValue : fromBundleList(creator, bundleList);
}
/**
* Converts a {@link SparseArray} of {@link Bundle} to a {@link SparseArray} of {@link
* Bundleable}. Returns {@code defaultValue} if {@code bundleSparseArray} is null.
*/
public static <T extends Bundleable> SparseArray<T> fromBundleNullableSparseArray(
Bundleable.Creator<T> creator,
@Nullable SparseArray<Bundle> bundleSparseArray,
SparseArray<T> defaultValue) {
if (bundleSparseArray == null) {
return defaultValue;
}
// Can't use ImmutableList as it doesn't support null elements.
SparseArray<T> result = new SparseArray<>(bundleSparseArray.size());
for (int i = 0; i < bundleSparseArray.size(); i++) {
result.put(bundleSparseArray.keyAt(i), creator.fromBundle(bundleSparseArray.valueAt(i)));
}
return result;
}
/**
* Converts a collection of {@link Bundleable} to an {@link ArrayList} of {@link Bundle} so that * Converts a collection of {@link Bundleable} to an {@link ArrayList} of {@link Bundle} so that
* the returned list can be put to {@link Bundle} using {@link Bundle#putParcelableArrayList} * the returned list can be put to {@link Bundle} using {@link Bundle#putParcelableArrayList}
* conveniently. * conveniently.
...@@ -124,6 +68,19 @@ public final class BundleableUtil { ...@@ -124,6 +68,19 @@ public final class BundleableUtil {
} }
/** /**
* Converts a {@link SparseArray} of {@link Bundle} to a {@link SparseArray} of {@link
* Bundleable}.
*/
public static <T extends Bundleable> SparseArray<T> fromBundleSparseArray(
Bundleable.Creator<T> creator, SparseArray<Bundle> bundleSparseArray) {
SparseArray<T> result = new SparseArray<>(bundleSparseArray.size());
for (int i = 0; i < bundleSparseArray.size(); i++) {
result.put(bundleSparseArray.keyAt(i), creator.fromBundle(bundleSparseArray.valueAt(i)));
}
return result;
}
/**
* Converts a {@link SparseArray} of {@link Bundleable} to an {@link SparseArray} of {@link * Converts a {@link SparseArray} of {@link Bundleable} to an {@link SparseArray} of {@link
* Bundle} so that the returned {@link SparseArray} can be put to {@link Bundle} using {@link * Bundle} so that the returned {@link SparseArray} can be put to {@link Bundle} using {@link
* Bundle#putSparseParcelableArray} conveniently. * Bundle#putSparseParcelableArray} conveniently.
......
...@@ -79,13 +79,6 @@ public final class EGLSurfaceTexture implements SurfaceTexture.OnFrameAvailableL ...@@ -79,13 +79,6 @@ public final class EGLSurfaceTexture implements SurfaceTexture.OnFrameAvailableL
private static final int EGL_PROTECTED_CONTENT_EXT = 0x32C0; private static final int EGL_PROTECTED_CONTENT_EXT = 0x32C0;
/** A runtime exception to be thrown if some EGL operations failed. */
public static final class GlException extends RuntimeException {
private GlException(String msg) {
super(msg);
}
}
private final Handler handler; private final Handler handler;
private final int[] textureIdHolder; private final int[] textureIdHolder;
@Nullable private final TextureImageListener callback; @Nullable private final TextureImageListener callback;
...@@ -125,7 +118,7 @@ public final class EGLSurfaceTexture implements SurfaceTexture.OnFrameAvailableL ...@@ -125,7 +118,7 @@ public final class EGLSurfaceTexture implements SurfaceTexture.OnFrameAvailableL
* *
* @param secureMode The {@link SecureMode} to be used for EGL surface. * @param secureMode The {@link SecureMode} to be used for EGL surface.
*/ */
public void init(@SecureMode int secureMode) { public void init(@SecureMode int secureMode) throws GlUtil.GlException {
display = getDefaultDisplay(); display = getDefaultDisplay();
EGLConfig config = chooseEGLConfig(display); EGLConfig config = chooseEGLConfig(display);
context = createEGLContext(display, config, secureMode); context = createEGLContext(display, config, secureMode);
...@@ -206,22 +199,18 @@ public final class EGLSurfaceTexture implements SurfaceTexture.OnFrameAvailableL ...@@ -206,22 +199,18 @@ public final class EGLSurfaceTexture implements SurfaceTexture.OnFrameAvailableL
} }
} }
private static EGLDisplay getDefaultDisplay() { private static EGLDisplay getDefaultDisplay() throws GlUtil.GlException {
EGLDisplay display = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY); EGLDisplay display = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY);
if (display == null) { GlUtil.checkGlException(display != null, "eglGetDisplay failed");
throw new GlException("eglGetDisplay failed");
}
int[] version = new int[2]; int[] version = new int[2];
boolean eglInitialized = boolean eglInitialized =
EGL14.eglInitialize(display, version, /* majorOffset= */ 0, version, /* minorOffset= */ 1); EGL14.eglInitialize(display, version, /* majorOffset= */ 0, version, /* minorOffset= */ 1);
if (!eglInitialized) { GlUtil.checkGlException(eglInitialized, "eglInitialize failed");
throw new GlException("eglInitialize failed");
}
return display; return display;
} }
private static EGLConfig chooseEGLConfig(EGLDisplay display) { private static EGLConfig chooseEGLConfig(EGLDisplay display) throws GlUtil.GlException {
EGLConfig[] configs = new EGLConfig[1]; EGLConfig[] configs = new EGLConfig[1];
int[] numConfigs = new int[1]; int[] numConfigs = new int[1];
boolean success = boolean success =
...@@ -234,18 +223,17 @@ public final class EGLSurfaceTexture implements SurfaceTexture.OnFrameAvailableL ...@@ -234,18 +223,17 @@ public final class EGLSurfaceTexture implements SurfaceTexture.OnFrameAvailableL
/* config_size= */ 1, /* config_size= */ 1,
numConfigs, numConfigs,
/* num_configOffset= */ 0); /* num_configOffset= */ 0);
if (!success || numConfigs[0] <= 0 || configs[0] == null) { GlUtil.checkGlException(
throw new GlException( success && numConfigs[0] > 0 && configs[0] != null,
Util.formatInvariant( Util.formatInvariant(
/* format= */ "eglChooseConfig failed: success=%b, numConfigs[0]=%d, configs[0]=%s", /* format= */ "eglChooseConfig failed: success=%b, numConfigs[0]=%d, configs[0]=%s",
success, numConfigs[0], configs[0])); success, numConfigs[0], configs[0]));
}
return configs[0]; return configs[0];
} }
private static EGLContext createEGLContext( private static EGLContext createEGLContext(
EGLDisplay display, EGLConfig config, @SecureMode int secureMode) { EGLDisplay display, EGLConfig config, @SecureMode int secureMode) throws GlUtil.GlException {
int[] glAttributes; int[] glAttributes;
if (secureMode == SECURE_MODE_NONE) { if (secureMode == SECURE_MODE_NONE) {
glAttributes = new int[] {EGL14.EGL_CONTEXT_CLIENT_VERSION, 2, EGL14.EGL_NONE}; glAttributes = new int[] {EGL14.EGL_CONTEXT_CLIENT_VERSION, 2, EGL14.EGL_NONE};
...@@ -262,14 +250,13 @@ public final class EGLSurfaceTexture implements SurfaceTexture.OnFrameAvailableL ...@@ -262,14 +250,13 @@ public final class EGLSurfaceTexture implements SurfaceTexture.OnFrameAvailableL
EGLContext context = EGLContext context =
EGL14.eglCreateContext( EGL14.eglCreateContext(
display, config, android.opengl.EGL14.EGL_NO_CONTEXT, glAttributes, 0); display, config, android.opengl.EGL14.EGL_NO_CONTEXT, glAttributes, 0);
if (context == null) { GlUtil.checkGlException(context != null, "eglCreateContext failed");
throw new GlException("eglCreateContext failed");
}
return context; return context;
} }
private static EGLSurface createEGLSurface( private static EGLSurface createEGLSurface(
EGLDisplay display, EGLConfig config, EGLContext context, @SecureMode int secureMode) { EGLDisplay display, EGLConfig config, EGLContext context, @SecureMode int secureMode)
throws GlUtil.GlException {
EGLSurface surface; EGLSurface surface;
if (secureMode == SECURE_MODE_SURFACELESS_CONTEXT) { if (secureMode == SECURE_MODE_SURFACELESS_CONTEXT) {
surface = EGL14.EGL_NO_SURFACE; surface = EGL14.EGL_NO_SURFACE;
...@@ -297,20 +284,16 @@ public final class EGLSurfaceTexture implements SurfaceTexture.OnFrameAvailableL ...@@ -297,20 +284,16 @@ public final class EGLSurfaceTexture implements SurfaceTexture.OnFrameAvailableL
}; };
} }
surface = EGL14.eglCreatePbufferSurface(display, config, pbufferAttributes, /* offset= */ 0); surface = EGL14.eglCreatePbufferSurface(display, config, pbufferAttributes, /* offset= */ 0);
if (surface == null) { GlUtil.checkGlException(surface != null, "eglCreatePbufferSurface failed");
throw new GlException("eglCreatePbufferSurface failed");
}
} }
boolean eglMadeCurrent = boolean eglMadeCurrent =
EGL14.eglMakeCurrent(display, /* draw= */ surface, /* read= */ surface, context); EGL14.eglMakeCurrent(display, /* draw= */ surface, /* read= */ surface, context);
if (!eglMadeCurrent) { GlUtil.checkGlException(eglMadeCurrent, "eglMakeCurrent failed");
throw new GlException("eglMakeCurrent failed");
}
return surface; return surface;
} }
private static void generateTextureIds(int[] textureIdHolder) { private static void generateTextureIds(int[] textureIdHolder) throws GlUtil.GlException {
GLES20.glGenTextures(/* n= */ 1, textureIdHolder, /* offset= */ 0); GLES20.glGenTextures(/* n= */ 1, textureIdHolder, /* offset= */ 0);
GlUtil.checkGlError(); GlUtil.checkGlError();
} }
......
...@@ -54,7 +54,7 @@ public final class GlProgram { ...@@ -54,7 +54,7 @@ public final class GlProgram {
* @throws IOException When failing to read shader files. * @throws IOException When failing to read shader files.
*/ */
public GlProgram(Context context, String vertexShaderFilePath, String fragmentShaderFilePath) public GlProgram(Context context, String vertexShaderFilePath, String fragmentShaderFilePath)
throws IOException { throws IOException, GlUtil.GlException {
this( this(
GlUtil.loadAsset(context, vertexShaderFilePath), GlUtil.loadAsset(context, vertexShaderFilePath),
GlUtil.loadAsset(context, fragmentShaderFilePath)); GlUtil.loadAsset(context, fragmentShaderFilePath));
...@@ -69,7 +69,7 @@ public final class GlProgram { ...@@ -69,7 +69,7 @@ public final class GlProgram {
* @param vertexShaderGlsl The vertex shader program. * @param vertexShaderGlsl The vertex shader program.
* @param fragmentShaderGlsl The fragment shader program. * @param fragmentShaderGlsl The fragment shader program.
*/ */
public GlProgram(String vertexShaderGlsl, String fragmentShaderGlsl) { public GlProgram(String vertexShaderGlsl, String fragmentShaderGlsl) throws GlUtil.GlException {
programId = GLES20.glCreateProgram(); programId = GLES20.glCreateProgram();
GlUtil.checkGlError(); GlUtil.checkGlError();
...@@ -81,10 +81,9 @@ public final class GlProgram { ...@@ -81,10 +81,9 @@ public final class GlProgram {
GLES20.glLinkProgram(programId); GLES20.glLinkProgram(programId);
int[] linkStatus = new int[] {GLES20.GL_FALSE}; int[] linkStatus = new int[] {GLES20.GL_FALSE};
GLES20.glGetProgramiv(programId, GLES20.GL_LINK_STATUS, linkStatus, /* offset= */ 0); GLES20.glGetProgramiv(programId, GLES20.GL_LINK_STATUS, linkStatus, /* offset= */ 0);
if (linkStatus[0] != GLES20.GL_TRUE) { GlUtil.checkGlException(
GlUtil.throwGlException( linkStatus[0] == GLES20.GL_TRUE,
"Unable to link shader program: \n" + GLES20.glGetProgramInfoLog(programId)); "Unable to link shader program: \n" + GLES20.glGetProgramInfoLog(programId));
}
GLES20.glUseProgram(programId); GLES20.glUseProgram(programId);
attributeByName = new HashMap<>(); attributeByName = new HashMap<>();
int[] attributeCount = new int[1]; int[] attributeCount = new int[1];
...@@ -107,16 +106,15 @@ public final class GlProgram { ...@@ -107,16 +106,15 @@ public final class GlProgram {
GlUtil.checkGlError(); GlUtil.checkGlError();
} }
private static void addShader(int programId, int type, String glsl) { private static void addShader(int programId, int type, String glsl) throws GlUtil.GlException {
int shader = GLES20.glCreateShader(type); int shader = GLES20.glCreateShader(type);
GLES20.glShaderSource(shader, glsl); GLES20.glShaderSource(shader, glsl);
GLES20.glCompileShader(shader); GLES20.glCompileShader(shader);
int[] result = new int[] {GLES20.GL_FALSE}; int[] result = new int[] {GLES20.GL_FALSE};
GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, result, /* offset= */ 0); GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, result, /* offset= */ 0);
if (result[0] != GLES20.GL_TRUE) { GlUtil.checkGlException(
GlUtil.throwGlException(GLES20.glGetShaderInfoLog(shader) + ", source: " + glsl); result[0] == GLES20.GL_TRUE, GLES20.glGetShaderInfoLog(shader) + ", source: " + glsl);
}
GLES20.glAttachShader(programId, shader); GLES20.glAttachShader(programId, shader);
GLES20.glDeleteShader(shader); GLES20.glDeleteShader(shader);
...@@ -146,13 +144,13 @@ public final class GlProgram { ...@@ -146,13 +144,13 @@ public final class GlProgram {
* *
* <p>Call this in the rendering loop to switch between different programs. * <p>Call this in the rendering loop to switch between different programs.
*/ */
public void use() { public void use() throws GlUtil.GlException {
GLES20.glUseProgram(programId); GLES20.glUseProgram(programId);
GlUtil.checkGlError(); GlUtil.checkGlError();
} }
/** Deletes the program. Deleted programs cannot be used again. */ /** Deletes the program. Deleted programs cannot be used again. */
public void delete() { public void delete() throws GlUtil.GlException {
GLES20.glDeleteProgram(programId); GLES20.glDeleteProgram(programId);
GlUtil.checkGlError(); GlUtil.checkGlError();
} }
...@@ -161,7 +159,7 @@ public final class GlProgram { ...@@ -161,7 +159,7 @@ public final class GlProgram {
* Returns the location of an {@link Attribute}, which has been enabled as a vertex attribute * Returns the location of an {@link Attribute}, which has been enabled as a vertex attribute
* array. * array.
*/ */
public int getAttributeArrayLocationAndEnable(String attributeName) { public int getAttributeArrayLocationAndEnable(String attributeName) throws GlUtil.GlException {
int location = getAttributeLocation(attributeName); int location = getAttributeLocation(attributeName);
GLES20.glEnableVertexAttribArray(location); GLES20.glEnableVertexAttribArray(location);
GlUtil.checkGlError(); GlUtil.checkGlError();
...@@ -196,7 +194,7 @@ public final class GlProgram { ...@@ -196,7 +194,7 @@ public final class GlProgram {
} }
/** Binds all attributes and uniforms in the program. */ /** Binds all attributes and uniforms in the program. */
public void bindAttributesAndUniforms() { public void bindAttributesAndUniforms() throws GlUtil.GlException {
for (Attribute attribute : attributes) { for (Attribute attribute : attributes) {
attribute.bind(); attribute.bind();
} }
...@@ -277,7 +275,7 @@ public final class GlProgram { ...@@ -277,7 +275,7 @@ public final class GlProgram {
* *
* <p>Should be called before each drawing call. * <p>Should be called before each drawing call.
*/ */
public void bind() { public void bind() throws GlUtil.GlException {
Buffer buffer = checkNotNull(this.buffer, "call setBuffer before bind"); Buffer buffer = checkNotNull(this.buffer, "call setBuffer before bind");
GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, /* buffer= */ 0); GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, /* buffer= */ 0);
GLES20.glVertexAttribPointer( GLES20.glVertexAttribPointer(
...@@ -363,7 +361,7 @@ public final class GlProgram { ...@@ -363,7 +361,7 @@ public final class GlProgram {
* *
* <p>Should be called before each drawing call. * <p>Should be called before each drawing call.
*/ */
public void bind() { public void bind() throws GlUtil.GlException {
switch (type) { switch (type) {
case GLES20.GL_FLOAT: case GLES20.GL_FLOAT:
GLES20.glUniform1fv(location, /* count= */ 1, value, /* offset= */ 0); GLES20.glUniform1fv(location, /* count= */ 1, value, /* offset= */ 0);
......
...@@ -25,8 +25,8 @@ import android.net.ConnectivityManager; ...@@ -25,8 +25,8 @@ import android.net.ConnectivityManager;
import android.net.NetworkInfo; import android.net.NetworkInfo;
import android.os.Handler; import android.os.Handler;
import android.os.Looper; import android.os.Looper;
import android.telephony.PhoneStateListener; import android.telephony.TelephonyCallback;
import android.telephony.ServiceState; import android.telephony.TelephonyCallback.DisplayInfoListener;
import android.telephony.TelephonyDisplayInfo; import android.telephony.TelephonyDisplayInfo;
import android.telephony.TelephonyManager; import android.telephony.TelephonyManager;
import androidx.annotation.GuardedBy; import androidx.annotation.GuardedBy;
...@@ -59,24 +59,6 @@ public final class NetworkTypeObserver { ...@@ -59,24 +59,6 @@ public final class NetworkTypeObserver {
void onNetworkTypeChanged(@C.NetworkType int networkType); void onNetworkTypeChanged(@C.NetworkType int networkType);
} }
/*
* Static configuration that may need to be set at app startup time is located in a separate
* static Config class. This allows apps to set their desired config without incurring unnecessary
* class loading costs during startup.
*/
/** Configuration for {@link NetworkTypeObserver}. */
public static final class Config {
private static volatile boolean disable5GNsaDisambiguation;
/** Disables logic to disambiguate 5G-NSA networks from 4G networks. */
public static void disable5GNsaDisambiguation() {
disable5GNsaDisambiguation = true;
}
private Config() {}
}
@Nullable private static NetworkTypeObserver staticInstance; @Nullable private static NetworkTypeObserver staticInstance;
private final Handler mainHandler; private final Handler mainHandler;
...@@ -232,55 +214,51 @@ public final class NetworkTypeObserver { ...@@ -232,55 +214,51 @@ public final class NetworkTypeObserver {
@Override @Override
public void onReceive(Context context, Intent intent) { public void onReceive(Context context, Intent intent) {
@C.NetworkType int networkType = getNetworkTypeFromConnectivityManager(context); @C.NetworkType int networkType = getNetworkTypeFromConnectivityManager(context);
if (Util.SDK_INT >= 29 if (Util.SDK_INT >= 31 && networkType == C.NETWORK_TYPE_4G) {
&& !Config.disable5GNsaDisambiguation
&& networkType == C.NETWORK_TYPE_4G) {
// Delay update of the network type to check whether this is actually 5G-NSA. // Delay update of the network type to check whether this is actually 5G-NSA.
Api31.disambiguate4gAnd5gNsa(context, /* instance= */ NetworkTypeObserver.this);
} else {
updateNetworkType(networkType);
}
}
}
@RequiresApi(31)
private static final class Api31 {
public static void disambiguate4gAnd5gNsa(Context context, NetworkTypeObserver instance) {
try { try {
// We can't access TelephonyManager getters like getServiceState() directly as they
// require special permissions. Attaching a listener is permission-free because the
// callback data is censored to not include sensitive information.
TelephonyManager telephonyManager = TelephonyManager telephonyManager =
checkNotNull((TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE)); checkNotNull((TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE));
TelephonyManagerListener listener = new TelephonyManagerListener(); DisplayInfoCallback callback = new DisplayInfoCallback(instance);
if (Util.SDK_INT < 31) { telephonyManager.registerTelephonyCallback(context.getMainExecutor(), callback);
telephonyManager.listen(listener, PhoneStateListener.LISTEN_SERVICE_STATE);
} else {
// Display info information can only be requested without permission from API 31.
telephonyManager.listen(listener, PhoneStateListener.LISTEN_DISPLAY_INFO_CHANGED);
}
// We are only interested in the initial response with the current state, so unregister // We are only interested in the initial response with the current state, so unregister
// the listener immediately. // the listener immediately.
telephonyManager.listen(listener, PhoneStateListener.LISTEN_NONE); telephonyManager.unregisterTelephonyCallback(callback);
return;
} catch (RuntimeException e) { } catch (RuntimeException e) {
// Ignore problems with listener registration and keep reporting as 4G. // Ignore problems with listener registration and keep reporting as 4G.
} instance.updateNetworkType(C.NETWORK_TYPE_4G);
}
updateNetworkType(networkType);
} }
} }
private class TelephonyManagerListener extends PhoneStateListener { private static final class DisplayInfoCallback extends TelephonyCallback
implements DisplayInfoListener {
@Override private final NetworkTypeObserver instance;
public void onServiceStateChanged(@Nullable ServiceState serviceState) {
// This workaround to check the toString output of ServiceState only works on API 29 and 30. public DisplayInfoCallback(NetworkTypeObserver instance) {
String serviceStateString = serviceState == null ? "" : serviceState.toString(); this.instance = instance;
boolean is5gNsa =
serviceStateString.contains("nrState=CONNECTED")
|| serviceStateString.contains("nrState=NOT_RESTRICTED");
updateNetworkType(is5gNsa ? C.NETWORK_TYPE_5G_NSA : C.NETWORK_TYPE_4G);
} }
@RequiresApi(31)
@Override @Override
public void onDisplayInfoChanged(TelephonyDisplayInfo telephonyDisplayInfo) { public void onDisplayInfoChanged(TelephonyDisplayInfo telephonyDisplayInfo) {
int overrideNetworkType = telephonyDisplayInfo.getOverrideNetworkType(); int overrideNetworkType = telephonyDisplayInfo.getOverrideNetworkType();
boolean is5gNsa = boolean is5gNsa =
overrideNetworkType == TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA overrideNetworkType == TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA
|| overrideNetworkType == TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA_MMWAVE; || overrideNetworkType == TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA_MMWAVE
updateNetworkType(is5gNsa ? C.NETWORK_TYPE_5G_NSA : C.NETWORK_TYPE_4G); || overrideNetworkType == TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_ADVANCED;
instance.updateNetworkType(is5gNsa ? C.NETWORK_TYPE_5G_NSA : C.NETWORK_TYPE_4G);
}
} }
} }
} }
...@@ -29,7 +29,7 @@ public class AudioAttributesTest { ...@@ -29,7 +29,7 @@ public class AudioAttributesTest {
public void roundTripViaBundle_yieldsEqualInstance() { public void roundTripViaBundle_yieldsEqualInstance() {
AudioAttributes audioAttributes = AudioAttributes audioAttributes =
new AudioAttributes.Builder() new AudioAttributes.Builder()
.setContentType(C.CONTENT_TYPE_SONIFICATION) .setContentType(C.AUDIO_CONTENT_TYPE_SONIFICATION)
.setFlags(C.FLAG_AUDIBILITY_ENFORCED) .setFlags(C.FLAG_AUDIBILITY_ENFORCED)
.setUsage(C.USAGE_ALARM) .setUsage(C.USAGE_ALARM)
.setAllowedCapturePolicy(C.ALLOW_CAPTURE_BY_SYSTEM) .setAllowedCapturePolicy(C.ALLOW_CAPTURE_BY_SYSTEM)
......
...@@ -18,22 +18,17 @@ package androidx.media3.common; ...@@ -18,22 +18,17 @@ package androidx.media3.common;
import static androidx.media3.common.Player.EVENT_IS_PLAYING_CHANGED; import static androidx.media3.common.Player.EVENT_IS_PLAYING_CHANGED;
import static androidx.media3.common.Player.EVENT_MEDIA_ITEM_TRANSITION; import static androidx.media3.common.Player.EVENT_MEDIA_ITEM_TRANSITION;
import static androidx.media3.common.Player.EVENT_TIMELINE_CHANGED; import static androidx.media3.common.Player.EVENT_TIMELINE_CHANGED;
import static androidx.media3.common.util.Assertions.checkArgument;
import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.same; import static org.mockito.ArgumentMatchers.same;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import androidx.media3.test.utils.StubPlayer; import androidx.media3.test.utils.StubPlayer;
import androidx.media3.test.utils.TestUtil;
import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.ext.junit.runners.AndroidJUnit4;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Queue;
import java.util.Set; import java.util.Set;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
...@@ -105,7 +100,7 @@ public class ForwardingPlayerTest { ...@@ -105,7 +100,7 @@ public class ForwardingPlayerTest {
@Test @Test
public void forwardingPlayer_overridesAllPlayerMethods() throws Exception { public void forwardingPlayer_overridesAllPlayerMethods() throws Exception {
// Check with reflection that ForwardingPlayer overrides all Player methods. // Check with reflection that ForwardingPlayer overrides all Player methods.
List<Method> methods = getPublicMethods(Player.class); List<Method> methods = TestUtil.getPublicMethods(Player.class);
for (Method method : methods) { for (Method method : methods) {
assertThat( assertThat(
ForwardingPlayer.class ForwardingPlayer.class
...@@ -119,7 +114,7 @@ public class ForwardingPlayerTest { ...@@ -119,7 +114,7 @@ public class ForwardingPlayerTest {
public void forwardingListener_overridesAllListenerMethods() throws Exception { public void forwardingListener_overridesAllListenerMethods() throws Exception {
// Check with reflection that ForwardingListener overrides all Listener methods. // Check with reflection that ForwardingListener overrides all Listener methods.
Class<?> forwardingListenerClass = getInnerClass("ForwardingListener"); Class<?> forwardingListenerClass = getInnerClass("ForwardingListener");
List<Method> methods = getPublicMethods(Player.Listener.class); List<Method> methods = TestUtil.getPublicMethods(Player.Listener.class);
for (Method method : methods) { for (Method method : methods) {
assertThat( assertThat(
forwardingListenerClass forwardingListenerClass
...@@ -129,32 +124,6 @@ public class ForwardingPlayerTest { ...@@ -129,32 +124,6 @@ public class ForwardingPlayerTest {
} }
} }
/** Returns all the public methods of a Java interface. */
private static List<Method> getPublicMethods(Class<?> anInterface) {
checkArgument(anInterface.isInterface());
// Run a BFS over all extended interfaces to inspect them all.
Queue<Class<?>> interfacesQueue = new ArrayDeque<>();
interfacesQueue.add(anInterface);
Set<Class<?>> interfaces = new HashSet<>();
while (!interfacesQueue.isEmpty()) {
Class<?> currentInterface = interfacesQueue.remove();
if (interfaces.add(currentInterface)) {
Collections.addAll(interfacesQueue, currentInterface.getInterfaces());
}
}
List<Method> list = new ArrayList<>();
for (Class<?> currentInterface : interfaces) {
for (Method method : currentInterface.getDeclaredMethods()) {
if (Modifier.isPublic(method.getModifiers())) {
list.add(method);
}
}
}
return list;
}
private static Class<?> getInnerClass(String className) { private static Class<?> getInnerClass(String className) {
for (Class<?> innerClass : ForwardingPlayer.class.getDeclaredClasses()) { for (Class<?> innerClass : ForwardingPlayer.class.getDeclaredClasses()) {
if (innerClass.getSimpleName().equals(className)) { if (innerClass.getSimpleName().equals(className)) {
......
...@@ -19,6 +19,8 @@ import static com.google.common.truth.Truth.assertThat; ...@@ -19,6 +19,8 @@ import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertThrows;
import android.net.Uri; import android.net.Uri;
import android.os.Bundle;
import androidx.media3.common.MediaItem.RequestMetadata;
import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap;
...@@ -223,7 +225,7 @@ public class MediaItemTest { ...@@ -223,7 +225,7 @@ public class MediaItemTest {
new MediaItem.DrmConfiguration.Builder(C.WIDEVINE_UUID) new MediaItem.DrmConfiguration.Builder(C.WIDEVINE_UUID)
.setLicenseUri(licenseUri) .setLicenseUri(licenseUri)
.setForcedSessionTrackTypes(ImmutableList.of(C.TRACK_TYPE_AUDIO)) .setForcedSessionTrackTypes(ImmutableList.of(C.TRACK_TYPE_AUDIO))
.forceSessionsForAudioAndVideoTracks(true) .setForceSessionsForAudioAndVideoTracks(true)
.build(); .build();
assertThat(drmConfiguration.sessionForClearTypes) assertThat(drmConfiguration.sessionForClearTypes)
...@@ -580,6 +582,24 @@ public class MediaItemTest { ...@@ -580,6 +582,24 @@ public class MediaItemTest {
} }
@Test @Test
public void builder_setRequestMetadata_setsRequestMetadata() {
Bundle extras = new Bundle();
extras.putString("key", "value");
RequestMetadata requestMetadata =
new RequestMetadata.Builder()
.setMediaUri(Uri.parse("http://test.test"))
.setSearchQuery("Play media!")
.setExtras(extras)
.build();
MediaItem mediaItem =
new MediaItem.Builder().setMediaId("mediaID").setRequestMetadata(requestMetadata).build();
assertThat(mediaItem.requestMetadata).isEqualTo(requestMetadata);
assertThat(mediaItem.requestMetadata.extras.getString("key")).isEqualTo("value");
}
@Test
@SuppressWarnings("deprecation") // Testing deprecated setter methods @SuppressWarnings("deprecation") // Testing deprecated setter methods
public void buildUpon_individualSetters_equalsToOriginal() { public void buildUpon_individualSetters_equalsToOriginal() {
MediaItem mediaItem = MediaItem mediaItem =
...@@ -677,6 +697,11 @@ public class MediaItemTest { ...@@ -677,6 +697,11 @@ public class MediaItemTest {
.setLabel("label") .setLabel("label")
.setId("id") .setId("id")
.build())) .build()))
.setRequestMetadata(
new RequestMetadata.Builder()
.setMediaUri(Uri.parse("http://test.test"))
.setSearchQuery("search")
.build())
.setTag(new Object()) .setTag(new Object())
.build(); .build();
...@@ -704,6 +729,11 @@ public class MediaItemTest { ...@@ -704,6 +729,11 @@ public class MediaItemTest {
.setClipRelativeToDefaultPosition(true) .setClipRelativeToDefaultPosition(true)
.setClipRelativeToLiveWindow(true) .setClipRelativeToLiveWindow(true)
.setClipStartsAtKeyFrame(true) .setClipStartsAtKeyFrame(true)
.setRequestMetadata(
new RequestMetadata.Builder()
.setMediaUri(Uri.parse("http://test.test"))
.setSearchQuery("search")
.build())
.build(); .build();
assertThat(mediaItem.localConfiguration).isNull(); assertThat(mediaItem.localConfiguration).isNull();
......
...@@ -41,7 +41,6 @@ public class MediaMetadataTest { ...@@ -41,7 +41,6 @@ public class MediaMetadataTest {
assertThat(mediaMetadata.displayTitle).isNull(); assertThat(mediaMetadata.displayTitle).isNull();
assertThat(mediaMetadata.subtitle).isNull(); assertThat(mediaMetadata.subtitle).isNull();
assertThat(mediaMetadata.description).isNull(); assertThat(mediaMetadata.description).isNull();
assertThat(mediaMetadata.mediaUri).isNull();
assertThat(mediaMetadata.userRating).isNull(); assertThat(mediaMetadata.userRating).isNull();
assertThat(mediaMetadata.overallRating).isNull(); assertThat(mediaMetadata.overallRating).isNull();
assertThat(mediaMetadata.artworkData).isNull(); assertThat(mediaMetadata.artworkData).isNull();
...@@ -127,7 +126,6 @@ public class MediaMetadataTest { ...@@ -127,7 +126,6 @@ public class MediaMetadataTest {
.setDisplayTitle("display title") .setDisplayTitle("display title")
.setSubtitle("subtitle") .setSubtitle("subtitle")
.setDescription("description") .setDescription("description")
.setMediaUri(Uri.parse("https://www.google.com"))
.setUserRating(new HeartRating(false)) .setUserRating(new HeartRating(false))
.setOverallRating(new PercentageRating(87.4f)) .setOverallRating(new PercentageRating(87.4f))
.setArtworkData( .setArtworkData(
......
...@@ -201,7 +201,7 @@ public final class TrackSelectionParametersTest { ...@@ -201,7 +201,7 @@ public final class TrackSelectionParametersTest {
new TrackSelectionParameters.Builder(getApplicationContext()).addOverride(override).build(); new TrackSelectionParameters.Builder(getApplicationContext()).addOverride(override).build();
TrackSelectionParameters fromBundle = TrackSelectionParameters fromBundle =
TrackSelectionParameters.CREATOR.fromBundle(trackSelectionParameters.toBundle()); TrackSelectionParameters.fromBundle(trackSelectionParameters.toBundle());
assertThat(fromBundle).isEqualTo(trackSelectionParameters); assertThat(fromBundle).isEqualTo(trackSelectionParameters);
assertThat(trackSelectionParameters.overrides) assertThat(trackSelectionParameters.overrides)
......
/*
* 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 androidx.media3.common.text;
import static com.google.common.truth.Truth.assertThat;
import android.graphics.Bitmap;
import android.os.Bundle;
import android.os.Parcel;
import android.text.SpannedString;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.google.common.collect.ImmutableList;
import org.junit.Test;
import org.junit.runner.RunWith;
/** Tests for {@link CueGroup}. */
@RunWith(AndroidJUnit4.class)
public class CueGroupTest {
@Test
public void bundleAndUnBundleCueGroup() {
Cue textCue = new Cue.Builder().setText(SpannedString.valueOf("text")).build();
Cue bitmapCue =
new Cue.Builder().setBitmap(Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888)).build();
ImmutableList<Cue> cues = ImmutableList.of(textCue, bitmapCue);
CueGroup cueGroup = new CueGroup(cues);
Parcel parcel = Parcel.obtain();
try {
parcel.writeBundle(cueGroup.toBundle());
parcel.setDataPosition(0);
Bundle bundle = parcel.readBundle();
CueGroup filteredCueGroup = CueGroup.CREATOR.fromBundle(bundle);
assertThat(filteredCueGroup.cues).containsExactly(textCue);
} finally {
parcel.recycle();
}
}
}
...@@ -103,50 +103,109 @@ public class UtilTest { ...@@ -103,50 +103,109 @@ public class UtilTest {
@Test @Test
public void inferContentType_handlesHlsIsmUris() { public void inferContentType_handlesHlsIsmUris() {
assertThat(Util.inferContentType("http://a.b/c.ism/manifest(format=m3u8-aapl)")) assertThat(Util.inferContentType(Uri.parse("http://a.b/c.ism/manifest(format=m3u8-aapl)")))
.isEqualTo(C.TYPE_HLS); .isEqualTo(C.CONTENT_TYPE_HLS);
assertThat(Util.inferContentType("http://a.b/c.ism/manifest(format=m3u8-aapl,quality=hd)")) assertThat(
.isEqualTo(C.TYPE_HLS); Util.inferContentType(
assertThat(Util.inferContentType("http://a.b/c.ism/manifest(quality=hd,format=m3u8-aapl)")) Uri.parse("http://a.b/c.ism/manifest(format=m3u8-aapl,quality=hd)")))
.isEqualTo(C.TYPE_HLS); .isEqualTo(C.CONTENT_TYPE_HLS);
assertThat(
Util.inferContentType(
Uri.parse("http://a.b/c.ism/manifest(quality=hd,format=m3u8-aapl)")))
.isEqualTo(C.CONTENT_TYPE_HLS);
} }
@Test @Test
public void inferContentType_handlesHlsIsmV3Uris() { public void inferContentType_handlesHlsIsmV3Uris() {
assertThat(Util.inferContentType("http://a.b/c.ism/manifest(format=m3u8-aapl-v3)")) assertThat(Util.inferContentType(Uri.parse("http://a.b/c.ism/manifest(format=m3u8-aapl-v3)")))
.isEqualTo(C.TYPE_HLS); .isEqualTo(C.CONTENT_TYPE_HLS);
assertThat(Util.inferContentType("http://a.b/c.ism/manifest(format=m3u8-aapl-v3,quality=hd)")) assertThat(
.isEqualTo(C.TYPE_HLS); Util.inferContentType(
assertThat(Util.inferContentType("http://a.b/c.ism/manifest(quality=hd,format=m3u8-aapl-v3)")) Uri.parse("http://a.b/c.ism/manifest(format=m3u8-aapl-v3,quality=hd)")))
.isEqualTo(C.TYPE_HLS); .isEqualTo(C.CONTENT_TYPE_HLS);
assertThat(
Util.inferContentType(
Uri.parse("http://a.b/c.ism/manifest(quality=hd,format=m3u8-aapl-v3)")))
.isEqualTo(C.CONTENT_TYPE_HLS);
} }
@Test @Test
public void inferContentType_handlesDashIsmUris() { public void inferContentType_handlesDashIsmUris() {
assertThat(Util.inferContentType("http://a.b/c.isml/manifest(format=mpd-time-csf)")) assertThat(Util.inferContentType(Uri.parse("http://a.b/c.isml/manifest(format=mpd-time-csf)")))
.isEqualTo(C.TYPE_DASH); .isEqualTo(C.CONTENT_TYPE_DASH);
assertThat(Util.inferContentType("http://a.b/c.isml/manifest(format=mpd-time-csf,quality=hd)")) assertThat(
.isEqualTo(C.TYPE_DASH); Util.inferContentType(
assertThat(Util.inferContentType("http://a.b/c.isml/manifest(quality=hd,format=mpd-time-csf)")) Uri.parse("http://a.b/c.isml/manifest(format=mpd-time-csf,quality=hd)")))
.isEqualTo(C.TYPE_DASH); .isEqualTo(C.CONTENT_TYPE_DASH);
assertThat(
Util.inferContentType(
Uri.parse("http://a.b/c.isml/manifest(quality=hd,format=mpd-time-csf)")))
.isEqualTo(C.CONTENT_TYPE_DASH);
} }
@Test @Test
public void inferContentType_handlesSmoothStreamingIsmUris() { public void inferContentType_handlesSmoothStreamingIsmUris() {
assertThat(Util.inferContentType("http://a.b/c.ism")).isEqualTo(C.TYPE_SS); assertThat(Util.inferContentType(Uri.parse("http://a.b/c.ism"))).isEqualTo(C.CONTENT_TYPE_SS);
assertThat(Util.inferContentType("http://a.b/c.isml")).isEqualTo(C.TYPE_SS); assertThat(Util.inferContentType(Uri.parse("http://a.b/c.isml"))).isEqualTo(C.CONTENT_TYPE_SS);
assertThat(Util.inferContentType("http://a.b/c.ism/")).isEqualTo(C.TYPE_SS); assertThat(Util.inferContentType(Uri.parse("http://a.b/c.ism/"))).isEqualTo(C.CONTENT_TYPE_SS);
assertThat(Util.inferContentType("http://a.b/c.isml/")).isEqualTo(C.TYPE_SS); assertThat(Util.inferContentType(Uri.parse("http://a.b/c.isml/"))).isEqualTo(C.CONTENT_TYPE_SS);
assertThat(Util.inferContentType("http://a.b/c.ism/Manifest")).isEqualTo(C.TYPE_SS); assertThat(Util.inferContentType(Uri.parse("http://a.b/c.ism/Manifest")))
assertThat(Util.inferContentType("http://a.b/c.isml/manifest")).isEqualTo(C.TYPE_SS); .isEqualTo(C.CONTENT_TYPE_SS);
assertThat(Util.inferContentType("http://a.b/c.isml/manifest(filter=x)")).isEqualTo(C.TYPE_SS); assertThat(Util.inferContentType(Uri.parse("http://a.b/c.isml/manifest")))
assertThat(Util.inferContentType("http://a.b/c.isml/manifest_hd")).isEqualTo(C.TYPE_SS); .isEqualTo(C.CONTENT_TYPE_SS);
assertThat(Util.inferContentType(Uri.parse("http://a.b/c.isml/manifest(filter=x)")))
.isEqualTo(C.CONTENT_TYPE_SS);
assertThat(Util.inferContentType(Uri.parse("http://a.b/c.isml/manifest_hd")))
.isEqualTo(C.CONTENT_TYPE_SS);
} }
@Test @Test
public void inferContentType_handlesOtherIsmUris() { public void inferContentType_handlesOtherIsmUris() {
assertThat(Util.inferContentType("http://a.b/c.ism/video.mp4")).isEqualTo(C.TYPE_OTHER); assertThat(Util.inferContentType(Uri.parse("http://a.b/c.ism/video.mp4")))
assertThat(Util.inferContentType("http://a.b/c.ism/prefix-manifest")).isEqualTo(C.TYPE_OTHER); .isEqualTo(C.CONTENT_TYPE_OTHER);
assertThat(Util.inferContentType(Uri.parse("http://a.b/c.ism/prefix-manifest")))
.isEqualTo(C.CONTENT_TYPE_OTHER);
}
/**
* Test that the deprecated {@link Util#inferContentType(String)} works when passed only a file
* extension and the leading dot.
*/
@SuppressWarnings("deprecation")
@Test
public void inferContentType_extensionAsPath() {
assertThat(Util.inferContentType(".m3u8")).isEqualTo(C.CONTENT_TYPE_HLS);
assertThat(Util.inferContentType(".mpd")).isEqualTo(C.CONTENT_TYPE_DASH);
assertThat(Util.inferContentType(".ism")).isEqualTo(C.TYPE_SS);
assertThat(Util.inferContentType(".isml")).isEqualTo(C.TYPE_SS);
assertThat(Util.inferContentType(".mp4")).isEqualTo(C.CONTENT_TYPE_OTHER);
}
// Testing deprecated method.
@SuppressWarnings("deprecation")
@Test
public void inferContentType_extensionOverride() {
assertThat(
Util.inferContentType(
Uri.parse("file:///path/to/something.mpd"), /* overrideExtension= */ null))
.isEqualTo(C.CONTENT_TYPE_DASH);
assertThat(
Util.inferContentType(
Uri.parse("file:///path/to/something.mpd"), /* overrideExtension= */ ""))
.isEqualTo(C.CONTENT_TYPE_DASH);
assertThat(
Util.inferContentType(
Uri.parse("file:///path/to/something.mpd"), /* overrideExtension= */ "m3u8"))
.isEqualTo(C.CONTENT_TYPE_HLS);
}
@Test
public void inferContentTypeForExtension() {
assertThat(Util.inferContentTypeForExtension("m3u8")).isEqualTo(C.CONTENT_TYPE_HLS);
assertThat(Util.inferContentTypeForExtension("mpd")).isEqualTo(C.CONTENT_TYPE_DASH);
assertThat(Util.inferContentTypeForExtension("ism")).isEqualTo(C.TYPE_SS);
assertThat(Util.inferContentTypeForExtension("isml")).isEqualTo(C.TYPE_SS);
assertThat(Util.inferContentTypeForExtension("mp4")).isEqualTo(C.CONTENT_TYPE_OTHER);
} }
@Test @Test
...@@ -1103,6 +1162,7 @@ public class UtilTest { ...@@ -1103,6 +1162,7 @@ public class UtilTest {
assertThat(Util.normalizeLanguageCode("ara-ayl")).isEqualTo("ar-ayl"); assertThat(Util.normalizeLanguageCode("ara-ayl")).isEqualTo("ar-ayl");
// Special case of short codes that are actually part of a macrolanguage. // Special case of short codes that are actually part of a macrolanguage.
assertThat(Util.normalizeLanguageCode("arb")).isEqualTo("ar-arb");
assertThat(Util.normalizeLanguageCode("nb")).isEqualTo("no-nob"); assertThat(Util.normalizeLanguageCode("nb")).isEqualTo("no-nob");
assertThat(Util.normalizeLanguageCode("nn")).isEqualTo("no-nno"); assertThat(Util.normalizeLanguageCode("nn")).isEqualTo("no-nno");
assertThat(Util.normalizeLanguageCode("nob")).isEqualTo("no-nob"); assertThat(Util.normalizeLanguageCode("nob")).isEqualTo("no-nob");
......
...@@ -21,19 +21,13 @@ import static java.lang.Math.min; ...@@ -21,19 +21,13 @@ import static java.lang.Math.min;
import android.content.ContentResolver; import android.content.ContentResolver;
import android.content.Context; import android.content.Context;
import android.content.res.AssetFileDescriptor; import android.content.res.AssetFileDescriptor;
import android.media.ApplicationMediaCapabilities;
import android.media.MediaFeature;
import android.media.MediaFormat;
import android.net.Uri; import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.provider.MediaStore; import android.provider.MediaStore;
import androidx.annotation.DoNotInline;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.media3.common.C; import androidx.media3.common.C;
import androidx.media3.common.PlaybackException; import androidx.media3.common.PlaybackException;
import androidx.media3.common.util.UnstableApi; import androidx.media3.common.util.UnstableApi;
import androidx.media3.common.util.Util;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
import java.io.IOException; import java.io.IOException;
...@@ -78,6 +72,7 @@ public final class ContentDataSource extends BaseDataSource { ...@@ -78,6 +72,7 @@ public final class ContentDataSource extends BaseDataSource {
} }
@Override @Override
@SuppressWarnings("InlinedApi") // We are inlining EXTRA_ACCEPT_ORIGINAL_MEDIA_FORMAT.
public long open(DataSpec dataSpec) throws ContentDataSourceException { public long open(DataSpec dataSpec) throws ContentDataSourceException {
try { try {
Uri uri = dataSpec.uri; Uri uri = dataSpec.uri;
...@@ -88,9 +83,8 @@ public final class ContentDataSource extends BaseDataSource { ...@@ -88,9 +83,8 @@ public final class ContentDataSource extends BaseDataSource {
AssetFileDescriptor assetFileDescriptor; AssetFileDescriptor assetFileDescriptor;
if ("content".equals(dataSpec.uri.getScheme())) { if ("content".equals(dataSpec.uri.getScheme())) {
Bundle providerOptions = new Bundle(); Bundle providerOptions = new Bundle();
if (Util.SDK_INT >= 31) { // We don't want compatible media transcoding.
Api31.disableTranscoding(providerOptions); providerOptions.putBoolean(MediaStore.EXTRA_ACCEPT_ORIGINAL_MEDIA_FORMAT, true);
}
assetFileDescriptor = assetFileDescriptor =
resolver.openTypedAssetFileDescriptor(uri, /* mimeType= */ "*/*", providerOptions); resolver.openTypedAssetFileDescriptor(uri, /* mimeType= */ "*/*", providerOptions);
} else { } else {
...@@ -232,21 +226,4 @@ public final class ContentDataSource extends BaseDataSource { ...@@ -232,21 +226,4 @@ public final class ContentDataSource extends BaseDataSource {
} }
} }
} }
@RequiresApi(31)
private static final class Api31 {
@DoNotInline
public static void disableTranscoding(Bundle providerOptions) {
ApplicationMediaCapabilities mediaCapabilities =
new ApplicationMediaCapabilities.Builder()
.addSupportedVideoMimeType(MediaFormat.MIMETYPE_VIDEO_HEVC)
.addSupportedHdrType(MediaFeature.HdrType.DOLBY_VISION)
.addSupportedHdrType(MediaFeature.HdrType.HDR10)
.addSupportedHdrType(MediaFeature.HdrType.HDR10_PLUS)
.addSupportedHdrType(MediaFeature.HdrType.HLG)
.build();
providerOptions.putParcelable(MediaStore.EXTRA_MEDIA_CAPABILITIES, mediaCapabilities);
}
}
} }
...@@ -418,7 +418,7 @@ public interface HttpDataSource extends DataSource { ...@@ -418,7 +418,7 @@ public interface HttpDataSource extends DataSource {
@Nullable public final String responseMessage; @Nullable public final String responseMessage;
/** An unmodifiable map of the response header fields and values. */ /** An unmodifiable map of the response header fields and values. */
public final Map<String, List<String>> headerFields; @UnstableApi public final Map<String, List<String>> headerFields;
/** The response body. */ /** The response body. */
public final byte[] responseBody; public final byte[] responseBody;
......
...@@ -481,11 +481,7 @@ public class CronetDataSource extends BaseDataSource implements HttpDataSource { ...@@ -481,11 +481,7 @@ public class CronetDataSource extends BaseDataSource implements HttpDataSource {
} }
/** /**
* Sets a content type {@link Predicate}. If a content type is rejected by the predicate then a * @deprecated Use {@link CronetDataSource.Factory#setContentTypePredicate(Predicate)} instead.
* {@link HttpDataSource.InvalidContentTypeException} is thrown from {@link #open(DataSpec)}.
*
* @param contentTypePredicate The content type {@link Predicate}, or {@code null} to clear a
* predicate that was previously set.
*/ */
@UnstableApi @UnstableApi
@Deprecated @Deprecated
......
...@@ -32,10 +32,14 @@ import androidx.media3.datasource.DataSource; ...@@ -32,10 +32,14 @@ import androidx.media3.datasource.DataSource;
import androidx.media3.datasource.DataSourceException; import androidx.media3.datasource.DataSourceException;
import androidx.media3.datasource.DataSpec; import androidx.media3.datasource.DataSpec;
import androidx.media3.datasource.HttpDataSource; import androidx.media3.datasource.HttpDataSource;
import androidx.media3.datasource.HttpDataSource.HttpDataSourceException;
import androidx.media3.datasource.HttpDataSource.InvalidContentTypeException;
import androidx.media3.datasource.HttpDataSource.InvalidResponseCodeException;
import androidx.media3.datasource.HttpUtil; import androidx.media3.datasource.HttpUtil;
import androidx.media3.datasource.TransferListener; import androidx.media3.datasource.TransferListener;
import com.google.common.base.Predicate; import com.google.common.base.Predicate;
import com.google.common.net.HttpHeaders; import com.google.common.net.HttpHeaders;
import com.google.common.util.concurrent.SettableFuture;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.InterruptedIOException; import java.io.InterruptedIOException;
...@@ -43,8 +47,10 @@ import java.util.Collections; ...@@ -43,8 +47,10 @@ import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.concurrent.ExecutionException;
import okhttp3.CacheControl; import okhttp3.CacheControl;
import okhttp3.Call; import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.HttpUrl; import okhttp3.HttpUrl;
import okhttp3.MediaType; import okhttp3.MediaType;
import okhttp3.OkHttpClient; import okhttp3.OkHttpClient;
...@@ -299,8 +305,9 @@ public class OkHttpDataSource extends BaseDataSource implements HttpDataSource { ...@@ -299,8 +305,9 @@ public class OkHttpDataSource extends BaseDataSource implements HttpDataSource {
Request request = makeRequest(dataSpec); Request request = makeRequest(dataSpec);
Response response; Response response;
ResponseBody responseBody; ResponseBody responseBody;
Call call = callFactory.newCall(request);
try { try {
this.response = callFactory.newCall(request).execute(); this.response = executeCall(call);
response = this.response; response = this.response;
responseBody = Assertions.checkNotNull(response.body()); responseBody = Assertions.checkNotNull(response.body());
responseByteStream = responseBody.byteStream(); responseByteStream = responseBody.byteStream();
...@@ -449,6 +456,35 @@ public class OkHttpDataSource extends BaseDataSource implements HttpDataSource { ...@@ -449,6 +456,35 @@ public class OkHttpDataSource extends BaseDataSource implements HttpDataSource {
} }
/** /**
* This method is an interrupt safe replacement of OkHttp Call.execute() which can get in bad
* states if interrupted while writing to the shared connection socket.
*/
private Response executeCall(Call call) throws IOException {
SettableFuture<Response> future = SettableFuture.create();
call.enqueue(
new Callback() {
@Override
public void onFailure(Call call, IOException e) {
future.setException(e);
}
@Override
public void onResponse(Call call, Response response) {
future.set(response);
}
});
try {
return future.get();
} catch (InterruptedException e) {
call.cancel();
throw new InterruptedIOException();
} catch (ExecutionException ee) {
throw new IOException(ee);
}
}
/**
* Attempts to skip the specified number of bytes in full. * Attempts to skip the specified number of bytes in full.
* *
* @param bytesToSkip The number of bytes to skip. * @param bytesToSkip The number of bytes to skip.
......
...@@ -16,6 +16,7 @@ ...@@ -16,6 +16,7 @@
package androidx.media3.decoder.opus; package androidx.media3.decoder.opus;
import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assume.assumeTrue;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.media3.common.C; import androidx.media3.common.C;
...@@ -26,7 +27,6 @@ import androidx.test.ext.junit.runners.AndroidJUnit4; ...@@ -26,7 +27,6 @@ import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.ByteOrder; import java.nio.ByteOrder;
import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
...@@ -69,11 +69,6 @@ public final class OpusDecoderTest { ...@@ -69,11 +69,6 @@ public final class OpusDecoderTest {
private static final ImmutableList<byte[]> FULL_INITIALIZATION_DATA = private static final ImmutableList<byte[]> FULL_INITIALIZATION_DATA =
ImmutableList.of(HEADER, CUSTOM_PRE_SKIP_BYTES, CUSTOM_SEEK_PRE_ROLL_BYTES); ImmutableList.of(HEADER, CUSTOM_PRE_SKIP_BYTES, CUSTOM_SEEK_PRE_ROLL_BYTES);
@Before
public void setUp() {
assertThat(LOADER.isAvailable()).isTrue();
}
@Test @Test
public void getChannelCount() { public void getChannelCount() {
int channelCount = OpusDecoder.getChannelCount(HEADER); int channelCount = OpusDecoder.getChannelCount(HEADER);
...@@ -120,6 +115,7 @@ public final class OpusDecoderTest { ...@@ -120,6 +115,7 @@ public final class OpusDecoderTest {
@Test @Test
public void decode_removesPreSkipFromOutput() throws OpusDecoderException { public void decode_removesPreSkipFromOutput() throws OpusDecoderException {
assumeTrue(LOADER.isAvailable());
OpusDecoder decoder = OpusDecoder decoder =
new OpusDecoder( new OpusDecoder(
/* numInputBuffers= */ 0, /* numInputBuffers= */ 0,
...@@ -139,6 +135,7 @@ public final class OpusDecoderTest { ...@@ -139,6 +135,7 @@ public final class OpusDecoderTest {
@Test @Test
public void decode_whenDiscardPaddingDisabled_returnsDiscardPadding() public void decode_whenDiscardPaddingDisabled_returnsDiscardPadding()
throws OpusDecoderException { throws OpusDecoderException {
assumeTrue(LOADER.isAvailable());
OpusDecoder decoder = OpusDecoder decoder =
new OpusDecoder( new OpusDecoder(
/* numInputBuffers= */ 0, /* numInputBuffers= */ 0,
...@@ -159,6 +156,7 @@ public final class OpusDecoderTest { ...@@ -159,6 +156,7 @@ public final class OpusDecoderTest {
@Test @Test
public void decode_whenDiscardPaddingEnabled_removesDiscardPadding() throws OpusDecoderException { public void decode_whenDiscardPaddingEnabled_removesDiscardPadding() throws OpusDecoderException {
assumeTrue(LOADER.isAvailable());
OpusDecoder decoder = OpusDecoder decoder =
new OpusDecoder( new OpusDecoder(
/* numInputBuffers= */ 0, /* numInputBuffers= */ 0,
...@@ -200,8 +198,12 @@ public final class OpusDecoderTest { ...@@ -200,8 +198,12 @@ public final class OpusDecoderTest {
return ImmutableList.of(HEADER, preSkip, CUSTOM_SEEK_PRE_ROLL_BYTES); 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) { private static ByteBuffer createSupplementalData(long value) {
return ByteBuffer.allocate(8).order(ByteOrder.LITTLE_ENDIAN).putLong(value).rewind(); return (ByteBuffer)
ByteBuffer.allocate(8).order(ByteOrder.LITTLE_ENDIAN).putLong(value).rewind();
} }
private static DecoderInputBuffer createInputBuffer( private static DecoderInputBuffer createInputBuffer(
......
...@@ -25,6 +25,7 @@ import androidx.media3.common.MediaItem.SubtitleConfiguration; ...@@ -25,6 +25,7 @@ import androidx.media3.common.MediaItem.SubtitleConfiguration;
import androidx.media3.common.MimeTypes; import androidx.media3.common.MimeTypes;
import androidx.media3.common.Player; import androidx.media3.common.Player;
import androidx.media3.common.text.Cue; import androidx.media3.common.text.Cue;
import androidx.media3.common.text.CueGroup;
import androidx.media3.common.util.ConditionVariable; import androidx.media3.common.util.ConditionVariable;
import androidx.media3.exoplayer.source.ClippingMediaSource; import androidx.media3.exoplayer.source.ClippingMediaSource;
import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.ext.junit.runners.AndroidJUnit4;
...@@ -135,8 +136,8 @@ public final class ClippedPlaybackTest { ...@@ -135,8 +136,8 @@ public final class ClippedPlaybackTest {
} }
@Override @Override
public void onCues(List<Cue> cues) { public void onCues(CueGroup cueGroup) {
this.cues.add(cues); this.cues.add(cueGroup.cues);
} }
@Override @Override
......
...@@ -276,7 +276,8 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; ...@@ -276,7 +276,8 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
boolean willPauseWhenDucked = willPauseWhenDucked(); boolean willPauseWhenDucked = willPauseWhenDucked();
audioFocusRequest = audioFocusRequest =
builder builder
.setAudioAttributes(checkNotNull(audioAttributes).getAudioAttributesV21()) .setAudioAttributes(
checkNotNull(audioAttributes).getAudioAttributesV21().audioAttributes)
.setWillPauseWhenDucked(willPauseWhenDucked) .setWillPauseWhenDucked(willPauseWhenDucked)
.setOnAudioFocusChangeListener(focusListener) .setOnAudioFocusChangeListener(focusListener)
.build(); .build();
...@@ -298,7 +299,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; ...@@ -298,7 +299,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
} }
private boolean willPauseWhenDucked() { private boolean willPauseWhenDucked() {
return audioAttributes != null && audioAttributes.contentType == C.CONTENT_TYPE_SPEECH; return audioAttributes != null && audioAttributes.contentType == C.AUDIO_CONTENT_TYPE_SPEECH;
} }
/** /**
...@@ -369,7 +370,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; ...@@ -369,7 +370,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
// Special usages: // Special usages:
case C.USAGE_ASSISTANCE_ACCESSIBILITY: case C.USAGE_ASSISTANCE_ACCESSIBILITY:
if (audioAttributes.contentType == C.CONTENT_TYPE_SPEECH) { if (audioAttributes.contentType == C.AUDIO_CONTENT_TYPE_SPEECH) {
// Voice shouldn't be interrupted by other playback. // Voice shouldn't be interrupted by other playback.
return AUDIOFOCUS_GAIN_TRANSIENT; return AUDIOFOCUS_GAIN_TRANSIENT;
} }
......
...@@ -485,6 +485,19 @@ public class DefaultRenderersFactory implements RenderersFactory { ...@@ -485,6 +485,19 @@ public class DefaultRenderersFactory implements RenderersFactory {
} }
try { try {
Class<?> clazz = Class.forName("androidx.media3.decoder.midi.MidiRenderer");
Constructor<?> constructor = clazz.getConstructor();
Renderer renderer = (Renderer) constructor.newInstance();
out.add(extensionRendererIndex++, renderer);
Log.i(TAG, "Loaded MidiRenderer.");
} catch (ClassNotFoundException e) {
// Expected if the app was built without the extension.
} catch (Exception e) {
// The extension is present, but instantiation failed.
throw new RuntimeException("Error instantiating MIDI extension", e);
}
try {
// Full class names used for constructor args so the LINT rule triggers if any of them move. // Full class names used for constructor args so the LINT rule triggers if any of them move.
Class<?> clazz = Class.forName("androidx.media3.decoder.opus.LibopusAudioRenderer"); Class<?> clazz = Class.forName("androidx.media3.decoder.opus.LibopusAudioRenderer");
Constructor<?> constructor = Constructor<?> constructor =
......
...@@ -33,7 +33,6 @@ import androidx.media3.common.Format; ...@@ -33,7 +33,6 @@ import androidx.media3.common.Format;
import androidx.media3.common.MediaPeriodId; import androidx.media3.common.MediaPeriodId;
import androidx.media3.common.PlaybackException; import androidx.media3.common.PlaybackException;
import androidx.media3.common.util.Assertions; import androidx.media3.common.util.Assertions;
import androidx.media3.common.util.BundleableUtil;
import androidx.media3.common.util.UnstableApi; import androidx.media3.common.util.UnstableApi;
import androidx.media3.common.util.Util; import androidx.media3.common.util.Util;
import androidx.media3.exoplayer.source.MediaSource; import androidx.media3.exoplayer.source.MediaSource;
...@@ -255,9 +254,9 @@ public final class ExoPlaybackException extends PlaybackException { ...@@ -255,9 +254,9 @@ public final class ExoPlaybackException extends PlaybackException {
rendererName = bundle.getString(keyForField(FIELD_RENDERER_NAME)); rendererName = bundle.getString(keyForField(FIELD_RENDERER_NAME));
rendererIndex = rendererIndex =
bundle.getInt(keyForField(FIELD_RENDERER_INDEX), /* defaultValue= */ C.INDEX_UNSET); bundle.getInt(keyForField(FIELD_RENDERER_INDEX), /* defaultValue= */ C.INDEX_UNSET);
@Nullable Bundle rendererFormatBundle = bundle.getBundle(keyForField(FIELD_RENDERER_FORMAT));
rendererFormat = rendererFormat =
BundleableUtil.fromNullableBundle( rendererFormatBundle == null ? null : Format.CREATOR.fromBundle(rendererFormatBundle);
Format.CREATOR, bundle.getBundle(keyForField(FIELD_RENDERER_FORMAT)));
rendererFormatSupport = rendererFormatSupport =
bundle.getInt( bundle.getInt(
keyForField(FIELD_RENDERER_FORMAT_SUPPORT), /* defaultValue= */ C.FORMAT_HANDLED); keyForField(FIELD_RENDERER_FORMAT_SUPPORT), /* defaultValue= */ C.FORMAT_HANDLED);
...@@ -424,8 +423,9 @@ public final class ExoPlaybackException extends PlaybackException { ...@@ -424,8 +423,9 @@ public final class ExoPlaybackException extends PlaybackException {
bundle.putInt(keyForField(FIELD_TYPE), type); bundle.putInt(keyForField(FIELD_TYPE), type);
bundle.putString(keyForField(FIELD_RENDERER_NAME), rendererName); bundle.putString(keyForField(FIELD_RENDERER_NAME), rendererName);
bundle.putInt(keyForField(FIELD_RENDERER_INDEX), rendererIndex); bundle.putInt(keyForField(FIELD_RENDERER_INDEX), rendererIndex);
bundle.putBundle( if (rendererFormat != null) {
keyForField(FIELD_RENDERER_FORMAT), BundleableUtil.toNullableBundle(rendererFormat)); bundle.putBundle(keyForField(FIELD_RENDERER_FORMAT), rendererFormat.toBundle());
}
bundle.putInt(keyForField(FIELD_RENDERER_FORMAT_SUPPORT), rendererFormatSupport); bundle.putInt(keyForField(FIELD_RENDERER_FORMAT_SUPPORT), rendererFormatSupport);
bundle.putBoolean(keyForField(FIELD_IS_RECOVERABLE), isRecoverable); bundle.putBoolean(keyForField(FIELD_IS_RECOVERABLE), isRecoverable);
return bundle; return bundle;
......
...@@ -40,7 +40,7 @@ import androidx.media3.common.PriorityTaskManager; ...@@ -40,7 +40,7 @@ import androidx.media3.common.PriorityTaskManager;
import androidx.media3.common.Timeline; import androidx.media3.common.Timeline;
import androidx.media3.common.Tracks; import androidx.media3.common.Tracks;
import androidx.media3.common.VideoSize; import androidx.media3.common.VideoSize;
import androidx.media3.common.text.Cue; import androidx.media3.common.text.CueGroup;
import androidx.media3.common.util.Clock; import androidx.media3.common.util.Clock;
import androidx.media3.common.util.UnstableApi; import androidx.media3.common.util.UnstableApi;
import androidx.media3.common.util.Util; import androidx.media3.common.util.Util;
...@@ -358,7 +358,7 @@ public interface ExoPlayer extends Player { ...@@ -358,7 +358,7 @@ public interface ExoPlayer extends Player {
* @deprecated Use {@link Player#getCurrentCues()} instead. * @deprecated Use {@link Player#getCurrentCues()} instead.
*/ */
@Deprecated @Deprecated
List<Cue> getCurrentCues(); CueGroup getCurrentCues();
} }
/** /**
...@@ -1175,7 +1175,6 @@ public interface ExoPlayer extends Player { ...@@ -1175,7 +1175,6 @@ public interface ExoPlayer extends Player {
* *
* @param listener The listener to be added. * @param listener The listener to be added.
*/ */
@UnstableApi
void addAnalyticsListener(AnalyticsListener listener); void addAnalyticsListener(AnalyticsListener listener);
/** /**
...@@ -1183,7 +1182,6 @@ public interface ExoPlayer extends Player { ...@@ -1183,7 +1182,6 @@ public interface ExoPlayer extends Player {
* *
* @param listener The listener to be removed. * @param listener The listener to be removed.
*/ */
@UnstableApi
void removeAnalyticsListener(AnalyticsListener listener); void removeAnalyticsListener(AnalyticsListener listener);
/** Returns the number of renderers. */ /** Returns the number of renderers. */
......
...@@ -73,6 +73,7 @@ import androidx.media3.common.TrackSelectionParameters; ...@@ -73,6 +73,7 @@ import androidx.media3.common.TrackSelectionParameters;
import androidx.media3.common.Tracks; import androidx.media3.common.Tracks;
import androidx.media3.common.VideoSize; import androidx.media3.common.VideoSize;
import androidx.media3.common.text.Cue; import androidx.media3.common.text.Cue;
import androidx.media3.common.text.CueGroup;
import androidx.media3.common.util.Assertions; import androidx.media3.common.util.Assertions;
import androidx.media3.common.util.Clock; import androidx.media3.common.util.Clock;
import androidx.media3.common.util.ConditionVariable; import androidx.media3.common.util.ConditionVariable;
...@@ -196,7 +197,7 @@ import java.util.concurrent.TimeoutException; ...@@ -196,7 +197,7 @@ import java.util.concurrent.TimeoutException;
private AudioAttributes audioAttributes; private AudioAttributes audioAttributes;
private float volume; private float volume;
private boolean skipSilenceEnabled; private boolean skipSilenceEnabled;
private List<Cue> currentCues; private CueGroup currentCueGroup;
@Nullable private VideoFrameMetadataListener videoFrameMetadataListener; @Nullable private VideoFrameMetadataListener videoFrameMetadataListener;
@Nullable private CameraMotionListener cameraMotionListener; @Nullable private CameraMotionListener cameraMotionListener;
private boolean throwsWhenUsingWrongThread; private boolean throwsWhenUsingWrongThread;
...@@ -302,7 +303,8 @@ import java.util.concurrent.TimeoutException; ...@@ -302,7 +303,8 @@ import java.util.concurrent.TimeoutException;
COMMAND_SET_DEVICE_VOLUME, COMMAND_SET_DEVICE_VOLUME,
COMMAND_ADJUST_DEVICE_VOLUME, COMMAND_ADJUST_DEVICE_VOLUME,
COMMAND_SET_VIDEO_SURFACE, COMMAND_SET_VIDEO_SURFACE,
COMMAND_GET_TEXT) COMMAND_GET_TEXT,
COMMAND_SET_MEDIA_ITEM)
.addIf( .addIf(
COMMAND_SET_TRACK_SELECTION_PARAMETERS, trackSelector.isSetParametersSupported()) COMMAND_SET_TRACK_SELECTION_PARAMETERS, trackSelector.isSetParametersSupported())
.build(); .build();
...@@ -353,7 +355,7 @@ import java.util.concurrent.TimeoutException; ...@@ -353,7 +355,7 @@ import java.util.concurrent.TimeoutException;
} else { } else {
audioSessionId = Util.generateAudioSessionIdV21(applicationContext); audioSessionId = Util.generateAudioSessionIdV21(applicationContext);
} }
currentCues = ImmutableList.of(); currentCueGroup = CueGroup.EMPTY;
throwsWhenUsingWrongThread = true; throwsWhenUsingWrongThread = true;
addListener(analyticsCollector); addListener(analyticsCollector);
...@@ -378,6 +380,7 @@ import java.util.concurrent.TimeoutException; ...@@ -378,6 +380,7 @@ import java.util.concurrent.TimeoutException;
deviceInfo = createDeviceInfo(streamVolumeManager); deviceInfo = createDeviceInfo(streamVolumeManager);
videoSize = VideoSize.UNKNOWN; videoSize = VideoSize.UNKNOWN;
trackSelector.setAudioAttributes(audioAttributes);
sendRendererMessage(TRACK_TYPE_AUDIO, MSG_SET_AUDIO_SESSION_ID, audioSessionId); sendRendererMessage(TRACK_TYPE_AUDIO, MSG_SET_AUDIO_SESSION_ID, audioSessionId);
sendRendererMessage(TRACK_TYPE_VIDEO, MSG_SET_AUDIO_SESSION_ID, audioSessionId); sendRendererMessage(TRACK_TYPE_VIDEO, MSG_SET_AUDIO_SESSION_ID, audioSessionId);
sendRendererMessage(TRACK_TYPE_AUDIO, MSG_SET_AUDIO_ATTRIBUTES, audioAttributes); sendRendererMessage(TRACK_TYPE_AUDIO, MSG_SET_AUDIO_ATTRIBUTES, audioAttributes);
...@@ -936,7 +939,7 @@ import java.util.concurrent.TimeoutException; ...@@ -936,7 +939,7 @@ import java.util.concurrent.TimeoutException;
verifyApplicationThread(); verifyApplicationThread();
audioFocusManager.updateAudioFocus(getPlayWhenReady(), Player.STATE_IDLE); audioFocusManager.updateAudioFocus(getPlayWhenReady(), Player.STATE_IDLE);
stopInternal(reset, /* error= */ null); stopInternal(reset, /* error= */ null);
currentCues = ImmutableList.of(); currentCueGroup = CueGroup.EMPTY;
} }
@Override @Override
...@@ -980,6 +983,7 @@ import java.util.concurrent.TimeoutException; ...@@ -980,6 +983,7 @@ import java.util.concurrent.TimeoutException;
playbackInfo.bufferedPositionUs = playbackInfo.positionUs; playbackInfo.bufferedPositionUs = playbackInfo.positionUs;
playbackInfo.totalBufferedDurationUs = 0; playbackInfo.totalBufferedDurationUs = 0;
analyticsCollector.release(); analyticsCollector.release();
trackSelector.release();
removeSurfaceCallbacks(); removeSurfaceCallbacks();
if (ownedSurface != null) { if (ownedSurface != null) {
ownedSurface.release(); ownedSurface.release();
...@@ -989,7 +993,7 @@ import java.util.concurrent.TimeoutException; ...@@ -989,7 +993,7 @@ import java.util.concurrent.TimeoutException;
checkNotNull(priorityTaskManager).remove(C.PRIORITY_PLAYBACK); checkNotNull(priorityTaskManager).remove(C.PRIORITY_PLAYBACK);
isPriorityTaskManagerRegistered = false; isPriorityTaskManagerRegistered = false;
} }
currentCues = ImmutableList.of(); currentCueGroup = CueGroup.EMPTY;
playerReleased = true; playerReleased = true;
} }
...@@ -1372,6 +1376,7 @@ import java.util.concurrent.TimeoutException; ...@@ -1372,6 +1376,7 @@ import java.util.concurrent.TimeoutException;
} }
audioFocusManager.setAudioAttributes(handleAudioFocus ? newAudioAttributes : null); audioFocusManager.setAudioAttributes(handleAudioFocus ? newAudioAttributes : null);
trackSelector.setAudioAttributes(newAudioAttributes);
boolean playWhenReady = getPlayWhenReady(); boolean playWhenReady = getPlayWhenReady();
@AudioFocusManager.PlayerCommand @AudioFocusManager.PlayerCommand
int playerCommand = audioFocusManager.updateAudioFocus(playWhenReady, getPlaybackState()); int playerCommand = audioFocusManager.updateAudioFocus(playWhenReady, getPlaybackState());
...@@ -1586,9 +1591,9 @@ import java.util.concurrent.TimeoutException; ...@@ -1586,9 +1591,9 @@ import java.util.concurrent.TimeoutException;
} }
@Override @Override
public List<Cue> getCurrentCues() { public CueGroup getCurrentCues() {
verifyApplicationThread(); verifyApplicationThread();
return currentCues; return currentCueGroup;
} }
@Override @Override
...@@ -2849,13 +2854,17 @@ import java.util.concurrent.TimeoutException; ...@@ -2849,13 +2854,17 @@ import java.util.concurrent.TimeoutException;
} }
// TextOutput implementation // TextOutput implementation
@Override @Override
public void onCues(List<Cue> cues) { public void onCues(List<Cue> cues) {
currentCues = cues;
listeners.sendEvent(EVENT_CUES, listener -> listener.onCues(cues)); listeners.sendEvent(EVENT_CUES, listener -> listener.onCues(cues));
} }
@Override
public void onCues(CueGroup cueGroup) {
currentCueGroup = cueGroup;
listeners.sendEvent(EVENT_CUES, listener -> listener.onCues(cueGroup));
}
// MetadataOutput implementation // MetadataOutput implementation
@Override @Override
......
...@@ -169,15 +169,6 @@ import java.util.concurrent.atomic.AtomicBoolean; ...@@ -169,15 +169,6 @@ import java.util.concurrent.atomic.AtomicBoolean;
private static final int ACTIVE_INTERVAL_MS = 10; private static final int ACTIVE_INTERVAL_MS = 10;
private static final int IDLE_INTERVAL_MS = 1000; private static final int IDLE_INTERVAL_MS = 1000;
/** /**
* Duration under which pausing the main DO_SOME_WORK loop is not expected to yield significant
* power saving.
*
* <p>This value is probably too high, power measurements are needed adjust it, but as renderer
* sleep is currently only implemented for audio offload, which uses buffer much bigger than 2s,
* this does not matter for now.
*/
private static final long MIN_RENDERER_SLEEP_DURATION_MS = 2000;
/**
* Duration for which the player needs to appear stuck before the playback is failed on the * Duration for which the player needs to appear stuck before the playback is failed on the
* assumption that no further progress will be made. To appear stuck, the player's renderers must * assumption that no further progress will be made. To appear stuck, the player's renderers must
* not be ready, there must be more media available to load, and the LoadControl must be refusing * not be ready, there must be more media available to load, and the LoadControl must be refusing
...@@ -2486,12 +2477,9 @@ import java.util.concurrent.atomic.AtomicBoolean; ...@@ -2486,12 +2477,9 @@ import java.util.concurrent.atomic.AtomicBoolean;
Renderer.MSG_SET_WAKEUP_LISTENER, Renderer.MSG_SET_WAKEUP_LISTENER,
new Renderer.WakeupListener() { new Renderer.WakeupListener() {
@Override @Override
public void onSleep(long wakeupDeadlineMs) { public void onSleep() {
// Do not sleep if the expected sleep time is not long enough to save significant power.
if (wakeupDeadlineMs >= MIN_RENDERER_SLEEP_DURATION_MS) {
requestForRendererSleep = true; requestForRendererSleep = true;
} }
}
@Override @Override
public void onWakeup() { public void onWakeup() {
......
...@@ -66,11 +66,8 @@ public interface Renderer extends PlayerMessage.Target { ...@@ -66,11 +66,8 @@ public interface Renderer extends PlayerMessage.Target {
* The renderer no longer needs to render until the next wakeup. * The renderer no longer needs to render until the next wakeup.
* *
* <p>Must be called from the thread ExoPlayer invokes the renderer from. * <p>Must be called from the thread ExoPlayer invokes the renderer from.
*
* @param wakeupDeadlineMs Maximum time in milliseconds until {@link #onWakeup()} will be
* called.
*/ */
void onSleep(long wakeupDeadlineMs); void onSleep();
/** /**
* The renderer needs to render some frames. The client should call {@link #render(long, long)} * The renderer needs to render some frames. The client should call {@link #render(long, long)}
......
...@@ -33,12 +33,13 @@ import androidx.media3.common.Format; ...@@ -33,12 +33,13 @@ import androidx.media3.common.Format;
import androidx.media3.common.MediaItem; import androidx.media3.common.MediaItem;
import androidx.media3.common.MediaMetadata; import androidx.media3.common.MediaMetadata;
import androidx.media3.common.PlaybackParameters; import androidx.media3.common.PlaybackParameters;
import androidx.media3.common.Player;
import androidx.media3.common.PriorityTaskManager; import androidx.media3.common.PriorityTaskManager;
import androidx.media3.common.Timeline; import androidx.media3.common.Timeline;
import androidx.media3.common.TrackSelectionParameters; import androidx.media3.common.TrackSelectionParameters;
import androidx.media3.common.Tracks; import androidx.media3.common.Tracks;
import androidx.media3.common.VideoSize; import androidx.media3.common.VideoSize;
import androidx.media3.common.text.Cue; import androidx.media3.common.text.CueGroup;
import androidx.media3.common.util.Clock; import androidx.media3.common.util.Clock;
import androidx.media3.common.util.ConditionVariable; import androidx.media3.common.util.ConditionVariable;
import androidx.media3.common.util.UnstableApi; import androidx.media3.common.util.UnstableApi;
...@@ -426,24 +427,44 @@ public class SimpleExoPlayer extends BasePlayer ...@@ -426,24 +427,44 @@ public class SimpleExoPlayer extends BasePlayer
return player.experimentalIsSleepingForOffload(); return player.experimentalIsSleepingForOffload();
} }
/**
* @deprecated Use {@link ExoPlayer}, as the {@link AudioComponent} methods are defined by that
* interface.
*/
@Deprecated
@Override @Override
@Nullable @Nullable
public AudioComponent getAudioComponent() { public AudioComponent getAudioComponent() {
return this; return this;
} }
/**
* @deprecated Use {@link ExoPlayer}, as the {@link VideoComponent} methods are defined by that
* interface.
*/
@Deprecated
@Override @Override
@Nullable @Nullable
public VideoComponent getVideoComponent() { public VideoComponent getVideoComponent() {
return this; return this;
} }
/**
* @deprecated Use {@link Player}, as the {@link TextComponent} methods are defined by that
* interface.
*/
@Deprecated
@Override @Override
@Nullable @Nullable
public TextComponent getTextComponent() { public TextComponent getTextComponent() {
return this; return this;
} }
/**
* @deprecated Use {@link Player}, as the {@link DeviceComponent} methods are defined by that
* interface.
*/
@Deprecated
@Override @Override
@Nullable @Nullable
public DeviceComponent getDeviceComponent() { public DeviceComponent getDeviceComponent() {
...@@ -690,7 +711,7 @@ public class SimpleExoPlayer extends BasePlayer ...@@ -690,7 +711,7 @@ public class SimpleExoPlayer extends BasePlayer
} }
@Override @Override
public List<Cue> getCurrentCues() { public CueGroup getCurrentCues() {
blockUntilConstructorFinished(); blockUntilConstructorFinished();
return player.getCurrentCues(); return player.getCurrentCues();
} }
...@@ -1003,6 +1024,11 @@ public class SimpleExoPlayer extends BasePlayer ...@@ -1003,6 +1024,11 @@ public class SimpleExoPlayer extends BasePlayer
player.stop(); player.stop();
} }
/**
* @deprecated Use {@link #stop()} and {@link #clearMediaItems()} (if {@code reset} is true) or
* just {@link #stop()} (if {@code reset} is false). Any player error will be cleared when
* {@link #prepare() re-preparing} the player.
*/
@Deprecated @Deprecated
@Override @Override
public void stop(boolean reset) { public void stop(boolean reset) {
...@@ -1046,12 +1072,20 @@ public class SimpleExoPlayer extends BasePlayer ...@@ -1046,12 +1072,20 @@ public class SimpleExoPlayer extends BasePlayer
return player.getTrackSelector(); return player.getTrackSelector();
} }
/**
* @deprecated Use {@link #getCurrentTracks()}.
*/
@Deprecated
@Override @Override
public TrackGroupArray getCurrentTrackGroups() { public TrackGroupArray getCurrentTrackGroups() {
blockUntilConstructorFinished(); blockUntilConstructorFinished();
return player.getCurrentTrackGroups(); return player.getCurrentTrackGroups();
} }
/**
* @deprecated Use {@link #getCurrentTracks()}.
*/
@Deprecated
@Override @Override
public TrackSelectionArray getCurrentTrackSelections() { public TrackSelectionArray getCurrentTrackSelections() {
blockUntilConstructorFinished(); blockUntilConstructorFinished();
...@@ -1166,6 +1200,9 @@ public class SimpleExoPlayer extends BasePlayer ...@@ -1166,6 +1200,9 @@ public class SimpleExoPlayer extends BasePlayer
return player.getContentBufferedPosition(); return player.getContentBufferedPosition();
} }
/**
* @deprecated Use {@link #setWakeMode(int)} instead.
*/
@Deprecated @Deprecated
@Override @Override
public void setHandleWakeLock(boolean handleWakeLock) { public void setHandleWakeLock(boolean handleWakeLock) {
......
...@@ -42,6 +42,7 @@ import androidx.media3.common.TrackSelectionParameters; ...@@ -42,6 +42,7 @@ import androidx.media3.common.TrackSelectionParameters;
import androidx.media3.common.Tracks; import androidx.media3.common.Tracks;
import androidx.media3.common.VideoSize; import androidx.media3.common.VideoSize;
import androidx.media3.common.text.Cue; import androidx.media3.common.text.Cue;
import androidx.media3.common.text.CueGroup;
import androidx.media3.common.util.Clock; import androidx.media3.common.util.Clock;
import androidx.media3.common.util.HandlerWrapper; import androidx.media3.common.util.HandlerWrapper;
import androidx.media3.common.util.ListenerSet; import androidx.media3.common.util.ListenerSet;
...@@ -695,6 +696,7 @@ public class DefaultAnalyticsCollector implements AnalyticsCollector { ...@@ -695,6 +696,7 @@ public class DefaultAnalyticsCollector implements AnalyticsCollector {
listener -> listener.onMetadata(eventTime, metadata)); listener -> listener.onMetadata(eventTime, metadata));
} }
@SuppressWarnings("deprecation") // Implementing and calling deprecated listener method.
@Override @Override
public void onCues(List<Cue> cues) { public void onCues(List<Cue> cues) {
EventTime eventTime = generateCurrentPlayerMediaPeriodEventTime(); EventTime eventTime = generateCurrentPlayerMediaPeriodEventTime();
...@@ -702,6 +704,13 @@ public class DefaultAnalyticsCollector implements AnalyticsCollector { ...@@ -702,6 +704,13 @@ public class DefaultAnalyticsCollector implements AnalyticsCollector {
eventTime, AnalyticsListener.EVENT_CUES, listener -> listener.onCues(eventTime, cues)); eventTime, AnalyticsListener.EVENT_CUES, listener -> listener.onCues(eventTime, cues));
} }
@Override
public void onCues(CueGroup cueGroup) {
EventTime eventTime = generateCurrentPlayerMediaPeriodEventTime();
sendEvent(
eventTime, AnalyticsListener.EVENT_CUES, listener -> listener.onCues(eventTime, cueGroup));
}
@SuppressWarnings("deprecation") // Implementing and calling deprecated listener method. @SuppressWarnings("deprecation") // Implementing and calling deprecated listener method.
@Override @Override
public final void onSeekProcessed() { public final void onSeekProcessed() {
......
...@@ -679,13 +679,13 @@ public final class MediaMetricsListener ...@@ -679,13 +679,13 @@ public final class MediaMetricsListener
Util.inferContentTypeForUriAndMimeType( Util.inferContentTypeForUriAndMimeType(
mediaItem.localConfiguration.uri, mediaItem.localConfiguration.mimeType); mediaItem.localConfiguration.uri, mediaItem.localConfiguration.mimeType);
switch (contentType) { switch (contentType) {
case C.TYPE_HLS: case C.CONTENT_TYPE_HLS:
return PlaybackMetrics.STREAM_TYPE_HLS; return PlaybackMetrics.STREAM_TYPE_HLS;
case C.TYPE_DASH: case C.CONTENT_TYPE_DASH:
return PlaybackMetrics.STREAM_TYPE_DASH; return PlaybackMetrics.STREAM_TYPE_DASH;
case C.TYPE_SS: case C.CONTENT_TYPE_SS:
return PlaybackMetrics.STREAM_TYPE_SS; return PlaybackMetrics.STREAM_TYPE_SS;
case C.TYPE_RTSP: case C.CONTENT_TYPE_RTSP:
default: default:
return PlaybackMetrics.STREAM_TYPE_OTHER; return PlaybackMetrics.STREAM_TYPE_OTHER;
} }
......
...@@ -15,22 +15,29 @@ ...@@ -15,22 +15,29 @@
*/ */
package androidx.media3.exoplayer.audio; package androidx.media3.exoplayer.audio;
import static androidx.media3.common.util.Assertions.checkNotNull;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.IntentFilter; import android.content.IntentFilter;
import android.media.AudioAttributes;
import android.media.AudioFormat; import android.media.AudioFormat;
import android.media.AudioManager; import android.media.AudioManager;
import android.media.AudioTrack; import android.media.AudioTrack;
import android.net.Uri; import android.net.Uri;
import android.provider.Settings.Global; import android.provider.Settings.Global;
import android.util.Pair;
import androidx.annotation.DoNotInline; import androidx.annotation.DoNotInline;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi; import androidx.annotation.RequiresApi;
import androidx.media3.common.C; import androidx.media3.common.C;
import androidx.media3.common.Format;
import androidx.media3.common.MimeTypes;
import androidx.media3.common.util.UnstableApi; import androidx.media3.common.util.UnstableApi;
import androidx.media3.common.util.Util; import androidx.media3.common.util.Util;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.primitives.Ints; import com.google.common.primitives.Ints;
import java.util.Arrays; import java.util.Arrays;
...@@ -54,18 +61,20 @@ public final class AudioCapabilities { ...@@ -54,18 +61,20 @@ public final class AudioCapabilities {
}, },
DEFAULT_MAX_CHANNEL_COUNT); DEFAULT_MAX_CHANNEL_COUNT);
/** Array of all surround sound encodings that a device may be capable of playing. */ /**
@SuppressWarnings("InlinedApi") * All surround sound encodings that a device may be capable of playing mapped to a maximum
private static final int[] ALL_SURROUND_ENCODINGS = * channel count.
new int[] { */
AudioFormat.ENCODING_AC3, private static final ImmutableMap<Integer, Integer> ALL_SURROUND_ENCODINGS_AND_MAX_CHANNELS =
AudioFormat.ENCODING_E_AC3, new ImmutableMap.Builder<Integer, Integer>()
AudioFormat.ENCODING_E_AC3_JOC, .put(C.ENCODING_AC3, 6)
AudioFormat.ENCODING_AC4, .put(C.ENCODING_AC4, 6)
AudioFormat.ENCODING_DOLBY_TRUEHD, .put(C.ENCODING_DTS, 6)
AudioFormat.ENCODING_DTS, .put(C.ENCODING_E_AC3_JOC, 6)
AudioFormat.ENCODING_DTS_HD, .put(C.ENCODING_E_AC3, 8)
}; .put(C.ENCODING_DTS_HD, 8)
.put(C.ENCODING_DOLBY_TRUEHD, 8)
.buildOrThrow();
/** Global settings key for devices that can specify external surround sound. */ /** Global settings key for devices that can specify external surround sound. */
private static final String EXTERNAL_SURROUND_SOUND_KEY = "external_surround_sound_enabled"; private static final String EXTERNAL_SURROUND_SOUND_KEY = "external_surround_sound_enabled";
...@@ -158,6 +167,62 @@ public final class AudioCapabilities { ...@@ -158,6 +167,62 @@ public final class AudioCapabilities {
return maxChannelCount; return maxChannelCount;
} }
/** Returns whether the device can do passthrough playback for {@code format}. */
public boolean isPassthroughPlaybackSupported(Format format) {
return getEncodingAndChannelConfigForPassthrough(format) != null;
}
/**
* Returns the encoding and channel config to use when configuring an {@link AudioTrack} in
* passthrough mode for the specified {@link Format}. Returns {@code null} if passthrough of the
* format is unsupported.
*
* @param format The {@link Format}.
* @return The encoding and channel config to use, or {@code null} if passthrough of the format is
* unsupported.
*/
@Nullable
public Pair<Integer, Integer> getEncodingAndChannelConfigForPassthrough(Format format) {
@C.Encoding
int encoding = MimeTypes.getEncoding(checkNotNull(format.sampleMimeType), format.codecs);
// Check that this is an encoding known to work for passthrough. This avoids trying to use
// passthrough with an encoding where the device/app reports it's capable but it is untested or
// known to be broken (for example AAC-LC).
if (!ALL_SURROUND_ENCODINGS_AND_MAX_CHANNELS.containsKey(encoding)) {
return null;
}
if (encoding == C.ENCODING_E_AC3_JOC && !supportsEncoding(C.ENCODING_E_AC3_JOC)) {
// E-AC3 receivers support E-AC3 JOC streams (but decode only the base layer).
encoding = C.ENCODING_E_AC3;
} else if (encoding == C.ENCODING_DTS_HD && !supportsEncoding(C.ENCODING_DTS_HD)) {
// DTS receivers support DTS-HD streams (but decode only the core layer).
encoding = C.ENCODING_DTS;
}
if (!supportsEncoding(encoding)) {
return null;
}
int channelCount;
if (format.channelCount == Format.NO_VALUE || encoding == C.ENCODING_E_AC3_JOC) {
// In HLS chunkless preparation, the format channel count and sample rate may be unset. See
// https://github.com/google/ExoPlayer/issues/10204 and b/222127949 for more details.
// For E-AC3 JOC, the format is object based so the format channel count is arbitrary.
int sampleRate =
format.sampleRate != Format.NO_VALUE ? format.sampleRate : DEFAULT_SAMPLE_RATE_HZ;
channelCount = getMaxSupportedChannelCountForPassthrough(encoding, sampleRate);
} else {
channelCount = format.channelCount;
if (channelCount > maxChannelCount) {
return null;
}
}
int channelConfig = getChannelConfigForPassthrough(channelCount);
if (channelConfig == AudioFormat.CHANNEL_INVALID) {
return null;
}
return Pair.create(encoding, channelConfig);
}
@Override @Override
public boolean equals(@Nullable Object other) { public boolean equals(@Nullable Object other) {
if (this == other) { if (this == other) {
...@@ -190,28 +255,93 @@ public final class AudioCapabilities { ...@@ -190,28 +255,93 @@ public final class AudioCapabilities {
&& ("Amazon".equals(Util.MANUFACTURER) || "Xiaomi".equals(Util.MANUFACTURER)); && ("Amazon".equals(Util.MANUFACTURER) || "Xiaomi".equals(Util.MANUFACTURER));
} }
/**
* Returns the maximum number of channels supported for passthrough playback of audio in the given
* encoding, or {@code 0} if the format is unsupported.
*/
private static int getMaxSupportedChannelCountForPassthrough(
@C.Encoding int encoding, int sampleRate) {
// From API 29 we can get the channel count from the platform, but before then there is no way
// to query the platform so we assume the channel count matches the maximum channel count per
// audio encoding spec.
if (Util.SDK_INT >= 29) {
return Api29.getMaxSupportedChannelCountForPassthrough(encoding, sampleRate);
}
return checkNotNull(ALL_SURROUND_ENCODINGS_AND_MAX_CHANNELS.getOrDefault(encoding, 0));
}
private static int getChannelConfigForPassthrough(int channelCount) {
if (Util.SDK_INT <= 28) {
// In passthrough mode the channel count used to configure the audio track doesn't affect how
// the stream is handled, except that some devices do overly-strict channel configuration
// checks. Therefore we override the channel count so that a known-working channel
// configuration is chosen in all cases. See [Internal: b/29116190].
if (channelCount == 7) {
channelCount = 8;
} else if (channelCount == 3 || channelCount == 4 || channelCount == 5) {
channelCount = 6;
}
}
// Workaround for Nexus Player not reporting support for mono passthrough. See
// [Internal: b/34268671].
if (Util.SDK_INT <= 26 && "fugu".equals(Util.DEVICE) && channelCount == 1) {
channelCount = 2;
}
return Util.getAudioTrackChannelConfig(channelCount);
}
@RequiresApi(29) @RequiresApi(29)
private static final class Api29 { private static final class Api29 {
private static final AudioAttributes DEFAULT_AUDIO_ATTRIBUTES =
new AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_MEDIA)
.setContentType(AudioAttributes.CONTENT_TYPE_MOVIE)
.setFlags(0)
.build();
private Api29() {}
@DoNotInline @DoNotInline
public static int[] getDirectPlaybackSupportedEncodings() { public static int[] getDirectPlaybackSupportedEncodings() {
ImmutableList.Builder<Integer> supportedEncodingsListBuilder = ImmutableList.builder(); ImmutableList.Builder<Integer> supportedEncodingsListBuilder = ImmutableList.builder();
for (int encoding : ALL_SURROUND_ENCODINGS) { for (int encoding : ALL_SURROUND_ENCODINGS_AND_MAX_CHANNELS.keySet()) {
if (AudioTrack.isDirectPlaybackSupported( if (AudioTrack.isDirectPlaybackSupported(
new AudioFormat.Builder() new AudioFormat.Builder()
.setChannelMask(AudioFormat.CHANNEL_OUT_STEREO) .setChannelMask(AudioFormat.CHANNEL_OUT_STEREO)
.setEncoding(encoding) .setEncoding(encoding)
.setSampleRate(DEFAULT_SAMPLE_RATE_HZ) .setSampleRate(DEFAULT_SAMPLE_RATE_HZ)
.build(), .build(),
new android.media.AudioAttributes.Builder() DEFAULT_AUDIO_ATTRIBUTES)) {
.setUsage(android.media.AudioAttributes.USAGE_MEDIA)
.setContentType(android.media.AudioAttributes.CONTENT_TYPE_MOVIE)
.setFlags(0)
.build())) {
supportedEncodingsListBuilder.add(encoding); supportedEncodingsListBuilder.add(encoding);
} }
} }
supportedEncodingsListBuilder.add(AudioFormat.ENCODING_PCM_16BIT); supportedEncodingsListBuilder.add(AudioFormat.ENCODING_PCM_16BIT);
return Ints.toArray(supportedEncodingsListBuilder.build()); return Ints.toArray(supportedEncodingsListBuilder.build());
} }
/**
* Returns the maximum number of channels supported for passthrough playback of audio in the
* given format, or {@code 0} if the format is unsupported.
*/
@DoNotInline
public static int getMaxSupportedChannelCountForPassthrough(
@C.Encoding int encoding, int sampleRate) {
// TODO(internal b/234351617): Query supported channel masks directly once it's supported,
// see also b/25994457.
for (int channelCount = DEFAULT_MAX_CHANNEL_COUNT; channelCount > 0; channelCount--) {
AudioFormat audioFormat =
new AudioFormat.Builder()
.setEncoding(encoding)
.setSampleRate(sampleRate)
.setChannelMask(Util.getAudioTrackChannelConfig(channelCount))
.build();
if (AudioTrack.isDirectPlaybackSupported(audioFormat, DEFAULT_AUDIO_ATTRIBUTES)) {
return channelCount;
}
}
return 0;
}
} }
} }
...@@ -108,13 +108,8 @@ public interface AudioSink { ...@@ -108,13 +108,8 @@ public interface AudioSink {
/** Called when the offload buffer has been partially emptied. */ /** Called when the offload buffer has been partially emptied. */
default void onOffloadBufferEmptying() {} default void onOffloadBufferEmptying() {}
/** /** Called when the offload buffer has been filled completely. */
* Called when the offload buffer has been filled completely. default void onOffloadBufferFull() {}
*
* @param bufferEmptyingDeadlineMs Maximum time in milliseconds until {@link
* #onOffloadBufferEmptying()} will be called.
*/
default void onOffloadBufferFull(long bufferEmptyingDeadlineMs) {}
/** /**
* Called when {@link AudioSink} has encountered an error. * Called when {@link AudioSink} has encountered an error.
......
...@@ -386,11 +386,6 @@ import java.lang.reflect.Method; ...@@ -386,11 +386,6 @@ import java.lang.reflect.Method;
return bufferSize - bytesPending; return bufferSize - bytesPending;
} }
/** Returns the duration of audio that is buffered but unplayed. */
public long getPendingBufferDurationMs(long writtenFrames) {
return Util.usToMs(framesToDurationUs(writtenFrames - getPlaybackHeadPosition()));
}
/** Returns whether the track is in an invalid state and must be recreated. */ /** Returns whether the track is in an invalid state and must be recreated. */
public boolean isStalled(long writtenFrames) { public boolean isStalled(long writtenFrames) {
return forceResetWorkaroundTimeMs != C.TIME_UNSET return forceResetWorkaroundTimeMs != C.TIME_UNSET
......
...@@ -684,7 +684,7 @@ public final class DefaultAudioSink implements AudioSink { ...@@ -684,7 +684,7 @@ public final class DefaultAudioSink implements AudioSink {
if (!offloadDisabledUntilNextConfiguration && useOffloadedPlayback(format, audioAttributes)) { if (!offloadDisabledUntilNextConfiguration && useOffloadedPlayback(format, audioAttributes)) {
return SINK_FORMAT_SUPPORTED_DIRECTLY; return SINK_FORMAT_SUPPORTED_DIRECTLY;
} }
if (isPassthroughPlaybackSupported(format, audioCapabilities)) { if (audioCapabilities.isPassthroughPlaybackSupported(format)) {
return SINK_FORMAT_SUPPORTED_DIRECTLY; return SINK_FORMAT_SUPPORTED_DIRECTLY;
} }
return SINK_FORMAT_UNSUPPORTED; return SINK_FORMAT_UNSUPPORTED;
...@@ -767,7 +767,7 @@ public final class DefaultAudioSink implements AudioSink { ...@@ -767,7 +767,7 @@ public final class DefaultAudioSink implements AudioSink {
outputMode = OUTPUT_MODE_PASSTHROUGH; outputMode = OUTPUT_MODE_PASSTHROUGH;
@Nullable @Nullable
Pair<Integer, Integer> encodingAndChannelConfig = Pair<Integer, Integer> encodingAndChannelConfig =
getEncodingAndChannelConfigForPassthrough(inputFormat, audioCapabilities); audioCapabilities.getEncodingAndChannelConfigForPassthrough(inputFormat);
if (encodingAndChannelConfig == null) { if (encodingAndChannelConfig == null) {
throw new ConfigurationException( throw new ConfigurationException(
"Unable to configure passthrough for: " + inputFormat, inputFormat); "Unable to configure passthrough for: " + inputFormat, inputFormat);
...@@ -914,7 +914,11 @@ public final class DefaultAudioSink implements AudioSink { ...@@ -914,7 +914,11 @@ public final class DefaultAudioSink implements AudioSink {
pendingConfiguration = null; pendingConfiguration = null;
if (isOffloadedPlayback(audioTrack) if (isOffloadedPlayback(audioTrack)
&& offloadMode != OFFLOAD_MODE_ENABLED_GAPLESS_DISABLED) { && offloadMode != OFFLOAD_MODE_ENABLED_GAPLESS_DISABLED) {
// If the first track is very short (typically <1s), the offload AudioTrack might
// not have started yet. Do not call setOffloadEndOfStream as it would throw.
if (audioTrack.getPlayState() == AudioTrack.PLAYSTATE_PLAYING) {
audioTrack.setOffloadEndOfStream(); audioTrack.setOffloadEndOfStream();
}
audioTrack.setOffloadDelayPadding( audioTrack.setOffloadDelayPadding(
configuration.inputFormat.encoderDelay, configuration.inputFormat.encoderPadding); configuration.inputFormat.encoderDelay, configuration.inputFormat.encoderPadding);
isWaitingForOffloadEndOfStreamHandled = true; isWaitingForOffloadEndOfStreamHandled = true;
...@@ -1198,9 +1202,7 @@ public final class DefaultAudioSink implements AudioSink { ...@@ -1198,9 +1202,7 @@ public final class DefaultAudioSink implements AudioSink {
&& listener != null && listener != null
&& bytesWritten < bytesRemaining && bytesWritten < bytesRemaining
&& !isWaitingForOffloadEndOfStreamHandled) { && !isWaitingForOffloadEndOfStreamHandled) {
long pendingDurationMs = listener.onOffloadBufferFull();
audioTrackPositionTracker.getPendingBufferDurationMs(writtenEncodedFrames);
listener.onOffloadBufferFull(pendingDurationMs);
} }
} }
...@@ -1691,134 +1693,6 @@ public final class DefaultAudioSink implements AudioSink { ...@@ -1691,134 +1693,6 @@ public final class DefaultAudioSink implements AudioSink {
: writtenEncodedFrames; : writtenEncodedFrames;
} }
private static boolean isPassthroughPlaybackSupported(
Format format, AudioCapabilities audioCapabilities) {
return getEncodingAndChannelConfigForPassthrough(format, audioCapabilities) != null;
}
/**
* Returns the encoding and channel config to use when configuring an {@link AudioTrack} in
* passthrough mode for the specified {@link Format}. Returns {@code null} if passthrough of the
* format is unsupported.
*
* @param format The {@link Format}.
* @param audioCapabilities The device audio capabilities.
* @return The encoding and channel config to use, or {@code null} if passthrough of the format is
* unsupported.
*/
@Nullable
private static Pair<Integer, Integer> getEncodingAndChannelConfigForPassthrough(
Format format, AudioCapabilities audioCapabilities) {
@C.Encoding
int encoding = MimeTypes.getEncoding(checkNotNull(format.sampleMimeType), format.codecs);
// Check for encodings that are known to work for passthrough with the implementation in this
// class. This avoids trying to use passthrough with an encoding where the device/app reports
// it's capable but it is untested or known to be broken (for example AAC-LC).
boolean supportedEncoding =
encoding == C.ENCODING_AC3
|| encoding == C.ENCODING_E_AC3
|| encoding == C.ENCODING_E_AC3_JOC
|| encoding == C.ENCODING_AC4
|| encoding == C.ENCODING_DTS
|| encoding == C.ENCODING_DTS_HD
|| encoding == C.ENCODING_DOLBY_TRUEHD;
if (!supportedEncoding) {
return null;
}
if (encoding == C.ENCODING_E_AC3_JOC
&& !audioCapabilities.supportsEncoding(C.ENCODING_E_AC3_JOC)) {
// E-AC3 receivers support E-AC3 JOC streams (but decode only the base layer).
encoding = C.ENCODING_E_AC3;
} else if (encoding == C.ENCODING_DTS_HD
&& !audioCapabilities.supportsEncoding(C.ENCODING_DTS_HD)) {
// DTS receivers support DTS-HD streams (but decode only the core layer).
encoding = C.ENCODING_DTS;
}
if (!audioCapabilities.supportsEncoding(encoding)) {
return null;
}
int channelCount;
if (encoding == C.ENCODING_E_AC3_JOC) {
// E-AC3 JOC is object based so the format channel count is arbitrary. From API 29 we can get
// the channel count for this encoding, but before then there is no way to query it so we
// assume 6 channel audio is supported.
if (Util.SDK_INT >= 29) {
// Default to 48 kHz if the format doesn't have a sample rate (for example, for chunkless
// HLS preparation). See [Internal: b/222127949].
int sampleRate = format.sampleRate != Format.NO_VALUE ? format.sampleRate : 48000;
channelCount =
getMaxSupportedChannelCountForPassthroughV29(C.ENCODING_E_AC3_JOC, sampleRate);
if (channelCount == 0) {
Log.w(TAG, "E-AC3 JOC encoding supported but no channel count supported");
return null;
}
} else {
channelCount = 6;
}
} else {
channelCount = format.channelCount;
if (channelCount > audioCapabilities.getMaxChannelCount()) {
return null;
}
}
int channelConfig = getChannelConfigForPassthrough(channelCount);
if (channelConfig == AudioFormat.CHANNEL_INVALID) {
return null;
}
return Pair.create(encoding, channelConfig);
}
/**
* Returns the maximum number of channels supported for passthrough playback of audio in the given
* format, or 0 if the format is unsupported.
*/
@RequiresApi(29)
private static int getMaxSupportedChannelCountForPassthroughV29(
@C.Encoding int encoding, int sampleRate) {
android.media.AudioAttributes audioAttributes =
new android.media.AudioAttributes.Builder()
.setUsage(android.media.AudioAttributes.USAGE_MEDIA)
.setContentType(android.media.AudioAttributes.CONTENT_TYPE_MOVIE)
.build();
// TODO(internal b/25994457): Query supported channel masks directly once it's supported.
for (int channelCount = 8; channelCount > 0; channelCount--) {
AudioFormat audioFormat =
new AudioFormat.Builder()
.setEncoding(encoding)
.setSampleRate(sampleRate)
.setChannelMask(Util.getAudioTrackChannelConfig(channelCount))
.build();
if (AudioTrack.isDirectPlaybackSupported(audioFormat, audioAttributes)) {
return channelCount;
}
}
return 0;
}
private static int getChannelConfigForPassthrough(int channelCount) {
if (Util.SDK_INT <= 28) {
// In passthrough mode the channel count used to configure the audio track doesn't affect how
// the stream is handled, except that some devices do overly-strict channel configuration
// checks. Therefore we override the channel count so that a known-working channel
// configuration is chosen in all cases. See [Internal: b/29116190].
if (channelCount == 7) {
channelCount = 8;
} else if (channelCount == 3 || channelCount == 4 || channelCount == 5) {
channelCount = 6;
}
}
// Workaround for Nexus Player not reporting support for mono passthrough. See
// [Internal: b/34268671].
if (Util.SDK_INT <= 26 && "fugu".equals(Util.DEVICE) && channelCount == 1) {
channelCount = 2;
}
return Util.getAudioTrackChannelConfig(channelCount);
}
private boolean useOffloadedPlayback(Format format, AudioAttributes audioAttributes) { private boolean useOffloadedPlayback(Format format, AudioAttributes audioAttributes) {
if (Util.SDK_INT < 29 || offloadMode == OFFLOAD_MODE_DISABLED) { if (Util.SDK_INT < 29 || offloadMode == OFFLOAD_MODE_DISABLED) {
return false; return false;
...@@ -1834,7 +1708,8 @@ public final class DefaultAudioSink implements AudioSink { ...@@ -1834,7 +1708,8 @@ public final class DefaultAudioSink implements AudioSink {
} }
AudioFormat audioFormat = getAudioFormat(format.sampleRate, channelConfig, encoding); AudioFormat audioFormat = getAudioFormat(format.sampleRate, channelConfig, encoding);
switch (getOffloadedPlaybackSupport(audioFormat, audioAttributes.getAudioAttributesV21())) { switch (getOffloadedPlaybackSupport(
audioFormat, audioAttributes.getAudioAttributesV21().audioAttributes)) {
case AudioManager.PLAYBACK_OFFLOAD_NOT_SUPPORTED: case AudioManager.PLAYBACK_OFFLOAD_NOT_SUPPORTED:
return false; return false;
case AudioManager.PLAYBACK_OFFLOAD_SUPPORTED: case AudioManager.PLAYBACK_OFFLOAD_SUPPORTED:
...@@ -2308,7 +2183,7 @@ public final class DefaultAudioSink implements AudioSink { ...@@ -2308,7 +2183,7 @@ public final class DefaultAudioSink implements AudioSink {
if (tunneling) { if (tunneling) {
return getAudioTrackTunnelingAttributesV21(); return getAudioTrackTunnelingAttributesV21();
} else { } else {
return audioAttributes.getAudioAttributesV21(); return audioAttributes.getAudioAttributesV21().audioAttributes;
} }
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment