Commit 2c720102 by Ian Baker Committed by GitHub

Merge pull request #88 from androidx/main-r1.0.0-beta01

r1.0.0 beta01
parents 72a4fb08 5f741bbe
Showing with 2238 additions and 782 deletions
---
name: Bug report
about: Issue template for a bug report.
title: ''
labels: bug, needs triage
assignees: ''
---
We can only process bug reports that are actionable. Unclear bug reports or
reports with insufficient information may not get attention.
Before filing a bug:
-------------------------
- Search existing issues, including issues that are closed:
https://github.com/androidx/media/issues?q=is%3Aissue
- For ExoPlayer-related bugs, please also check the ExoPlayer tracker:
https://github.com/google/ExoPlayer/issues?q=is%3Aissue
When reporting a bug:
-------------------------
Describe how the issue can be reproduced, ideally using one of the demo apps
or a small sample app that you’re able to share as source code on GitHub. To
increase the chance of your issue getting attention, please also include:
- Clear reproduction steps including observed and expected behavior
- Output of running "adb bugreport" in the console shortly after encountering
the issue
- URI to test content for reproduction
- For protected content:
- DRM scheme and license server URL
- Authentication HTTP headers
- AndroidX Media version number
- Android version
- Android device
If there's something you don't want to post publicly, please submit the issue,
then email the link/bug report to dev.exoplayer@gmail.com using a subject in the
format "Issue #1234", where #1234 is your issue number (we don't reply to
emails).
name: Bug Report
description: Report a bug in the Media3 library
labels: ["bug", "needs triage"]
body:
- type: markdown
attributes:
value: |
We can only process bug reports that are actionable. Unclear bug reports or reports with insufficient information may not get attention.
Before filing a bug:
-------------------------
- Search existing issues, including issues that are closed: https://github.com/androidx/media/issues?q=is%3Aissue
- For ExoPlayer-related bugs, please also check the ExoPlayer tracker: https://github.com/google/ExoPlayer/issues?q=is%3Aissue
- type: dropdown
attributes:
label: Media3 Version
description: What version of Media3 are you using?
options:
- 1.0.0-beta01
- 1.0.0-alpha03
- 1.0.0-alpha02
- 1.0.0-alpha01
validations:
required: true
- type: textarea
attributes:
label: Devices that reproduce the issue
placeholder: |
Example:
* Pixel 4 running Android 12
* Samsung S21 running Android 11
validations:
required: true
- type: textarea
attributes:
label: Devices that do not reproduce the issue
placeholder: |
Example:
* Pixel 3 running Android Pie
- type: dropdown
attributes:
label: Reproducible in the demo app?
description: Please try and reproduce the issue in the [Media3 demo app](https://github.com/androidx/media/tree/release/demos/main).
options:
- "Yes"
- "No"
- Not tested
validations:
required: true
- type: textarea
attributes:
label: Reproduction steps
description: Clear and complete steps we can use to reproduce the problem
placeholder: |
Example:
1. Play the attached media in the demo app
2. Seek forward 10s
validations:
required: true
- type: textarea
attributes:
label: Expected result
placeholder: |
Example:
The media plays successfully
validations:
required: true
- type: textarea
attributes:
label: Actual result
placeholder: |
Example:
Playback crashes with the following stack trace:
...
validations:
required: true
- type: textarea
attributes:
label: Media
description: |
Media we can use to reproduce the problem. Either:
* Attach a file here
* Include a media URL
* Refer to a piece of media from the demo app (e.g. `Misc > Dizzy (MP4)`)
* If you don't want to post media publicly please email the info to dev.exoplayer@gmail.com with subject 'Issue #\<issuenumber\>' after filing this issue, and note that you will do this here.
* If you are certain the issue does not depend on the media being played, enter "Not applicable" here.
For DRM-protected media please also include the scheme and license server URL.
validations:
required: true
- type: checkboxes
attributes:
label: Bug Report
description: |
After filing this issue please run `adb bugreport` shortly after reproducing the problem (ideally in the [demo app](https://github.com/androidx/media/tree/release/demos/main)) to capture a zip file, and email this to dev.exoplayer@gmail.com with subject 'Issue #\<issuenumber\>'.
**Note:** Logcat output is **not** the same as a full bug report, and is often missing information that's useful for diagnosing issues. Please ensure you're sending a full bug report zip file.
options:
- label: You will email the zip file produced by `adb bugreport` to dev.exoplayer@gmail.com after filing this issue.
blank_issues_enabled: false
This diff could not be displayed because it is too large.
...@@ -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
}
} }
...@@ -12,21 +12,21 @@ ...@@ -12,21 +12,21 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
project.ext { project.ext {
releaseVersion = '1.0.0-alpha03' releaseVersion = '1.0.0-beta01'
releaseVersionCode = 1_000_000_0_03 releaseVersionCode = 1_000_000_1_01
minSdkVersion = 16 minSdkVersion = 16
appTargetSdkVersion = 29 appTargetSdkVersion = 29
// Upgrading this requires [Internal ref: b/193254928] to be fixed, or some // Upgrading this requires [Internal ref: b/193254928] to be fixed, or some
// additional robolectric config. // additional robolectric config.
targetSdkVersion = 30 targetSdkVersion = 30
compileSdkVersion = 31 compileSdkVersion = 32
dexmakerVersion = '2.28.1' dexmakerVersion = '2.28.1'
junitVersion = '4.13.2' junitVersion = '4.13.2'
// Use the same Guava version as the Android repo: // Use the same Guava version as the Android repo:
// https://cs.android.com/android/platform/superproject/+/master:external/guava/METADATA // https://cs.android.com/android/platform/superproject/+/master:external/guava/METADATA
guavaVersion = '31.0.1-android' guavaVersion = '31.0.1-android'
mockitoVersion = '3.12.4' mockitoVersion = '3.12.4'
robolectricVersion = '4.6.1' robolectricVersion = '4.8.1'
// Keep this in sync with Google's internal Checker Framework version. // Keep this in sync with Google's internal Checker Framework version.
checkerframeworkVersion = '3.13.0' checkerframeworkVersion = '3.13.0'
checkerframeworkCompatVersion = '2.5.5' checkerframeworkCompatVersion = '2.5.5'
......
...@@ -87,3 +87,7 @@ include modulePrefix + 'test-data' ...@@ -87,3 +87,7 @@ include modulePrefix + 'test-data'
project(modulePrefix + 'test-data').projectDir = new File(rootDir, 'libraries/test_data') project(modulePrefix + 'test-data').projectDir = new File(rootDir, 'libraries/test_data')
include modulePrefix + 'test-utils' include modulePrefix + 'test-utils'
project(modulePrefix + 'test-utils').projectDir = new File(rootDir, 'libraries/test_utils') project(modulePrefix + 'test-utils').projectDir = new File(rootDir, 'libraries/test_utils')
include modulePrefix + 'test-session-common'
project(modulePrefix + 'test-session-common').projectDir = new File(rootDir, 'libraries/test_session_common')
include modulePrefix + 'test-session-current'
project(modulePrefix + 'test-session-current').projectDir = new File(rootDir, 'libraries/test_session_current')
...@@ -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());
} }
} }
......
...@@ -26,7 +26,7 @@ import androidx.media3.common.Player; ...@@ -26,7 +26,7 @@ import androidx.media3.common.Player;
import androidx.media3.common.Player.DiscontinuityReason; import androidx.media3.common.Player.DiscontinuityReason;
import androidx.media3.common.Player.TimelineChangeReason; import androidx.media3.common.Player.TimelineChangeReason;
import androidx.media3.common.Timeline; import androidx.media3.common.Timeline;
import androidx.media3.common.TracksInfo; import androidx.media3.common.Tracks;
import androidx.media3.exoplayer.ExoPlayer; import androidx.media3.exoplayer.ExoPlayer;
import androidx.media3.ui.PlayerControlView; import androidx.media3.ui.PlayerControlView;
import androidx.media3.ui.PlayerView; import androidx.media3.ui.PlayerView;
...@@ -57,7 +57,7 @@ import java.util.ArrayList; ...@@ -57,7 +57,7 @@ import java.util.ArrayList;
private final ArrayList<MediaItem> mediaQueue; private final ArrayList<MediaItem> mediaQueue;
private final Listener listener; private final Listener listener;
private TracksInfo lastSeenTrackGroupInfo; private Tracks lastSeenTracks;
private int currentItemIndex; private int currentItemIndex;
private Player currentPlayer; private Player currentPlayer;
...@@ -219,19 +219,19 @@ import java.util.ArrayList; ...@@ -219,19 +219,19 @@ import java.util.ArrayList;
} }
@Override @Override
public void onTracksInfoChanged(TracksInfo tracksInfo) { public void onTracksChanged(Tracks tracks) {
if (currentPlayer != localPlayer || tracksInfo == lastSeenTrackGroupInfo) { if (currentPlayer != localPlayer || tracks == lastSeenTracks) {
return; return;
} }
if (!tracksInfo.isTypeSupportedOrEmpty( if (tracks.containsType(C.TRACK_TYPE_VIDEO)
C.TRACK_TYPE_VIDEO, /* allowExceedsCapabilities= */ true)) { && !tracks.isTypeSupported(C.TRACK_TYPE_VIDEO, /* allowExceedsCapabilities= */ true)) {
listener.onUnsupportedTrack(C.TRACK_TYPE_VIDEO); listener.onUnsupportedTrack(C.TRACK_TYPE_VIDEO);
} }
if (!tracksInfo.isTypeSupportedOrEmpty( if (tracks.containsType(C.TRACK_TYPE_AUDIO)
C.TRACK_TYPE_AUDIO, /* allowExceedsCapabilities= */ true)) { && !tracks.isTypeSupported(C.TRACK_TYPE_AUDIO, /* allowExceedsCapabilities= */ true)) {
listener.onUnsupportedTrack(C.TRACK_TYPE_AUDIO); listener.onUnsupportedTrack(C.TRACK_TYPE_AUDIO);
} }
lastSeenTrackGroupInfo = tracksInfo; lastSeenTracks = tracks;
} }
// CastPlayer.SessionAvailabilityListener implementation. // CastPlayer.SessionAvailabilityListener implementation.
......
...@@ -27,6 +27,7 @@ import android.graphics.drawable.BitmapDrawable; ...@@ -27,6 +27,7 @@ import android.graphics.drawable.BitmapDrawable;
import android.opengl.GLES20; import android.opengl.GLES20;
import android.opengl.GLUtils; import android.opengl.GLUtils;
import androidx.media3.common.C; import androidx.media3.common.C;
import androidx.media3.common.util.GlProgram;
import androidx.media3.common.util.GlUtil; import androidx.media3.common.util.GlUtil;
import java.io.IOException; import java.io.IOException;
import java.util.Locale; import java.util.Locale;
...@@ -50,7 +51,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; ...@@ -50,7 +51,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
private final Bitmap logoBitmap; private final Bitmap logoBitmap;
private final Canvas overlayCanvas; private final Canvas overlayCanvas;
private GlUtil.@MonotonicNonNull Program program; private @MonotonicNonNull GlProgram program;
private float bitmapScaleX; private float bitmapScaleX;
private float bitmapScaleY; private float bitmapScaleY;
...@@ -78,7 +79,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; ...@@ -78,7 +79,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
public void initialize() { public void initialize() {
try { try {
program = program =
new GlUtil.Program( new GlProgram(
context, context,
/* vertexShaderFilePath= */ "bitmap_overlay_video_processor_vertex.glsl", /* vertexShaderFilePath= */ "bitmap_overlay_video_processor_vertex.glsl",
/* fragmentShaderFilePath= */ "bitmap_overlay_video_processor_fragment.glsl"); /* fragmentShaderFilePath= */ "bitmap_overlay_video_processor_fragment.glsl");
...@@ -86,9 +87,13 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; ...@@ -86,9 +87,13 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
throw new IllegalStateException(e); throw new IllegalStateException(e);
} }
program.setBufferAttribute( program.setBufferAttribute(
"aFramePosition", GlUtil.getNormalizedCoordinateBounds(), GlUtil.RECTANGLE_VERTICES_COUNT); "aFramePosition",
GlUtil.getNormalizedCoordinateBounds(),
GlUtil.HOMOGENEOUS_COORDINATE_VECTOR_SIZE);
program.setBufferAttribute( program.setBufferAttribute(
"aTexCoords", GlUtil.getTextureCoordinateBounds(), GlUtil.RECTANGLE_VERTICES_COUNT); "aTexCoords",
GlUtil.getTextureCoordinateBounds(),
GlUtil.HOMOGENEOUS_COORDINATE_VECTOR_SIZE);
GLES20.glGenTextures(1, textures, 0); GLES20.glGenTextures(1, textures, 0);
GLES20.glBindTexture(GL10.GL_TEXTURE_2D, textures[0]); GLES20.glBindTexture(GL10.GL_TEXTURE_2D, textures[0]);
GLES20.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_NEAREST); GLES20.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_NEAREST);
...@@ -117,9 +122,9 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; ...@@ -117,9 +122,9 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
GlUtil.checkGlError(); GlUtil.checkGlError();
// Run the shader program. // Run the shader program.
GlUtil.Program program = checkNotNull(this.program); GlProgram program = checkNotNull(this.program);
program.setSamplerTexIdUniform("uTexSampler0", frameTexture, /* unit= */ 0); program.setSamplerTexIdUniform("uTexSampler0", frameTexture, /* texUnitIndex= */ 0);
program.setSamplerTexIdUniform("uTexSampler1", textures[0], /* unit= */ 1); program.setSamplerTexIdUniform("uTexSampler1", textures[0], /* texUnitIndex= */ 1);
program.setFloatUniform("uScaleX", bitmapScaleX); program.setFloatUniform("uScaleX", bitmapScaleX);
program.setFloatUniform("uScaleY", bitmapScaleY); program.setFloatUniform("uScaleY", bitmapScaleY);
program.setFloatsUniform("uTexTransform", transformMatrix); program.setFloatsUniform("uTexTransform", transformMatrix);
......
...@@ -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;
...@@ -32,7 +33,6 @@ import androidx.media3.common.util.Util; ...@@ -32,7 +33,6 @@ import androidx.media3.common.util.Util;
import androidx.media3.datasource.DataSource; import androidx.media3.datasource.DataSource;
import androidx.media3.datasource.DefaultDataSource; import androidx.media3.datasource.DefaultDataSource;
import androidx.media3.datasource.DefaultHttpDataSource; import androidx.media3.datasource.DefaultHttpDataSource;
import androidx.media3.datasource.HttpDataSource;
import androidx.media3.exoplayer.ExoPlayer; import androidx.media3.exoplayer.ExoPlayer;
import androidx.media3.exoplayer.dash.DashMediaSource; import androidx.media3.exoplayer.dash.DashMediaSource;
import androidx.media3.exoplayer.drm.DefaultDrmSessionManager; import androidx.media3.exoplayer.drm.DefaultDrmSessionManager;
...@@ -144,7 +144,7 @@ public final class MainActivity extends Activity { ...@@ -144,7 +144,7 @@ public final class MainActivity extends Activity {
String drmScheme = Assertions.checkNotNull(intent.getStringExtra(DRM_SCHEME_EXTRA)); String drmScheme = Assertions.checkNotNull(intent.getStringExtra(DRM_SCHEME_EXTRA));
String drmLicenseUrl = Assertions.checkNotNull(intent.getStringExtra(DRM_LICENSE_URL_EXTRA)); String drmLicenseUrl = Assertions.checkNotNull(intent.getStringExtra(DRM_LICENSE_URL_EXTRA));
UUID drmSchemeUuid = Assertions.checkNotNull(Util.getDrmUuid(drmScheme)); UUID drmSchemeUuid = Assertions.checkNotNull(Util.getDrmUuid(drmScheme));
HttpDataSource.Factory licenseDataSourceFactory = new DefaultHttpDataSource.Factory(); DataSource.Factory licenseDataSourceFactory = new DefaultHttpDataSource.Factory();
HttpMediaDrmCallback drmCallback = HttpMediaDrmCallback drmCallback =
new HttpMediaDrmCallback(drmLicenseUrl, licenseDataSourceFactory); new HttpMediaDrmCallback(drmLicenseUrl, licenseDataSourceFactory);
drmSessionManager = drmSessionManager =
...@@ -157,13 +157,18 @@ public final class MainActivity extends Activity { ...@@ -157,13 +157,18 @@ public final class MainActivity extends Activity {
DataSource.Factory dataSourceFactory = new DefaultDataSource.Factory(this); DataSource.Factory dataSourceFactory = new DefaultDataSource.Factory(this);
MediaSource mediaSource; MediaSource mediaSource;
@C.ContentType int type = Util.inferContentType(uri, intent.getStringExtra(EXTENSION_EXTRA)); @Nullable String fileExtension = intent.getStringExtra(EXTENSION_EXTRA);
if (type == C.TYPE_DASH) { @C.ContentType
int type =
TextUtils.isEmpty(fileExtension)
? Util.inferContentType(uri)
: Util.inferContentTypeForExtension(fileExtension);
if (type == C.CONTENT_TYPE_DASH) {
mediaSource = mediaSource =
new DashMediaSource.Factory(dataSourceFactory) new DashMediaSource.Factory(dataSourceFactory)
.setDrmSessionManagerProvider(unusedMediaItem -> drmSessionManager) .setDrmSessionManagerProvider(unusedMediaItem -> drmSessionManager)
.createMediaSource(MediaItem.fromUri(uri)); .createMediaSource(MediaItem.fromUri(uri));
} else if (type == C.TYPE_OTHER) { } else if (type == C.CONTENT_TYPE_OTHER) {
mediaSource = mediaSource =
new ProgressiveMediaSource.Factory(dataSourceFactory) new ProgressiveMediaSource.Factory(dataSourceFactory)
.setDrmSessionManagerProvider(unusedMediaItem -> drmSessionManager) .setDrmSessionManagerProvider(unusedMediaItem -> drmSessionManager)
...@@ -181,7 +186,7 @@ public final class MainActivity extends Activity { ...@@ -181,7 +186,7 @@ public final class MainActivity extends Activity {
Assertions.checkNotNull(this.videoProcessingGLSurfaceView); Assertions.checkNotNull(this.videoProcessingGLSurfaceView);
videoProcessingGLSurfaceView.setPlayer(player); videoProcessingGLSurfaceView.setPlayer(player);
Assertions.checkNotNull(playerView).setPlayer(player); Assertions.checkNotNull(playerView).setPlayer(player);
player.addAnalyticsListener(new EventLogger(/* trackSelector= */ null)); player.addAnalyticsListener(new EventLogger());
this.player = player; this.player = player;
} }
......
...@@ -20,6 +20,7 @@ import static androidx.media3.demo.main.DemoUtil.DOWNLOAD_NOTIFICATION_CHANNEL_I ...@@ -20,6 +20,7 @@ import static androidx.media3.demo.main.DemoUtil.DOWNLOAD_NOTIFICATION_CHANNEL_I
import android.app.Notification; import android.app.Notification;
import android.content.Context; import android.content.Context;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.annotation.OptIn;
import androidx.media3.common.util.NotificationUtil; import androidx.media3.common.util.NotificationUtil;
import androidx.media3.common.util.Util; import androidx.media3.common.util.Util;
import androidx.media3.exoplayer.offline.Download; import androidx.media3.exoplayer.offline.Download;
...@@ -32,6 +33,7 @@ import androidx.media3.exoplayer.scheduler.Scheduler; ...@@ -32,6 +33,7 @@ import androidx.media3.exoplayer.scheduler.Scheduler;
import java.util.List; import java.util.List;
/** A service for downloading media. */ /** A service for downloading media. */
@OptIn(markerClass = androidx.media3.common.util.UnstableApi.class)
public class DemoDownloadService extends DownloadService { public class DemoDownloadService extends DownloadService {
private static final int JOB_ID = 1; private static final int JOB_ID = 1;
......
...@@ -16,12 +16,12 @@ ...@@ -16,12 +16,12 @@
package androidx.media3.demo.main; package androidx.media3.demo.main;
import android.content.Context; import android.content.Context;
import androidx.annotation.OptIn;
import androidx.media3.database.DatabaseProvider; import androidx.media3.database.DatabaseProvider;
import androidx.media3.database.StandaloneDatabaseProvider; import androidx.media3.database.StandaloneDatabaseProvider;
import androidx.media3.datasource.DataSource; import androidx.media3.datasource.DataSource;
import androidx.media3.datasource.DefaultDataSource; import androidx.media3.datasource.DefaultDataSource;
import androidx.media3.datasource.DefaultHttpDataSource; import androidx.media3.datasource.DefaultHttpDataSource;
import androidx.media3.datasource.HttpDataSource;
import androidx.media3.datasource.cache.Cache; import androidx.media3.datasource.cache.Cache;
import androidx.media3.datasource.cache.CacheDataSource; import androidx.media3.datasource.cache.CacheDataSource;
import androidx.media3.datasource.cache.NoOpCacheEvictor; import androidx.media3.datasource.cache.NoOpCacheEvictor;
...@@ -59,7 +59,7 @@ public final class DemoUtil { ...@@ -59,7 +59,7 @@ public final class DemoUtil {
private static final String DOWNLOAD_CONTENT_DIRECTORY = "downloads"; private static final String DOWNLOAD_CONTENT_DIRECTORY = "downloads";
private static DataSource.@MonotonicNonNull Factory dataSourceFactory; private static DataSource.@MonotonicNonNull Factory dataSourceFactory;
private static HttpDataSource.@MonotonicNonNull Factory httpDataSourceFactory; private static DataSource.@MonotonicNonNull Factory httpDataSourceFactory;
private static @MonotonicNonNull DatabaseProvider databaseProvider; private static @MonotonicNonNull DatabaseProvider databaseProvider;
private static @MonotonicNonNull File downloadDirectory; private static @MonotonicNonNull File downloadDirectory;
private static @MonotonicNonNull Cache downloadCache; private static @MonotonicNonNull Cache downloadCache;
...@@ -72,6 +72,7 @@ public final class DemoUtil { ...@@ -72,6 +72,7 @@ public final class DemoUtil {
return BuildConfig.USE_DECODER_EXTENSIONS; return BuildConfig.USE_DECODER_EXTENSIONS;
} }
@OptIn(markerClass = androidx.media3.common.util.UnstableApi.class)
public static RenderersFactory buildRenderersFactory( public static RenderersFactory buildRenderersFactory(
Context context, boolean preferExtensionRenderer) { Context context, boolean preferExtensionRenderer) {
@DefaultRenderersFactory.ExtensionRendererMode @DefaultRenderersFactory.ExtensionRendererMode
...@@ -85,7 +86,7 @@ public final class DemoUtil { ...@@ -85,7 +86,7 @@ public final class DemoUtil {
.setExtensionRendererMode(extensionRendererMode); .setExtensionRendererMode(extensionRendererMode);
} }
public static synchronized HttpDataSource.Factory getHttpDataSourceFactory(Context context) { public static synchronized DataSource.Factory getHttpDataSourceFactory(Context context) {
if (httpDataSourceFactory == null) { if (httpDataSourceFactory == null) {
if (USE_CRONET_FOR_NETWORKING) { if (USE_CRONET_FOR_NETWORKING) {
context = context.getApplicationContext(); context = context.getApplicationContext();
...@@ -117,6 +118,7 @@ public final class DemoUtil { ...@@ -117,6 +118,7 @@ public final class DemoUtil {
return dataSourceFactory; return dataSourceFactory;
} }
@OptIn(markerClass = androidx.media3.common.util.UnstableApi.class)
public static synchronized DownloadNotificationHelper getDownloadNotificationHelper( public static synchronized DownloadNotificationHelper getDownloadNotificationHelper(
Context context) { Context context) {
if (downloadNotificationHelper == null) { if (downloadNotificationHelper == null) {
...@@ -136,6 +138,7 @@ public final class DemoUtil { ...@@ -136,6 +138,7 @@ public final class DemoUtil {
return downloadTracker; return downloadTracker;
} }
@OptIn(markerClass = androidx.media3.common.util.UnstableApi.class)
private static synchronized Cache getDownloadCache(Context context) { private static synchronized Cache getDownloadCache(Context context) {
if (downloadCache == null) { if (downloadCache == null) {
File downloadContentDirectory = File downloadContentDirectory =
...@@ -147,6 +150,7 @@ public final class DemoUtil { ...@@ -147,6 +150,7 @@ public final class DemoUtil {
return downloadCache; return downloadCache;
} }
@OptIn(markerClass = androidx.media3.common.util.UnstableApi.class)
private static synchronized void ensureDownloadManagerInitialized(Context context) { private static synchronized void ensureDownloadManagerInitialized(Context context) {
if (downloadManager == null) { if (downloadManager == null) {
downloadManager = downloadManager =
...@@ -161,6 +165,7 @@ public final class DemoUtil { ...@@ -161,6 +165,7 @@ public final class DemoUtil {
} }
} }
@OptIn(markerClass = androidx.media3.common.util.UnstableApi.class)
private static synchronized DatabaseProvider getDatabaseProvider(Context context) { private static synchronized DatabaseProvider getDatabaseProvider(Context context) {
if (databaseProvider == null) { if (databaseProvider == null) {
databaseProvider = new StandaloneDatabaseProvider(context); databaseProvider = new StandaloneDatabaseProvider(context);
...@@ -178,6 +183,7 @@ public final class DemoUtil { ...@@ -178,6 +183,7 @@ public final class DemoUtil {
return downloadDirectory; return downloadDirectory;
} }
@OptIn(markerClass = androidx.media3.common.util.UnstableApi.class)
private static CacheDataSource.Factory buildReadOnlyCacheDataSource( private static CacheDataSource.Factory buildReadOnlyCacheDataSource(
DataSource.Factory upstreamFactory, Cache cache) { DataSource.Factory upstreamFactory, Cache cache) {
return new CacheDataSource.Factory() return new CacheDataSource.Factory()
......
...@@ -15,8 +15,7 @@ ...@@ -15,8 +15,7 @@
*/ */
package androidx.media3.demo.main; package androidx.media3.demo.main;
import static androidx.media3.common.util.Assertions.checkNotNull; import static com.google.common.base.Preconditions.checkNotNull;
import static androidx.media3.common.util.Assertions.checkStateNotNull;
import android.content.Context; import android.content.Context;
import android.content.DialogInterface; import android.content.DialogInterface;
...@@ -24,16 +23,18 @@ import android.net.Uri; ...@@ -24,16 +23,18 @@ import android.net.Uri;
import android.os.AsyncTask; import android.os.AsyncTask;
import android.widget.Toast; import android.widget.Toast;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.annotation.OptIn;
import androidx.annotation.RequiresApi; import androidx.annotation.RequiresApi;
import androidx.fragment.app.FragmentManager; import androidx.fragment.app.FragmentManager;
import androidx.media3.common.DrmInitData; import androidx.media3.common.DrmInitData;
import androidx.media3.common.Format; import androidx.media3.common.Format;
import androidx.media3.common.MediaItem; import androidx.media3.common.MediaItem;
import androidx.media3.common.TrackGroup; import androidx.media3.common.TrackGroup;
import androidx.media3.common.TrackGroupArray; import androidx.media3.common.TrackSelectionParameters;
import androidx.media3.common.Tracks;
import androidx.media3.common.util.Log; import androidx.media3.common.util.Log;
import androidx.media3.common.util.Util; import androidx.media3.common.util.Util;
import androidx.media3.datasource.HttpDataSource; import androidx.media3.datasource.DataSource;
import androidx.media3.exoplayer.RenderersFactory; import androidx.media3.exoplayer.RenderersFactory;
import androidx.media3.exoplayer.drm.DrmSession; import androidx.media3.exoplayer.drm.DrmSession;
import androidx.media3.exoplayer.drm.DrmSessionEventListener; import androidx.media3.exoplayer.drm.DrmSessionEventListener;
...@@ -46,13 +47,14 @@ import androidx.media3.exoplayer.offline.DownloadIndex; ...@@ -46,13 +47,14 @@ import androidx.media3.exoplayer.offline.DownloadIndex;
import androidx.media3.exoplayer.offline.DownloadManager; import androidx.media3.exoplayer.offline.DownloadManager;
import androidx.media3.exoplayer.offline.DownloadRequest; import androidx.media3.exoplayer.offline.DownloadRequest;
import androidx.media3.exoplayer.offline.DownloadService; import androidx.media3.exoplayer.offline.DownloadService;
import androidx.media3.exoplayer.trackselection.DefaultTrackSelector; import androidx.media3.exoplayer.source.TrackGroupArray;
import androidx.media3.exoplayer.trackselection.MappingTrackSelector.MappedTrackInfo; import androidx.media3.exoplayer.trackselection.MappingTrackSelector.MappedTrackInfo;
import java.io.IOException; import java.io.IOException;
import java.util.HashMap; import java.util.HashMap;
import java.util.concurrent.CopyOnWriteArraySet; import java.util.concurrent.CopyOnWriteArraySet;
/** Tracks media that has been downloaded. */ /** Tracks media that has been downloaded. */
@OptIn(markerClass = androidx.media3.common.util.UnstableApi.class)
public class DownloadTracker { public class DownloadTracker {
/** Listens for changes in the tracked downloads. */ /** Listens for changes in the tracked downloads. */
...@@ -65,31 +67,26 @@ public class DownloadTracker { ...@@ -65,31 +67,26 @@ public class DownloadTracker {
private static final String TAG = "DownloadTracker"; private static final String TAG = "DownloadTracker";
private final Context context; private final Context context;
private final HttpDataSource.Factory httpDataSourceFactory; private final DataSource.Factory dataSourceFactory;
private final CopyOnWriteArraySet<Listener> listeners; private final CopyOnWriteArraySet<Listener> listeners;
private final HashMap<Uri, Download> downloads; private final HashMap<Uri, Download> downloads;
private final DownloadIndex downloadIndex; private final DownloadIndex downloadIndex;
private final DefaultTrackSelector.Parameters trackSelectorParameters;
@Nullable private StartDownloadDialogHelper startDownloadDialogHelper; @Nullable private StartDownloadDialogHelper startDownloadDialogHelper;
public DownloadTracker( public DownloadTracker(
Context context, Context context, DataSource.Factory dataSourceFactory, DownloadManager downloadManager) {
HttpDataSource.Factory httpDataSourceFactory,
DownloadManager downloadManager) {
this.context = context.getApplicationContext(); this.context = context.getApplicationContext();
this.httpDataSourceFactory = httpDataSourceFactory; this.dataSourceFactory = dataSourceFactory;
listeners = new CopyOnWriteArraySet<>(); listeners = new CopyOnWriteArraySet<>();
downloads = new HashMap<>(); downloads = new HashMap<>();
downloadIndex = downloadManager.getDownloadIndex(); downloadIndex = downloadManager.getDownloadIndex();
trackSelectorParameters = DownloadHelper.getDefaultTrackSelectorParameters(context);
downloadManager.addListener(new DownloadManagerListener()); downloadManager.addListener(new DownloadManagerListener());
loadDownloads(); loadDownloads();
} }
public void addListener(Listener listener) { public void addListener(Listener listener) {
checkNotNull(listener); listeners.add(checkNotNull(listener));
listeners.add(listener);
} }
public void removeListener(Listener listener) { public void removeListener(Listener listener) {
...@@ -120,8 +117,7 @@ public class DownloadTracker { ...@@ -120,8 +117,7 @@ public class DownloadTracker {
startDownloadDialogHelper = startDownloadDialogHelper =
new StartDownloadDialogHelper( new StartDownloadDialogHelper(
fragmentManager, fragmentManager,
DownloadHelper.forMediaItem( DownloadHelper.forMediaItem(context, mediaItem, renderersFactory, dataSourceFactory),
context, mediaItem, renderersFactory, httpDataSourceFactory),
mediaItem); mediaItem);
} }
} }
...@@ -159,7 +155,7 @@ public class DownloadTracker { ...@@ -159,7 +155,7 @@ public class DownloadTracker {
private final class StartDownloadDialogHelper private final class StartDownloadDialogHelper
implements DownloadHelper.Callback, implements DownloadHelper.Callback,
DialogInterface.OnClickListener, TrackSelectionDialog.TrackSelectionListener,
DialogInterface.OnDismissListener { DialogInterface.OnDismissListener {
private final FragmentManager fragmentManager; private final FragmentManager fragmentManager;
...@@ -167,7 +163,6 @@ public class DownloadTracker { ...@@ -167,7 +163,6 @@ public class DownloadTracker {
private final MediaItem mediaItem; private final MediaItem mediaItem;
private TrackSelectionDialog trackSelectionDialog; private TrackSelectionDialog trackSelectionDialog;
private MappedTrackInfo mappedTrackInfo;
private WidevineOfflineLicenseFetchTask widevineOfflineLicenseFetchTask; private WidevineOfflineLicenseFetchTask widevineOfflineLicenseFetchTask;
@Nullable private byte[] keySetId; @Nullable private byte[] keySetId;
...@@ -220,7 +215,7 @@ public class DownloadTracker { ...@@ -220,7 +215,7 @@ public class DownloadTracker {
new WidevineOfflineLicenseFetchTask( new WidevineOfflineLicenseFetchTask(
format, format,
mediaItem.localConfiguration.drmConfiguration, mediaItem.localConfiguration.drmConfiguration,
httpDataSourceFactory, dataSourceFactory,
/* dialogHelper= */ this, /* dialogHelper= */ this,
helper); helper);
widevineOfflineLicenseFetchTask.execute(); widevineOfflineLicenseFetchTask.execute();
...@@ -237,21 +232,13 @@ public class DownloadTracker { ...@@ -237,21 +232,13 @@ public class DownloadTracker {
Log.e(TAG, logMessage, e); Log.e(TAG, logMessage, e);
} }
// DialogInterface.OnClickListener implementation. // TrackSelectionListener implementation.
@Override @Override
public void onClick(DialogInterface dialog, int which) { public void onTracksSelected(TrackSelectionParameters trackSelectionParameters) {
for (int periodIndex = 0; periodIndex < downloadHelper.getPeriodCount(); periodIndex++) { for (int periodIndex = 0; periodIndex < downloadHelper.getPeriodCount(); periodIndex++) {
downloadHelper.clearTrackSelections(periodIndex); downloadHelper.clearTrackSelections(periodIndex);
for (int i = 0; i < mappedTrackInfo.getRendererCount(); i++) { downloadHelper.addTrackSelection(periodIndex, trackSelectionParameters);
if (!trackSelectionDialog.getIsDisabled(/* rendererIndex= */ i)) {
downloadHelper.addTrackSelectionForSingleRenderer(
periodIndex,
/* rendererIndex= */ i,
trackSelectorParameters,
trackSelectionDialog.getOverrides(/* rendererIndex= */ i));
}
}
} }
DownloadRequest downloadRequest = buildDownloadRequest(); DownloadRequest downloadRequest = buildDownloadRequest();
if (downloadRequest.streamKeys.isEmpty()) { if (downloadRequest.streamKeys.isEmpty()) {
...@@ -316,21 +303,21 @@ public class DownloadTracker { ...@@ -316,21 +303,21 @@ public class DownloadTracker {
return; return;
} }
mappedTrackInfo = downloadHelper.getMappedTrackInfo(/* periodIndex= */ 0); Tracks tracks = downloadHelper.getTracks(/* periodIndex= */ 0);
if (!TrackSelectionDialog.willHaveContent(mappedTrackInfo)) { if (!TrackSelectionDialog.willHaveContent(tracks)) {
Log.d(TAG, "No dialog content. Downloading entire stream."); Log.d(TAG, "No dialog content. Downloading entire stream.");
startDownload(); startDownload();
downloadHelper.release(); downloadHelper.release();
return; return;
} }
trackSelectionDialog = trackSelectionDialog =
TrackSelectionDialog.createForMappedTrackInfoAndParameters( TrackSelectionDialog.createForTracksAndParameters(
/* titleId= */ R.string.exo_download_description, /* titleId= */ R.string.exo_download_description,
mappedTrackInfo, tracks,
trackSelectorParameters, DownloadHelper.getDefaultTrackSelectorParameters(context),
/* allowAdaptiveSelections= */ false, /* allowAdaptiveSelections= */ false,
/* allowMultipleOverrides= */ true, /* allowMultipleOverrides= */ true,
/* onClickListener= */ this, /* onTracksSelectedListener= */ this,
/* onDismissListener= */ this); /* onDismissListener= */ this);
trackSelectionDialog.show(fragmentManager, /* tag= */ null); trackSelectionDialog.show(fragmentManager, /* tag= */ null);
} }
...@@ -371,7 +358,7 @@ public class DownloadTracker { ...@@ -371,7 +358,7 @@ public class DownloadTracker {
private final Format format; private final Format format;
private final MediaItem.DrmConfiguration drmConfiguration; private final MediaItem.DrmConfiguration drmConfiguration;
private final HttpDataSource.Factory httpDataSourceFactory; private final DataSource.Factory dataSourceFactory;
private final StartDownloadDialogHelper dialogHelper; private final StartDownloadDialogHelper dialogHelper;
private final DownloadHelper downloadHelper; private final DownloadHelper downloadHelper;
...@@ -381,12 +368,12 @@ public class DownloadTracker { ...@@ -381,12 +368,12 @@ public class DownloadTracker {
public WidevineOfflineLicenseFetchTask( public WidevineOfflineLicenseFetchTask(
Format format, Format format,
MediaItem.DrmConfiguration drmConfiguration, MediaItem.DrmConfiguration drmConfiguration,
HttpDataSource.Factory httpDataSourceFactory, DataSource.Factory dataSourceFactory,
StartDownloadDialogHelper dialogHelper, StartDownloadDialogHelper dialogHelper,
DownloadHelper downloadHelper) { DownloadHelper downloadHelper) {
this.format = format; this.format = format;
this.drmConfiguration = drmConfiguration; this.drmConfiguration = drmConfiguration;
this.httpDataSourceFactory = httpDataSourceFactory; this.dataSourceFactory = dataSourceFactory;
this.dialogHelper = dialogHelper; this.dialogHelper = dialogHelper;
this.downloadHelper = downloadHelper; this.downloadHelper = downloadHelper;
} }
...@@ -397,7 +384,7 @@ public class DownloadTracker { ...@@ -397,7 +384,7 @@ public class DownloadTracker {
OfflineLicenseHelper.newWidevineInstance( OfflineLicenseHelper.newWidevineInstance(
drmConfiguration.licenseUri.toString(), drmConfiguration.licenseUri.toString(),
drmConfiguration.forceDefaultLicenseUri, drmConfiguration.forceDefaultLicenseUri,
httpDataSourceFactory, dataSourceFactory,
drmConfiguration.licenseRequestHeaders, drmConfiguration.licenseRequestHeaders,
new DrmSessionEventListener.EventDispatcher()); new DrmSessionEventListener.EventDispatcher());
try { try {
...@@ -415,7 +402,7 @@ public class DownloadTracker { ...@@ -415,7 +402,7 @@ public class DownloadTracker {
if (drmSessionException != null) { if (drmSessionException != null) {
dialogHelper.onOfflineLicenseFetchedError(drmSessionException); dialogHelper.onOfflineLicenseFetchedError(drmSessionException);
} else { } else {
dialogHelper.onOfflineLicenseFetched(downloadHelper, checkStateNotNull(keySetId)); dialogHelper.onOfflineLicenseFetched(downloadHelper, checkNotNull(keySetId));
} }
} }
} }
......
...@@ -15,8 +15,9 @@ ...@@ -15,8 +15,9 @@
*/ */
package androidx.media3.demo.main; package androidx.media3.demo.main;
import static androidx.media3.common.util.Assertions.checkNotNull; import static com.google.common.base.Preconditions.checkArgument;
import static androidx.media3.common.util.Assertions.checkState; import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import android.content.Intent; import android.content.Intent;
import android.net.Uri; import android.net.Uri;
...@@ -26,7 +27,6 @@ import androidx.media3.common.MediaItem; ...@@ -26,7 +27,6 @@ import androidx.media3.common.MediaItem;
import androidx.media3.common.MediaItem.ClippingConfiguration; import androidx.media3.common.MediaItem.ClippingConfiguration;
import androidx.media3.common.MediaItem.SubtitleConfiguration; import androidx.media3.common.MediaItem.SubtitleConfiguration;
import androidx.media3.common.MediaMetadata; import androidx.media3.common.MediaMetadata;
import androidx.media3.common.util.Assertions;
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 java.util.ArrayList; import java.util.ArrayList;
...@@ -86,7 +86,7 @@ public class IntentUtil { ...@@ -86,7 +86,7 @@ public class IntentUtil {
/** Populates the intent with the given list of {@link MediaItem media items}. */ /** Populates the intent with the given list of {@link MediaItem media items}. */
public static void addToIntent(List<MediaItem> mediaItems, Intent intent) { public static void addToIntent(List<MediaItem> mediaItems, Intent intent) {
Assertions.checkArgument(!mediaItems.isEmpty()); checkArgument(!mediaItems.isEmpty());
if (mediaItems.size() == 1) { if (mediaItems.size() == 1) {
MediaItem mediaItem = mediaItems.get(0); MediaItem mediaItem = mediaItems.get(0);
MediaItem.LocalConfiguration localConfiguration = checkNotNull(mediaItem.localConfiguration); MediaItem.LocalConfiguration localConfiguration = checkNotNull(mediaItem.localConfiguration);
...@@ -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());
} }
...@@ -241,7 +241,7 @@ public class IntentUtil { ...@@ -241,7 +241,7 @@ public class IntentUtil {
drmConfiguration.forcedSessionTrackTypes; drmConfiguration.forcedSessionTrackTypes;
if (!forcedDrmSessionTrackTypes.isEmpty()) { if (!forcedDrmSessionTrackTypes.isEmpty()) {
// Only video and audio together are supported. // Only video and audio together are supported.
Assertions.checkState( checkState(
forcedDrmSessionTrackTypes.size() == 2 forcedDrmSessionTrackTypes.size() == 2
&& forcedDrmSessionTrackTypes.contains(C.TRACK_TYPE_VIDEO) && forcedDrmSessionTrackTypes.contains(C.TRACK_TYPE_VIDEO)
&& forcedDrmSessionTrackTypes.contains(C.TRACK_TYPE_AUDIO)); && forcedDrmSessionTrackTypes.contains(C.TRACK_TYPE_AUDIO));
......
...@@ -15,9 +15,9 @@ ...@@ -15,9 +15,9 @@
*/ */
package androidx.media3.demo.main; package androidx.media3.demo.main;
import static androidx.media3.common.util.Assertions.checkArgument; import static com.google.common.base.Preconditions.checkArgument;
import static androidx.media3.common.util.Assertions.checkNotNull; import static com.google.common.base.Preconditions.checkNotNull;
import static androidx.media3.common.util.Assertions.checkState; import static com.google.common.base.Preconditions.checkState;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
...@@ -27,6 +27,7 @@ import android.content.res.AssetManager; ...@@ -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;
...@@ -41,6 +42,7 @@ import android.widget.ImageButton; ...@@ -41,6 +42,7 @@ import android.widget.ImageButton;
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.OptIn;
import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.app.AppCompatActivity;
import androidx.media3.common.MediaItem; import androidx.media3.common.MediaItem;
import androidx.media3.common.MediaItem.ClippingConfiguration; import androidx.media3.common.MediaItem.ClippingConfiguration;
...@@ -53,6 +55,7 @@ import androidx.media3.datasource.DataSourceUtil; ...@@ -53,6 +55,7 @@ import androidx.media3.datasource.DataSourceUtil;
import androidx.media3.datasource.DataSpec; import androidx.media3.datasource.DataSpec;
import androidx.media3.exoplayer.RenderersFactory; import androidx.media3.exoplayer.RenderersFactory;
import androidx.media3.exoplayer.offline.DownloadService; import androidx.media3.exoplayer.offline.DownloadService;
import com.google.common.base.Objects;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap;
import java.io.IOException; import java.io.IOException;
...@@ -116,8 +119,12 @@ public class SampleChooserActivity extends AppCompatActivity ...@@ -116,8 +119,12 @@ public class SampleChooserActivity extends AppCompatActivity
useExtensionRenderers = DemoUtil.useExtensionRenderers(); useExtensionRenderers = DemoUtil.useExtensionRenderers();
downloadTracker = DemoUtil.getDownloadTracker(/* context= */ this); downloadTracker = DemoUtil.getDownloadTracker(/* context= */ this);
loadSample(); loadSample();
startDownloadService();
}
// Start the download service if it should be running but it's not currently. /** Start the download service if it should be running but it's not currently. */
@OptIn(markerClass = androidx.media3.common.util.UnstableApi.class)
private void startDownloadService() {
// Starting the service in the foreground causes notification flicker if there is no scheduled // Starting the service in the foreground causes notification flicker if there is no scheduled
// action. Starting it in the background throws an exception if the app is in the background too // action. Starting it in the background throws an exception if the app is in the background too
// (e.g. if device screen is locked). // (e.g. if device screen is locked).
...@@ -271,6 +278,7 @@ public class SampleChooserActivity extends AppCompatActivity ...@@ -271,6 +278,7 @@ public class SampleChooserActivity extends AppCompatActivity
private boolean sawError; private boolean sawError;
@OptIn(markerClass = androidx.media3.common.util.UnstableApi.class)
@Override @Override
protected List<PlaylistGroup> doInBackground(String... uris) { protected List<PlaylistGroup> doInBackground(String... uris) {
List<PlaylistGroup> result = new ArrayList<>(); List<PlaylistGroup> result = new ArrayList<>();
...@@ -436,7 +444,10 @@ public class SampleChooserActivity extends AppCompatActivity ...@@ -436,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())
...@@ -447,7 +458,7 @@ public class SampleChooserActivity extends AppCompatActivity ...@@ -447,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());
...@@ -481,7 +492,7 @@ public class SampleChooserActivity extends AppCompatActivity ...@@ -481,7 +492,7 @@ public class SampleChooserActivity extends AppCompatActivity
private PlaylistGroup getGroup(String groupName, List<PlaylistGroup> groups) { private PlaylistGroup getGroup(String groupName, List<PlaylistGroup> groups) {
for (int i = 0; i < groups.size(); i++) { for (int i = 0; i < groups.size(); i++) {
if (Util.areEqual(groupName, groups.get(i).title)) { if (Objects.equal(groupName, groups.get(i).title)) {
return groups.get(i); return groups.get(i);
} }
} }
......
...@@ -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"/>
......
...@@ -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;
...@@ -35,7 +36,6 @@ import androidx.media3.common.util.Util; ...@@ -35,7 +36,6 @@ import androidx.media3.common.util.Util;
import androidx.media3.datasource.DataSource; import androidx.media3.datasource.DataSource;
import androidx.media3.datasource.DefaultDataSource; import androidx.media3.datasource.DefaultDataSource;
import androidx.media3.datasource.DefaultHttpDataSource; import androidx.media3.datasource.DefaultHttpDataSource;
import androidx.media3.datasource.HttpDataSource;
import androidx.media3.exoplayer.ExoPlayer; import androidx.media3.exoplayer.ExoPlayer;
import androidx.media3.exoplayer.dash.DashMediaSource; import androidx.media3.exoplayer.dash.DashMediaSource;
import androidx.media3.exoplayer.drm.DefaultDrmSessionManager; import androidx.media3.exoplayer.drm.DefaultDrmSessionManager;
...@@ -189,7 +189,7 @@ public final class MainActivity extends Activity { ...@@ -189,7 +189,7 @@ public final class MainActivity extends Activity {
String drmScheme = Assertions.checkNotNull(intent.getStringExtra(DRM_SCHEME_EXTRA)); String drmScheme = Assertions.checkNotNull(intent.getStringExtra(DRM_SCHEME_EXTRA));
String drmLicenseUrl = Assertions.checkNotNull(intent.getStringExtra(DRM_LICENSE_URL_EXTRA)); String drmLicenseUrl = Assertions.checkNotNull(intent.getStringExtra(DRM_LICENSE_URL_EXTRA));
UUID drmSchemeUuid = Assertions.checkNotNull(Util.getDrmUuid(drmScheme)); UUID drmSchemeUuid = Assertions.checkNotNull(Util.getDrmUuid(drmScheme));
HttpDataSource.Factory licenseDataSourceFactory = new DefaultHttpDataSource.Factory(); DataSource.Factory licenseDataSourceFactory = new DefaultHttpDataSource.Factory();
HttpMediaDrmCallback drmCallback = HttpMediaDrmCallback drmCallback =
new HttpMediaDrmCallback(drmLicenseUrl, licenseDataSourceFactory); new HttpMediaDrmCallback(drmLicenseUrl, licenseDataSourceFactory);
drmSessionManager = drmSessionManager =
...@@ -202,13 +202,18 @@ public final class MainActivity extends Activity { ...@@ -202,13 +202,18 @@ public final class MainActivity extends Activity {
DataSource.Factory dataSourceFactory = new DefaultDataSource.Factory(this); DataSource.Factory dataSourceFactory = new DefaultDataSource.Factory(this);
MediaSource mediaSource; MediaSource mediaSource;
@C.ContentType int type = Util.inferContentType(uri, intent.getStringExtra(EXTENSION_EXTRA)); @Nullable String fileExtension = intent.getStringExtra(EXTENSION_EXTRA);
if (type == C.TYPE_DASH) { @C.ContentType
int type =
TextUtils.isEmpty(fileExtension)
? Util.inferContentType(uri)
: Util.inferContentTypeForExtension(fileExtension);
if (type == C.CONTENT_TYPE_DASH) {
mediaSource = mediaSource =
new DashMediaSource.Factory(dataSourceFactory) new DashMediaSource.Factory(dataSourceFactory)
.setDrmSessionManagerProvider(unusedMediaItem -> drmSessionManager) .setDrmSessionManagerProvider(unusedMediaItem -> drmSessionManager)
.createMediaSource(MediaItem.fromUri(uri)); .createMediaSource(MediaItem.fromUri(uri));
} else if (type == C.TYPE_OTHER) { } else if (type == C.CONTENT_TYPE_OTHER) {
mediaSource = mediaSource =
new ProgressiveMediaSource.Factory(dataSourceFactory) new ProgressiveMediaSource.Factory(dataSourceFactory)
.setDrmSessionManagerProvider(unusedMediaItem -> drmSessionManager) .setDrmSessionManagerProvider(unusedMediaItem -> drmSessionManager)
......
# 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'
} }
#version 100
// Copyright 2022 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// ES 2 fragment shader that overlays the bitmap from uTexSampler1 over a video
// frame from uTexSampler0.
precision mediump float;
// Texture containing an input video frame.
uniform sampler2D uTexSampler0;
// Texture containing the overlap bitmap.
uniform sampler2D uTexSampler1;
// Horizontal scaling factor for the overlap bitmap.
uniform float uScaleX;
// Vertical scaling factory for the overlap bitmap.
uniform float uScaleY;
varying vec2 vTexSamplingCoord;
void main() {
vec4 videoColor = texture2D(uTexSampler0, vTexSamplingCoord);
vec4 overlayColor = texture2D(uTexSampler1,
vec2(vTexSamplingCoord.x * uScaleX,
vTexSamplingCoord.y * uScaleY));
// Blend the video decoder output and the overlay bitmap.
gl_FragColor = videoColor * (1.0 - overlayColor.a)
+ overlayColor * overlayColor.a;
}
#version 100
// Copyright 2022 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// ES 2 fragment shader that samples from a (non-external) texture with uTexSampler,
// copying from this texture to the current output while applying a vignette effect
// by linearly darkening the pixels between uInnerRadius and uOuterRadius.
precision mediump float;
uniform sampler2D uTexSampler;
uniform vec2 uCenter;
uniform float uInnerRadius;
uniform float uOuterRadius;
varying vec2 vTexSamplingCoord;
void main() {
vec3 src = texture2D(uTexSampler, vTexSamplingCoord).xyz;
float dist = distance(vTexSamplingCoord, uCenter);
float scale = clamp(1.0 - (dist - uInnerRadius) / (uOuterRadius - uInnerRadius), 0.0, 1.0);
gl_FragColor = vec4(src.r * scale, src.g * scale, src.b * scale, 1.0);
}
#version 100
// Copyright 2022 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// ES 2 vertex shader that leaves the coordinates unchanged.
attribute vec4 aFramePosition;
varying vec2 vTexSamplingCoord;
void main() {
gl_Position = aFramePosition;
vTexSamplingCoord = vec2(aFramePosition.x * 0.5 + 0.5, aFramePosition.y * 0.5 + 0.5);
}
/*
* 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.checkStateNotNull;
import android.content.Context;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.drawable.BitmapDrawable;
import android.opengl.GLES20;
import android.opengl.GLUtils;
import android.util.Size;
import androidx.media3.common.C;
import androidx.media3.common.util.GlProgram;
import androidx.media3.common.util.GlUtil;
import androidx.media3.transformer.FrameProcessingException;
import androidx.media3.transformer.SingleFrameGlTextureProcessor;
import java.io.IOException;
import java.util.Locale;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
/**
* A {@link SingleFrameGlTextureProcessor} that overlays a bitmap with a logo and timer on each
* frame.
*
* <p>The bitmap is drawn using an Android {@link Canvas}.
*/
// TODO(b/227625365): Delete this class and use a texture processor from the Transformer library,
// once overlaying a bitmap and text is supported in Transformer.
/* package */ final class BitmapOverlayProcessor implements SingleFrameGlTextureProcessor {
static {
GlUtil.glAssertionsEnabled = true;
}
private static final String VERTEX_SHADER_PATH = "vertex_shader_copy_es2.glsl";
private static final String FRAGMENT_SHADER_PATH = "fragment_shader_bitmap_overlay_es2.glsl";
private static final int BITMAP_WIDTH_HEIGHT = 512;
private final Paint paint;
private final Bitmap overlayBitmap;
private final Canvas overlayCanvas;
private float bitmapScaleX;
private float bitmapScaleY;
private int bitmapTexId;
private @MonotonicNonNull Size outputSize;
private @MonotonicNonNull Bitmap logoBitmap;
private @MonotonicNonNull GlProgram glProgram;
public BitmapOverlayProcessor() {
paint = new Paint();
paint.setTextSize(64);
paint.setAntiAlias(true);
paint.setARGB(0xFF, 0xFF, 0xFF, 0xFF);
paint.setColor(Color.GRAY);
overlayBitmap =
Bitmap.createBitmap(BITMAP_WIDTH_HEIGHT, BITMAP_WIDTH_HEIGHT, Bitmap.Config.ARGB_8888);
overlayCanvas = new Canvas(overlayBitmap);
}
@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 {
logoBitmap =
((BitmapDrawable)
context.getPackageManager().getApplicationIcon(context.getPackageName()))
.getBitmap();
} catch (PackageManager.NameNotFoundException e) {
throw new IllegalStateException(e);
}
bitmapTexId = GlUtil.createTexture(BITMAP_WIDTH_HEIGHT, BITMAP_WIDTH_HEIGHT);
GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, /* level= */ 0, overlayBitmap, /* border= */ 0);
glProgram = new GlProgram(context, VERTEX_SHADER_PATH, FRAGMENT_SHADER_PATH);
// Draw the frame on the entire normalized device coordinate space, from -1 to 1, for x and y.
glProgram.setBufferAttribute(
"aFramePosition",
GlUtil.getNormalizedCoordinateBounds(),
GlUtil.HOMOGENEOUS_COORDINATE_VECTOR_SIZE);
glProgram.setSamplerTexIdUniform("uTexSampler0", inputTexId, /* texUnitIndex= */ 0);
glProgram.setSamplerTexIdUniform("uTexSampler1", bitmapTexId, /* texUnitIndex= */ 1);
glProgram.setFloatUniform("uScaleX", bitmapScaleX);
glProgram.setFloatUniform("uScaleY", bitmapScaleY);
}
@Override
public Size getOutputSize() {
return checkStateNotNull(outputSize);
}
@Override
public void drawFrame(long presentationTimeUs) throws FrameProcessingException {
try {
checkStateNotNull(glProgram).use();
// Draw to the canvas and store it in a texture.
String text =
String.format(Locale.US, "%.02f", presentationTimeUs / (float) C.MICROS_PER_SECOND);
overlayBitmap.eraseColor(Color.TRANSPARENT);
overlayCanvas.drawBitmap(checkStateNotNull(logoBitmap), /* left= */ 3, /* top= */ 378, paint);
overlayCanvas.drawText(text, /* x= */ 160, /* y= */ 466, paint);
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, bitmapTexId);
GLUtils.texSubImage2D(
GLES20.GL_TEXTURE_2D,
/* level= */ 0,
/* xoffset= */ 0,
/* yoffset= */ 0,
flipBitmapVertically(overlayBitmap));
GlUtil.checkGlError();
glProgram.bindAttributesAndUniforms();
// The four-vertex triangle strip forms a quad.
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, /* first= */ 0, /* count= */ 4);
GlUtil.checkGlError();
} catch (GlUtil.GlException e) {
throw new FrameProcessingException(e);
}
}
@Override
public void release() {
if (glProgram != null) {
glProgram.delete();
}
}
private static Bitmap flipBitmapVertically(Bitmap bitmap) {
Matrix flip = new Matrix();
flip.postScale(1f, -1f);
return Bitmap.createBitmap(
bitmap,
/* x= */ 0,
/* y= */ 0,
bitmap.getWidth(),
bitmap.getHeight(),
flip,
/* filter= */ true);
}
}
/*
* 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 android.graphics.Matrix;
import androidx.media3.common.C;
import androidx.media3.common.util.Util;
import androidx.media3.transformer.GlMatrixTransformation;
import androidx.media3.transformer.MatrixTransformation;
/**
* Factory for {@link GlMatrixTransformation GlMatrixTransformations} and {@link
* MatrixTransformation MatrixTransformations} that create video effects by applying transformation
* matrices to the individual video frames.
*/
/* package */ final class MatrixTransformationFactory {
/**
* Returns a {@link MatrixTransformation} that rescales the frames over the first {@value
* #ZOOM_DURATION_SECONDS} seconds, such that the rectangle filled with the input frame increases
* linearly in size from a single point to filling the full output frame.
*/
public static MatrixTransformation createZoomInTransition() {
return MatrixTransformationFactory::calculateZoomInTransitionMatrix;
}
/**
* Returns a {@link MatrixTransformation} that crops frames to a rectangle that moves on an
* ellipse.
*/
public static MatrixTransformation createDizzyCropEffect() {
return MatrixTransformationFactory::calculateDizzyCropMatrix;
}
/**
* Returns a {@link GlMatrixTransformation} that rotates a frame in 3D around the y-axis and
* applies perspective projection to 2D.
*/
public static GlMatrixTransformation createSpin3dEffect() {
return MatrixTransformationFactory::calculate3dSpinMatrix;
}
private static final float ZOOM_DURATION_SECONDS = 2f;
private static final float DIZZY_CROP_ROTATION_PERIOD_US = 1_500_000f;
private static Matrix calculateZoomInTransitionMatrix(long presentationTimeUs) {
Matrix transformationMatrix = new Matrix();
float scale = Math.min(1, presentationTimeUs / (C.MICROS_PER_SECOND * ZOOM_DURATION_SECONDS));
transformationMatrix.postScale(/* sx= */ scale, /* sy= */ scale);
return transformationMatrix;
}
private static android.graphics.Matrix calculateDizzyCropMatrix(long presentationTimeUs) {
double theta = presentationTimeUs * 2 * Math.PI / DIZZY_CROP_ROTATION_PERIOD_US;
float centerX = 0.5f * (float) Math.cos(theta);
float centerY = 0.5f * (float) Math.sin(theta);
android.graphics.Matrix transformationMatrix = new android.graphics.Matrix();
transformationMatrix.postTranslate(/* dx= */ centerX, /* dy= */ centerY);
transformationMatrix.postScale(/* sx= */ 2f, /* sy= */ 2f);
return transformationMatrix;
}
private static float[] calculate3dSpinMatrix(long presentationTimeUs) {
float[] transformationMatrix = new float[16];
android.opengl.Matrix.frustumM(
transformationMatrix,
/* offset= */ 0,
/* left= */ -1f,
/* right= */ 1f,
/* bottom= */ -1f,
/* top= */ 1f,
/* near= */ 3f,
/* far= */ 5f);
android.opengl.Matrix.translateM(
transformationMatrix, /* mOffset= */ 0, /* x= */ 0f, /* y= */ 0f, /* z= */ -4f);
float theta = Util.usToMs(presentationTimeUs) / 10f;
android.opengl.Matrix.rotateM(
transformationMatrix, /* mOffset= */ 0, theta, /* x= */ 0f, /* y= */ 1f, /* z= */ 0f);
return transformationMatrix;
}
}
/*
* 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.checkArgument;
import static androidx.media3.common.util.Assertions.checkStateNotNull;
import android.content.Context;
import android.opengl.GLES20;
import android.util.Size;
import androidx.media3.common.util.GlProgram;
import androidx.media3.common.util.GlUtil;
import androidx.media3.transformer.FrameProcessingException;
import androidx.media3.transformer.SingleFrameGlTextureProcessor;
import java.io.IOException;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
/**
* A {@link SingleFrameGlTextureProcessor} that periodically dims the frames such that pixels are
* darker the further they are away from the frame center.
*/
/* package */ final class PeriodicVignetteProcessor implements SingleFrameGlTextureProcessor {
static {
GlUtil.glAssertionsEnabled = true;
}
private static final String VERTEX_SHADER_PATH = "vertex_shader_copy_es2.glsl";
private static final String FRAGMENT_SHADER_PATH = "fragment_shader_vignette_es2.glsl";
private static final float DIMMING_PERIOD_US = 5_600_000f;
private float centerX;
private float centerY;
private float minInnerRadius;
private float deltaInnerRadius;
private float outerRadius;
private @MonotonicNonNull Size outputSize;
private @MonotonicNonNull GlProgram glProgram;
/**
* Creates a new instance.
*
* <p>The inner radius of the vignette effect oscillates smoothly between {@code minInnerRadius}
* and {@code maxInnerRadius}.
*
* <p>The pixels between the inner radius and the {@code outerRadius} are darkened linearly based
* on their distance from {@code innerRadius}. All pixels outside {@code outerRadius} are black.
*
* <p>The parameters are given in normalized texture coordinates from 0 to 1.
*
* @param centerX The x-coordinate of the center of the effect.
* @param centerY The y-coordinate of the center of the effect.
* @param minInnerRadius The lower bound of the radius that is unaffected by the effect.
* @param maxInnerRadius The upper bound of the radius that is unaffected by the effect.
* @param outerRadius The radius after which all pixels are black.
*/
public PeriodicVignetteProcessor(
float centerX, float centerY, float minInnerRadius, float maxInnerRadius, float outerRadius) {
checkArgument(minInnerRadius <= maxInnerRadius);
checkArgument(maxInnerRadius <= outerRadius);
this.centerX = centerX;
this.centerY = centerY;
this.minInnerRadius = minInnerRadius;
this.deltaInnerRadius = maxInnerRadius - minInnerRadius;
this.outerRadius = outerRadius;
}
@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.setSamplerTexIdUniform("uTexSampler", inputTexId, /* texUnitIndex= */ 0);
glProgram.setFloatsUniform("uCenter", new float[] {centerX, centerY});
glProgram.setFloatsUniform("uOuterRadius", new float[] {outerRadius});
// Draw the frame on the entire normalized device coordinate space, from -1 to 1, for x and y.
glProgram.setBufferAttribute(
"aFramePosition",
GlUtil.getNormalizedCoordinateBounds(),
GlUtil.HOMOGENEOUS_COORDINATE_VECTOR_SIZE);
}
@Override
public Size getOutputSize() {
return checkStateNotNull(outputSize);
}
@Override
public void drawFrame(long presentationTimeUs) throws FrameProcessingException {
try {
checkStateNotNull(glProgram).use();
double theta = presentationTimeUs * 2 * Math.PI / DIMMING_PERIOD_US;
float innerRadius =
minInnerRadius + deltaInnerRadius * (0.5f - 0.5f * (float) Math.cos(theta));
glProgram.setFloatsUniform("uInnerRadius", new float[] {innerRadius});
glProgram.bindAttributesAndUniforms();
// The four-vertex triangle strip forms a quad.
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, /* first= */ 0, /* count= */ 4);
} catch (GlUtil.GlException e) {
throw new FrameProcessingException(e);
}
}
@Override
public void release() {
if (glProgram != null) {
glProgram.delete();
}
}
}
...@@ -21,7 +21,6 @@ import static androidx.media3.common.util.Assertions.checkNotNull; ...@@ -21,7 +21,6 @@ import static androidx.media3.common.util.Assertions.checkNotNull;
import android.app.Activity; import android.app.Activity;
import android.content.Intent; import android.content.Intent;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.graphics.Matrix;
import android.net.Uri; import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.os.Handler; import android.os.Handler;
...@@ -32,6 +31,7 @@ import android.view.ViewGroup; ...@@ -32,6 +31,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;
...@@ -39,17 +39,24 @@ import androidx.media3.common.util.Log; ...@@ -39,17 +39,24 @@ import androidx.media3.common.util.Log;
import androidx.media3.common.util.Util; import androidx.media3.common.util.Util;
import androidx.media3.exoplayer.ExoPlayer; 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.EncoderSelector;
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.Transformer; import androidx.media3.transformer.Transformer;
import androidx.media3.ui.AspectRatioFrameLayout; import androidx.media3.ui.AspectRatioFrameLayout;
import androidx.media3.ui.PlayerView; import androidx.media3.ui.PlayerView;
import com.google.android.material.progressindicator.LinearProgressIndicator; import com.google.android.material.progressindicator.LinearProgressIndicator;
import com.google.common.base.Stopwatch; import com.google.common.base.Stopwatch;
import com.google.common.base.Ticker; import com.google.common.base.Ticker;
import com.google.common.collect.ImmutableList;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.lang.reflect.Constructor;
import java.util.concurrent.CountDownLatch; import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull; import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
...@@ -145,9 +152,10 @@ public final class TransformerActivity extends AppCompatActivity { ...@@ -145,9 +152,10 @@ public final class TransformerActivity extends AppCompatActivity {
externalCacheFile = createExternalCacheFile("transformer-output.mp4"); externalCacheFile = createExternalCacheFile("transformer-output.mp4");
String filePath = externalCacheFile.getAbsolutePath(); String filePath = externalCacheFile.getAbsolutePath();
@Nullable Bundle bundle = intent.getExtras(); @Nullable Bundle bundle = intent.getExtras();
MediaItem mediaItem = createMediaItem(bundle, uri);
Transformer transformer = createTransformer(bundle, filePath); Transformer transformer = createTransformer(bundle, filePath);
transformationStopwatch.start(); transformationStopwatch.start();
transformer.startTransformation(MediaItem.fromUri(uri), filePath); transformer.startTransformation(mediaItem, filePath);
this.transformer = transformer; this.transformer = transformer;
} catch (IOException e) { } catch (IOException e) {
throw new IllegalStateException(e); throw new IllegalStateException(e);
...@@ -174,6 +182,24 @@ public final class TransformerActivity extends AppCompatActivity { ...@@ -174,6 +182,24 @@ public final class TransformerActivity extends AppCompatActivity {
}); });
} }
private MediaItem createMediaItem(@Nullable Bundle bundle, Uri uri) {
MediaItem.Builder mediaItemBuilder = new MediaItem.Builder().setUri(uri);
if (bundle != null) {
long trimStartMs =
bundle.getLong(ConfigurationActivity.TRIM_START_MS, /* defaultValue= */ C.TIME_UNSET);
long trimEndMs =
bundle.getLong(ConfigurationActivity.TRIM_END_MS, /* defaultValue= */ C.TIME_UNSET);
if (trimStartMs != C.TIME_UNSET && trimEndMs != C.TIME_UNSET) {
mediaItemBuilder.setClippingConfiguration(
new MediaItem.ClippingConfiguration.Builder()
.setStartPositionMs(trimStartMs)
.setEndPositionMs(trimEndMs)
.build());
}
}
return mediaItemBuilder.build();
}
// Create a cache file, resetting it if it already exists. // Create a cache file, resetting it if it already exists.
private File createExternalCacheFile(String fileName) throws IOException { private File createExternalCacheFile(String fileName) throws IOException {
File file = new File(getExternalCacheDir(), fileName); File file = new File(getExternalCacheDir(), fileName);
...@@ -214,22 +240,88 @@ public final class TransformerActivity extends AppCompatActivity { ...@@ -214,22 +240,88 @@ public final class TransformerActivity extends AppCompatActivity {
if (resolutionHeight != C.LENGTH_UNSET) { if (resolutionHeight != C.LENGTH_UNSET) {
requestBuilder.setResolution(resolutionHeight); requestBuilder.setResolution(resolutionHeight);
} }
Matrix transformationMatrix = getTransformationMatrix(bundle);
if (!transformationMatrix.isIdentity()) { float scaleX = bundle.getFloat(ConfigurationActivity.SCALE_X, /* defaultValue= */ 1);
requestBuilder.setTransformationMatrix(transformationMatrix); float scaleY = bundle.getFloat(ConfigurationActivity.SCALE_Y, /* defaultValue= */ 1);
} requestBuilder.setScale(scaleX, scaleY);
float rotateDegrees =
bundle.getFloat(ConfigurationActivity.ROTATE_DEGREES, /* defaultValue= */ 0);
requestBuilder.setRotationDegrees(rotateDegrees);
requestBuilder.setEnableRequestSdrToneMapping(
bundle.getBoolean(ConfigurationActivity.ENABLE_REQUEST_SDR_TONE_MAPPING));
requestBuilder.experimental_setEnableHdrEditing( requestBuilder.experimental_setEnableHdrEditing(
bundle.getBoolean(ConfigurationActivity.ENABLE_HDR_EDITING)); bundle.getBoolean(ConfigurationActivity.ENABLE_HDR_EDITING));
transformerBuilder transformerBuilder
.setTransformationRequest(requestBuilder.build()) .setTransformationRequest(requestBuilder.build())
.setRemoveAudio(bundle.getBoolean(ConfigurationActivity.SHOULD_REMOVE_AUDIO)) .setRemoveAudio(bundle.getBoolean(ConfigurationActivity.SHOULD_REMOVE_AUDIO))
.setRemoveVideo(bundle.getBoolean(ConfigurationActivity.SHOULD_REMOVE_VIDEO)); .setRemoveVideo(bundle.getBoolean(ConfigurationActivity.SHOULD_REMOVE_VIDEO))
.setEncoderFactory(
new DefaultEncoderFactory(
EncoderSelector.DEFAULT,
/* enableFallback= */ bundle.getBoolean(ConfigurationActivity.ENABLE_FALLBACK)));
ImmutableList.Builder<GlEffect> effects = new ImmutableList.Builder<>();
@Nullable
boolean[] selectedEffects =
bundle.getBooleanArray(ConfigurationActivity.DEMO_EFFECTS_SELECTIONS);
if (selectedEffects != null) {
if (selectedEffects[0]) {
effects.add(MatrixTransformationFactory.createDizzyCropEffect());
}
if (selectedEffects[1]) {
try {
Class<?> clazz = Class.forName("androidx.media3.demo.transformer.MediaPipeProcessor");
Constructor<?> constructor =
clazz.getConstructor(String.class, String.class, String.class);
effects.add(
() -> {
try {
return (SingleFrameGlTextureProcessor)
constructor.newInstance(
/* 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(
() ->
new PeriodicVignetteProcessor(
bundle.getFloat(ConfigurationActivity.PERIODIC_VIGNETTE_CENTER_X),
bundle.getFloat(ConfigurationActivity.PERIODIC_VIGNETTE_CENTER_Y),
/* minInnerRadius= */ bundle.getFloat(
ConfigurationActivity.PERIODIC_VIGNETTE_INNER_RADIUS),
/* maxInnerRadius= */ bundle.getFloat(
ConfigurationActivity.PERIODIC_VIGNETTE_OUTER_RADIUS),
bundle.getFloat(ConfigurationActivity.PERIODIC_VIGNETTE_OUTER_RADIUS)));
}
if (selectedEffects[3]) {
effects.add(MatrixTransformationFactory.createSpin3dEffect());
}
if (selectedEffects[4]) {
effects.add(BitmapOverlayProcessor::new);
}
if (selectedEffects[5]) {
effects.add(MatrixTransformationFactory.createZoomInTransition());
}
transformerBuilder.setVideoFrameEffects(effects.build());
}
} }
return transformerBuilder return transformerBuilder
.addListener( .addListener(
new Transformer.Listener() { new Transformer.Listener() {
@Override @Override
public void onTransformationCompleted(MediaItem mediaItem) { public void onTransformationCompleted(
MediaItem mediaItem, TransformationResult transformationResult) {
TransformerActivity.this.onTransformationCompleted(filePath); TransformerActivity.this.onTransformationCompleted(filePath);
} }
...@@ -243,26 +335,6 @@ public final class TransformerActivity extends AppCompatActivity { ...@@ -243,26 +335,6 @@ public final class TransformerActivity extends AppCompatActivity {
.build(); .build();
} }
private static Matrix getTransformationMatrix(Bundle bundle) {
Matrix transformationMatrix = new Matrix();
float translateX = bundle.getFloat(ConfigurationActivity.TRANSLATE_X, /* defaultValue= */ 0);
float translateY = bundle.getFloat(ConfigurationActivity.TRANSLATE_Y, /* defaultValue= */ 0);
// TODO(b/213198690): Get resolution for aspect ratio and scale all translations' translateX
// by this aspect ratio.
transformationMatrix.postTranslate(translateX, translateY);
float scaleX = bundle.getFloat(ConfigurationActivity.SCALE_X, /* defaultValue= */ 1);
float scaleY = bundle.getFloat(ConfigurationActivity.SCALE_Y, /* defaultValue= */ 1);
transformationMatrix.postScale(scaleX, scaleY);
float rotateDegrees =
bundle.getFloat(ConfigurationActivity.ROTATE_DEGREES, /* defaultValue= */ 0);
transformationMatrix.postRotate(rotateDegrees);
return transformationMatrix;
}
@RequiresNonNull({ @RequiresNonNull({
"informationTextView", "informationTextView",
"progressViewGroup", "progressViewGroup",
...@@ -335,6 +407,10 @@ public final class TransformerActivity extends AppCompatActivity { ...@@ -335,6 +407,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
......
...@@ -34,18 +34,18 @@ ...@@ -34,18 +34,18 @@
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" /> app:layout_constraintTop_toTopOf="parent" />
<Button <Button
android:id="@+id/choose_file_button" android:id="@+id/select_file_button"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="32dp" android:layout_marginTop="32dp"
android:layout_marginStart="32dp" android:layout_marginStart="32dp"
android:layout_marginEnd="32dp" android:layout_marginEnd="32dp"
android:text="@string/choose_file_title" android:text="@string/select_file_title"
app:layout_constraintTop_toBottomOf="@+id/configuration_text_view" app:layout_constraintTop_toBottomOf="@+id/configuration_text_view"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" /> app:layout_constraintStart_toStartOf="parent" />
<TextView <TextView
android:id="@+id/chosen_file_text_view" android:id="@+id/selected_file_text_view"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="12dp" android:layout_marginTop="12dp"
...@@ -57,14 +57,14 @@ ...@@ -57,14 +57,14 @@
android:gravity="center" android:gravity="center"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/choose_file_button" /> app:layout_constraintTop_toBottomOf="@+id/select_file_button" />
<androidx.core.widget.NestedScrollView <androidx.core.widget.NestedScrollView
android:layout_width="fill_parent" android:layout_width="fill_parent"
android:layout_height="0dp" android:layout_height="0dp"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/chosen_file_text_view" app:layout_constraintTop_toBottomOf="@+id/selected_file_text_view"
app:layout_constraintBottom_toTopOf="@+id/transform_button"> app:layout_constraintBottom_toTopOf="@+id/select_demo_effects_button">
<TableLayout <TableLayout
android:layout_width="fill_parent" android:layout_width="fill_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
...@@ -141,17 +141,6 @@ ...@@ -141,17 +141,6 @@
android:layout_weight="1" android:layout_weight="1"
android:gravity="center_vertical" > android:gravity="center_vertical" >
<TextView <TextView
android:id="@+id/translate"
android:text="@string/translate"/>
<Spinner
android:id="@+id/translate_spinner"
android:layout_gravity="right|center_vertical"
android:gravity="right" />
</TableRow>
<TableRow
android:layout_weight="1"
android:gravity="center_vertical" >
<TextView
android:id="@+id/scale" android:id="@+id/scale"
android:text="@string/scale"/> android:text="@string/scale"/>
<Spinner <Spinner
...@@ -174,6 +163,36 @@ ...@@ -174,6 +163,36 @@
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" />
<CheckBox
android:id="@+id/enable_fallback_checkbox"
android:layout_gravity="right"
android:checked="true"/>
</TableRow>
<TableRow
android:layout_weight="1"
android:gravity="center_vertical" >
<TextView
android:id="@+id/request_sdr_tone_mapping"
android:text="@string/request_sdr_tone_mapping" />
<CheckBox
android:id="@+id/request_sdr_tone_mapping_checkbox"
android:layout_gravity="right" />
</TableRow>
<TableRow
android:layout_weight="1"
android:gravity="center_vertical" >
<TextView
android:id="@+id/hdr_editing" android:id="@+id/hdr_editing"
android:text="@string/hdr_editing" /> android:text="@string/hdr_editing" />
<CheckBox <CheckBox
...@@ -183,6 +202,17 @@ ...@@ -183,6 +202,17 @@
</TableLayout> </TableLayout>
</androidx.core.widget.NestedScrollView> </androidx.core.widget.NestedScrollView>
<Button <Button
android:id="@+id/select_demo_effects_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="32dp"
android:layout_marginStart="32dp"
android:layout_marginEnd="32dp"
android:text="@string/select_demo_effects"
app:layout_constraintBottom_toTopOf="@+id/transform_button"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<Button
android:id="@+id/transform_button" android:id="@+id/transform_button"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
......
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright 2022 The Android Open Source Project
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
tools:context=".ConfigurationActivity">
<TableLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:stretchColumns="1"
android:layout_marginTop="32dp"
android:measureWithLargestChild="true"
android:paddingLeft="24dp"
android:paddingRight="12dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent">
<TableRow
android:layout_weight="1"
android:gravity="center_vertical" >
<TextView
android:text="@string/center_x" />
<com.google.android.material.slider.Slider
android:id="@+id/periodic_vignette_center_x_slider"
android:valueFrom="0.0"
android:value="0.5"
android:valueTo="1.0"
android:layout_gravity="right"/>
</TableRow>
<TableRow
android:layout_weight="1"
android:gravity="center_vertical" >
<TextView
android:text="@string/center_y" />
<com.google.android.material.slider.Slider
android:id="@+id/periodic_vignette_center_y_slider"
android:valueFrom="0.0"
android:value="0.5"
android:valueTo="1.0"
android:layout_gravity="right"/>
</TableRow>
<TableRow
android:layout_weight="1"
android:gravity="center_vertical" >
<TextView
android:text="@string/radius_range" />
<com.google.android.material.slider.RangeSlider
android:id="@+id/periodic_vignette_radius_range_slider"
android:valueFrom="0.0"
android:valueTo="1.414"
android:layout_gravity="right"/>
</TableRow>
</TableLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright 2022 The Android Open Source Project
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
tools:context=".ConfigurationActivity">
<TableLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:stretchColumns="1"
android:layout_marginTop="32dp"
android:measureWithLargestChild="true"
android:paddingLeft="24dp"
android:paddingRight="12dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent">
<TableRow
android:layout_weight="1"
android:gravity="center_vertical" >
<TextView
android:text="@string/trim_range" />
<com.google.android.material.slider.RangeSlider
android:id="@+id/trim_bounds_range_slider"
android:valueFrom="0.0"
android:valueTo="60.0"
android:layout_gravity="right"/>
</TableRow>
</TableLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
...@@ -17,22 +17,31 @@ ...@@ -17,22 +17,31 @@
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" translatable="false">Transformer Demo</string> <string name="app_name" translatable="false">Transformer Demo</string>
<string name="configuration" translatable="false">Configuration</string> <string name="configuration" translatable="false">Configuration</string>
<string name="choose_file_title" translatable="false">Choose file</string> <string name="select_file_title" translatable="false">Choose file</string>
<string name="remove_audio" translatable="false">Remove audio</string> <string name="remove_audio" translatable="false">Remove audio</string>
<string name="remove_video" translatable="false">Remove video</string> <string name="remove_video" translatable="false">Remove video</string>
<string name="flatten_for_slow_motion" translatable="false">Flatten for slow motion</string> <string name="flatten_for_slow_motion" translatable="false">Flatten for slow motion</string>
<string name="audio_mime" translatable="false">Output audio MIME type</string> <string name="audio_mime" translatable="false">Output audio MIME type</string>
<string name="video_mime" translatable="false">Output video MIME type</string> <string name="video_mime" translatable="false">Output video MIME type</string>
<string name="resolution_height" translatable="false">Output video resolution</string> <string name="resolution_height" translatable="false">Output video resolution</string>
<string name="translate" translatable="false">Translate video</string>
<string name="scale" translatable="false">Scale video</string> <string name="scale" translatable="false">Scale video</string>
<string name="rotate" translatable="false">Rotate video (degrees)</string> <string name="rotate" translatable="false">Rotate video (degrees)</string>
<string name="transform" translatable="false">Transform</string> <string name="enable_fallback" translatable="false">Enable fallback</string>
<string name="trim" translatable="false">Trim</string>
<string name="request_sdr_tone_mapping" translatable="false">Request SDR tone-mapping (API 31+)</string>
<string name="hdr_editing" translatable="false">[Experimental] HDR editing</string> <string name="hdr_editing" translatable="false">[Experimental] HDR editing</string>
<string name="select_demo_effects" translatable="false">Add demo effects</string>
<string name="periodic_vignette_options" translatable="false">Periodic vignette options</string>
<string name="no_media_pipe_error" translatable="false">Failed to load MediaPipe processor. Check the README for instructions.</string>
<string name="transform" translatable="false">Transform</string>
<string name="debug_preview" translatable="false">Debug preview:</string> <string name="debug_preview" translatable="false">Debug preview:</string>
<string name="debug_preview_not_available" translatable="false">No debug preview available.</string> <string name="debug_preview_not_available" translatable="false">No debug preview available.</string>
<string name="transformation_started" translatable="false">Transformation started</string> <string name="transformation_started" translatable="false">Transformation started</string>
<string name="transformation_timer" translatable="false">Transformation started %d seconds ago.</string> <string name="transformation_timer" translatable="false">Transformation started %d seconds ago.</string>
<string name="transformation_completed" translatable="false">Transformation completed in %d seconds.</string> <string name="transformation_completed" translatable="false">Transformation completed in %d seconds.</string>
<string name="transformation_error" translatable="false">Transformation error</string> <string name="transformation_error" translatable="false">Transformation error</string>
<string name="center_x">Center X</string>
<string name="center_y">Center Y</string>
<string name="radius_range">Radius range</string>
<string name="trim_range">Bounds in seconds</string>
</resources> </resources>
<?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 implements SingleFrameGlTextureProcessor {
private static final LibraryLoader LOADER =
new LibraryLoader("mediapipe_jni") {
@Override
protected void loadLibrary(String name) {
System.loadLibrary(name);
}
};
static {
// Not all build configurations require OpenCV to be loaded separately, so attempt to load the
// library but ignore the error if it's not present.
try {
System.loadLibrary("opencv_java3");
} catch (UnsatisfiedLinkError e) {
// Do nothing.
}
}
private static final String COPY_VERTEX_SHADER_NAME = "vertex_shader_copy_es2.glsl";
private static final String COPY_FRAGMENT_SHADER_NAME = "shaders/fragment_shader_copy_es2.glsl";
private final String graphName;
private final String inputStreamName;
private final String outputStreamName;
private final ConditionVariable frameProcessorConditionVariable;
private @MonotonicNonNull FrameProcessor frameProcessor;
private int inputWidth;
private int inputHeight;
private int inputTexId;
private @MonotonicNonNull GlProgram glProgram;
private @MonotonicNonNull TextureFrame outputFrame;
private @MonotonicNonNull RuntimeException frameProcessorPendingError;
/**
* Creates a new texture processor that wraps a MediaPipe graph.
*
* @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.
*/
public MediaPipeProcessor(String graphName, String inputStreamName, String outputStreamName) {
checkState(LOADER.isAvailable());
this.graphName = graphName;
this.inputStreamName = inputStreamName;
this.outputStreamName = outputStreamName;
frameProcessorConditionVariable = new ConditionVariable();
}
@Override
public void initialize(Context context, int inputTexId, int inputWidth, int inputHeight)
throws IOException {
this.inputTexId = inputTexId;
this.inputWidth = inputWidth;
this.inputHeight = inputHeight;
glProgram = new GlProgram(context, COPY_VERTEX_SHADER_NAME, COPY_FRAGMENT_SHADER_NAME);
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();
});
}
@Override
public Size getOutputSize() {
return new Size(inputWidth, inputHeight);
}
@Override
public void drawFrame(long presentationTimeUs) throws FrameProcessingException {
frameProcessorConditionVariable.close();
// Pass the input frame to MediaPipe.
AppTextureFrame appTextureFrame = new AppTextureFrame(inputTexId, inputWidth, inputHeight);
appTextureFrame.setTimestamp(presentationTimeUs);
checkStateNotNull(frameProcessor).onNewFrame(appTextureFrame);
// Wait for output to be passed to the consumer.
try {
frameProcessorConditionVariable.block();
} catch (InterruptedException e) {
// Propagate the interrupted flag so the next blocking operation will throw.
// TODO(b/230469581): The next processor that runs will not have valid input due to returning
// early here. This could be fixed by checking for interruption in the outer loop that runs
// through the texture processors.
Thread.currentThread().interrupt();
return;
}
if (frameProcessorPendingError != null) {
throw new FrameProcessingException(frameProcessorPendingError);
}
// Copy from MediaPipe's output texture to the current output.
try {
checkStateNotNull(glProgram).use();
glProgram.setSamplerTexIdUniform(
"uTexSampler", checkStateNotNull(outputFrame).getTextureName(), /* texUnitIndex= */ 0);
glProgram.setBufferAttribute(
"aFramePosition",
GlUtil.getNormalizedCoordinateBounds(),
GlUtil.HOMOGENEOUS_COORDINATE_VECTOR_SIZE);
glProgram.bindAttributesAndUniforms();
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, /* first= */ 0, /* count= */ 4);
GlUtil.checkGlError();
} catch (GlUtil.GlException e) {
throw new FrameProcessingException(e);
} finally {
checkStateNotNull(outputFrame).release();
}
}
@Override
public void release() {
checkStateNotNull(frameProcessor).close();
}
}
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())
} }
} }
......
...@@ -34,19 +34,15 @@ import androidx.media3.common.DeviceInfo; ...@@ -34,19 +34,15 @@ import androidx.media3.common.DeviceInfo;
import androidx.media3.common.MediaItem; import androidx.media3.common.MediaItem;
import androidx.media3.common.MediaLibraryInfo; import androidx.media3.common.MediaLibraryInfo;
import androidx.media3.common.MediaMetadata; import androidx.media3.common.MediaMetadata;
import androidx.media3.common.MimeTypes;
import androidx.media3.common.PlaybackException; import androidx.media3.common.PlaybackException;
import androidx.media3.common.PlaybackParameters; import androidx.media3.common.PlaybackParameters;
import androidx.media3.common.Player; import androidx.media3.common.Player;
import androidx.media3.common.Timeline; import androidx.media3.common.Timeline;
import androidx.media3.common.TrackGroup; import androidx.media3.common.TrackGroup;
import androidx.media3.common.TrackGroupArray;
import androidx.media3.common.TrackSelection;
import androidx.media3.common.TrackSelectionArray;
import androidx.media3.common.TrackSelectionParameters; import androidx.media3.common.TrackSelectionParameters;
import androidx.media3.common.TracksInfo; 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;
...@@ -68,7 +64,6 @@ import com.google.android.gms.common.api.PendingResult; ...@@ -68,7 +64,6 @@ import com.google.android.gms.common.api.PendingResult;
import com.google.android.gms.common.api.ResultCallback; import com.google.android.gms.common.api.ResultCallback;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import java.util.List; import java.util.List;
import org.checkerframework.checker.nullness.compatqual.NullableType;
import org.checkerframework.checker.nullness.qual.RequiresNonNull; import org.checkerframework.checker.nullness.qual.RequiresNonNull;
/** /**
...@@ -107,7 +102,8 @@ public final class CastPlayer extends BasePlayer { ...@@ -107,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_TRACK_INFOS) 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;
...@@ -115,13 +111,7 @@ public final class CastPlayer extends BasePlayer { ...@@ -115,13 +111,7 @@ public final class CastPlayer extends BasePlayer {
private static final String TAG = "CastPlayer"; private static final String TAG = "CastPlayer";
private static final int RENDERER_COUNT = 3;
private static final int RENDERER_INDEX_VIDEO = 0;
private static final int RENDERER_INDEX_AUDIO = 1;
private static final int RENDERER_INDEX_TEXT = 2;
private static final long PROGRESS_REPORT_PERIOD_MS = 1000; private static final long PROGRESS_REPORT_PERIOD_MS = 1000;
private static final TrackSelectionArray EMPTY_TRACK_SELECTION_ARRAY =
new TrackSelectionArray(null, null, null);
private static final long[] EMPTY_TRACK_ID_ARRAY = new long[0]; private static final long[] EMPTY_TRACK_ID_ARRAY = new long[0];
private final CastContext castContext; private final CastContext castContext;
...@@ -146,9 +136,7 @@ public final class CastPlayer extends BasePlayer { ...@@ -146,9 +136,7 @@ public final class CastPlayer extends BasePlayer {
private final StateHolder<PlaybackParameters> playbackParameters; private final StateHolder<PlaybackParameters> playbackParameters;
@Nullable private RemoteMediaClient remoteMediaClient; @Nullable private RemoteMediaClient remoteMediaClient;
private CastTimeline currentTimeline; private CastTimeline currentTimeline;
private TrackGroupArray currentTrackGroups; private Tracks currentTracks;
private TrackSelectionArray currentTrackSelection;
private TracksInfo currentTracksInfo;
private Commands availableCommands; private Commands availableCommands;
private @Player.State int playbackState; private @Player.State int playbackState;
private int currentWindowIndex; private int currentWindowIndex;
...@@ -224,9 +212,7 @@ public final class CastPlayer extends BasePlayer { ...@@ -224,9 +212,7 @@ public final class CastPlayer extends BasePlayer {
playbackParameters = new StateHolder<>(PlaybackParameters.DEFAULT); playbackParameters = new StateHolder<>(PlaybackParameters.DEFAULT);
playbackState = STATE_IDLE; playbackState = STATE_IDLE;
currentTimeline = CastTimeline.EMPTY_CAST_TIMELINE; currentTimeline = CastTimeline.EMPTY_CAST_TIMELINE;
currentTrackGroups = TrackGroupArray.EMPTY; currentTracks = Tracks.EMPTY;
currentTrackSelection = EMPTY_TRACK_SELECTION_ARRAY;
currentTracksInfo = TracksInfo.EMPTY;
availableCommands = new Commands.Builder().addAll(PERMANENT_AVAILABLE_COMMANDS).build(); availableCommands = new Commands.Builder().addAll(PERMANENT_AVAILABLE_COMMANDS).build();
pendingSeekWindowIndex = C.INDEX_UNSET; pendingSeekWindowIndex = C.INDEX_UNSET;
pendingSeekPositionMs = C.TIME_UNSET; pendingSeekPositionMs = C.TIME_UNSET;
...@@ -473,6 +459,11 @@ public final class CastPlayer extends BasePlayer { ...@@ -473,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) {
...@@ -558,18 +549,8 @@ public final class CastPlayer extends BasePlayer { ...@@ -558,18 +549,8 @@ public final class CastPlayer extends BasePlayer {
} }
@Override @Override
public TrackGroupArray getCurrentTrackGroups() { public Tracks getCurrentTracks() {
return currentTrackGroups; return currentTracks;
}
@Override
public TrackSelectionArray getCurrentTrackSelections() {
return currentTrackSelection;
}
@Override
public TracksInfo getCurrentTracksInfo() {
return currentTracksInfo;
} }
@Override @Override
...@@ -730,10 +711,10 @@ public final class CastPlayer extends BasePlayer { ...@@ -730,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}. */
...@@ -842,10 +823,7 @@ public final class CastPlayer extends BasePlayer { ...@@ -842,10 +823,7 @@ public final class CastPlayer extends BasePlayer {
} }
if (updateTracksAndSelectionsAndNotifyIfChanged()) { if (updateTracksAndSelectionsAndNotifyIfChanged()) {
listeners.queueEvent( listeners.queueEvent(
Player.EVENT_TRACKS_CHANGED, Player.EVENT_TRACKS_CHANGED, listener -> listener.onTracksChanged(currentTracks));
listener -> listener.onTracksChanged(currentTrackGroups, currentTrackSelection));
listeners.queueEvent(
Player.EVENT_TRACKS_CHANGED, listener -> listener.onTracksInfoChanged(currentTracksInfo));
} }
updateAvailableCommandsAndNotifyIfChanged(); updateAvailableCommandsAndNotifyIfChanged();
listeners.flushEvents(); listeners.flushEvents();
...@@ -1000,55 +978,33 @@ public final class CastPlayer extends BasePlayer { ...@@ -1000,55 +978,33 @@ public final class CastPlayer extends BasePlayer {
return false; return false;
} }
MediaStatus mediaStatus = getMediaStatus(); @Nullable MediaStatus mediaStatus = getMediaStatus();
MediaInfo mediaInfo = mediaStatus != null ? mediaStatus.getMediaInfo() : null; @Nullable MediaInfo mediaInfo = mediaStatus != null ? mediaStatus.getMediaInfo() : null;
@Nullable
List<MediaTrack> castMediaTracks = mediaInfo != null ? mediaInfo.getMediaTracks() : null; List<MediaTrack> castMediaTracks = mediaInfo != null ? mediaInfo.getMediaTracks() : null;
if (castMediaTracks == null || castMediaTracks.isEmpty()) { if (castMediaTracks == null || castMediaTracks.isEmpty()) {
boolean hasChanged = !currentTrackGroups.isEmpty(); boolean hasChanged = !Tracks.EMPTY.equals(currentTracks);
currentTrackGroups = TrackGroupArray.EMPTY; currentTracks = Tracks.EMPTY;
currentTrackSelection = EMPTY_TRACK_SELECTION_ARRAY;
currentTracksInfo = TracksInfo.EMPTY;
return hasChanged; return hasChanged;
} }
long[] activeTrackIds = mediaStatus.getActiveTrackIds(); @Nullable long[] activeTrackIds = mediaStatus.getActiveTrackIds();
if (activeTrackIds == null) { if (activeTrackIds == null) {
activeTrackIds = EMPTY_TRACK_ID_ARRAY; activeTrackIds = EMPTY_TRACK_ID_ARRAY;
} }
TrackGroup[] trackGroups = new TrackGroup[castMediaTracks.size()]; Tracks.Group[] trackGroups = new Tracks.Group[castMediaTracks.size()];
@NullableType TrackSelection[] trackSelections = new TrackSelection[RENDERER_COUNT];
TracksInfo.TrackGroupInfo[] trackGroupInfos =
new TracksInfo.TrackGroupInfo[castMediaTracks.size()];
for (int i = 0; i < castMediaTracks.size(); i++) { for (int i = 0; i < castMediaTracks.size(); i++) {
MediaTrack mediaTrack = castMediaTracks.get(i); MediaTrack mediaTrack = castMediaTracks.get(i);
trackGroups[i] = TrackGroup trackGroup =
new TrackGroup(/* id= */ Integer.toString(i), CastUtils.mediaTrackToFormat(mediaTrack)); new TrackGroup(/* id= */ Integer.toString(i), CastUtils.mediaTrackToFormat(mediaTrack));
@C.FormatSupport int[] trackSupport = new int[] {C.FORMAT_HANDLED};
long id = mediaTrack.getId(); boolean[] trackSelected = new boolean[] {isTrackActive(mediaTrack.getId(), activeTrackIds)};
@C.TrackType int trackType = MimeTypes.getTrackType(mediaTrack.getContentType()); trackGroups[i] =
int rendererIndex = getRendererIndexForTrackType(trackType); new Tracks.Group(trackGroup, /* adaptiveSupported= */ false, trackSupport, trackSelected);
boolean supported = rendererIndex != C.INDEX_UNSET; }
boolean selected = Tracks newTracks = new Tracks(ImmutableList.copyOf(trackGroups));
isTrackActive(id, activeTrackIds) && supported && trackSelections[rendererIndex] == null; if (!newTracks.equals(currentTracks)) {
if (selected) { currentTracks = newTracks;
trackSelections[rendererIndex] = new CastTrackSelection(trackGroups[i]);
}
@C.FormatSupport
int[] trackSupport = new int[] {supported ? C.FORMAT_HANDLED : C.FORMAT_UNSUPPORTED_TYPE};
final boolean[] trackSelected = new boolean[] {selected};
trackGroupInfos[i] =
new TracksInfo.TrackGroupInfo(trackGroups[i], trackSupport, trackType, trackSelected);
}
TrackGroupArray newTrackGroups = new TrackGroupArray(trackGroups);
TrackSelectionArray newTrackSelections = new TrackSelectionArray(trackSelections);
TracksInfo newTracksInfo = new TracksInfo(ImmutableList.copyOf(trackGroupInfos));
if (!newTrackGroups.equals(currentTrackGroups)
|| !newTrackSelections.equals(currentTrackSelection)
|| !newTracksInfo.equals(currentTracksInfo)) {
currentTrackSelection = newTrackSelections;
currentTrackGroups = newTrackGroups;
currentTracksInfo = newTracksInfo;
return true; return true;
} }
return false; return false;
...@@ -1306,14 +1262,6 @@ public final class CastPlayer extends BasePlayer { ...@@ -1306,14 +1262,6 @@ public final class CastPlayer extends BasePlayer {
return false; return false;
} }
private static int getRendererIndexForTrackType(@C.TrackType int trackType) {
return trackType == C.TRACK_TYPE_VIDEO
? RENDERER_INDEX_VIDEO
: trackType == C.TRACK_TYPE_AUDIO
? RENDERER_INDEX_AUDIO
: trackType == C.TRACK_TYPE_TEXT ? RENDERER_INDEX_TEXT : C.INDEX_UNSET;
}
private static int getCastRepeatMode(@RepeatMode int repeatMode) { private static int getCastRepeatMode(@RepeatMode int repeatMode) {
switch (repeatMode) { switch (repeatMode) {
case REPEAT_MODE_ONE: case REPEAT_MODE_ONE:
......
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.media3.cast;
import androidx.annotation.Nullable;
import androidx.media3.common.C;
import androidx.media3.common.Format;
import androidx.media3.common.TrackGroup;
import androidx.media3.common.TrackSelection;
import androidx.media3.common.util.Assertions;
/**
* {@link TrackSelection} that only selects the first track of the provided {@link TrackGroup}.
*
* <p>This relies on {@link CastPlayer} track groups only having one track.
*/
/* package */ class CastTrackSelection implements TrackSelection {
private final TrackGroup trackGroup;
/** @param trackGroup The {@link TrackGroup} from which the first track will only be selected. */
public CastTrackSelection(TrackGroup trackGroup) {
this.trackGroup = trackGroup;
}
@Override
public int getType() {
return TYPE_UNSET;
}
@Override
public TrackGroup getTrackGroup() {
return trackGroup;
}
@Override
public int length() {
return 1;
}
@Override
public Format getFormat(int index) {
Assertions.checkArgument(index == 0);
return trackGroup.getFormat(0);
}
@Override
public int getIndexInTrackGroup(int index) {
return index == 0 ? 0 : C.INDEX_UNSET;
}
@Override
@SuppressWarnings("ReferenceEquality")
public int indexOf(Format format) {
return format == trackGroup.getFormat(0) ? 0 : C.INDEX_UNSET;
}
@Override
public int indexOf(int indexInTrackGroup) {
return indexInTrackGroup == 0 ? 0 : C.INDEX_UNSET;
}
// Object overrides.
@Override
public int hashCode() {
return System.identityHashCode(trackGroup);
}
// Track groups are compared by identity not value, as distinct groups may have the same value.
@Override
@SuppressWarnings({"ReferenceEquality", "EqualsGetClass"})
public boolean equals(@Nullable Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
CastTrackSelection other = (CastTrackSelection) obj;
return trackGroup == other.trackGroup;
}
}
...@@ -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();
......
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.media3.cast;
import static com.google.common.truth.Truth.assertThat;
import androidx.media3.common.C;
import androidx.media3.common.Format;
import androidx.media3.common.TrackGroup;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
/** Test for {@link CastTrackSelection}. */
@RunWith(AndroidJUnit4.class)
public class CastTrackSelectionTest {
private static final TrackGroup TRACK_GROUP =
new TrackGroup(new Format.Builder().build(), new Format.Builder().build());
private static final CastTrackSelection SELECTION = new CastTrackSelection(TRACK_GROUP);
@Test
public void length_isOne() {
assertThat(SELECTION.length()).isEqualTo(1);
}
@Test
public void getTrackGroup_returnsSameGroup() {
assertThat(SELECTION.getTrackGroup()).isSameInstanceAs(TRACK_GROUP);
}
@Test
public void getFormatSelectedTrack_isFirstTrack() {
assertThat(SELECTION.getFormat(0)).isSameInstanceAs(TRACK_GROUP.getFormat(0));
}
@Test
public void getIndexInTrackGroup_ofSelectedTrack_returnsFirstTrack() {
assertThat(SELECTION.getIndexInTrackGroup(0)).isEqualTo(0);
}
@Test
public void getIndexInTrackGroup_onePastTheEnd_returnsIndexUnset() {
assertThat(SELECTION.getIndexInTrackGroup(1)).isEqualTo(C.INDEX_UNSET);
}
@Test
public void indexOf_selectedTrack_returnsFirstTrack() {
assertThat(SELECTION.indexOf(0)).isEqualTo(0);
}
@Test
public void indexOf_onePastTheEnd_returnsIndexUnset() {
assertThat(SELECTION.indexOf(1)).isEqualTo(C.INDEX_UNSET);
}
@Test(expected = Exception.class)
public void getFormat_outOfBound_throws() {
CastTrackSelection selection = new CastTrackSelection(TRACK_GROUP);
selection.getFormat(1);
}
}
...@@ -30,6 +30,9 @@ android { ...@@ -30,6 +30,9 @@ android {
testCoverageEnabled = true testCoverageEnabled = true
} }
} }
lint {
baseline = file("lint-baseline.xml")
}
} }
dependencies { dependencies {
......
<?xml version="1.0" encoding="UTF-8"?>
<!-- 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.
-->
<issues format="6" by="lint 7.2.1" type="baseline" client="gradle" dependencies="false" name="AGP (7.2.1)" variant="all" version="7.2.1">
<issue
id="NewApi"
message="Call requires API level 33 (current min is 32): `android.media.AudioAttributes.Builder#setSpatializationBehavior`"
errorLine1=" builder.setSpatializationBehavior(spatializationBehavior);"
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/java/androidx/media3/common/AudioAttributes.java"
line="278"
column="15"/>
</issue>
</issues>
...@@ -97,14 +97,18 @@ public final class AdOverlayInfo { ...@@ -97,14 +97,18 @@ public final class AdOverlayInfo {
/** An optional, detailed reason that the overlay view is needed. */ /** An optional, detailed reason that the overlay view is needed. */
@Nullable public final String reasonDetail; @Nullable public final String reasonDetail;
/** @deprecated Use {@link Builder} instead. */ /**
* @deprecated Use {@link Builder} instead.
*/
@UnstableApi @UnstableApi
@Deprecated @Deprecated
public AdOverlayInfo(View view, @Purpose int purpose) { public AdOverlayInfo(View view, @Purpose int purpose) {
this(view, purpose, /* detailedReason= */ null); this(view, purpose, /* detailedReason= */ null);
} }
/** @deprecated Use {@link Builder} instead. */ /**
* @deprecated Use {@link Builder} instead.
*/
@UnstableApi @UnstableApi
@Deprecated @Deprecated
public AdOverlayInfo(View view, @Purpose int purpose, @Nullable String detailedReason) { public AdOverlayInfo(View view, @Purpose int purpose, @Nullable String detailedReason) {
......
...@@ -827,6 +827,36 @@ public final class AdPlaybackState implements Bundleable { ...@@ -827,6 +827,36 @@ public final class AdPlaybackState implements Bundleable {
adsId, adGroups, adResumePositionUs, contentDurationUs, removedAdGroupCount); adsId, adGroups, adResumePositionUs, contentDurationUs, removedAdGroupCount);
} }
/**
* Returns a copy of the ad playback state with the given ads ID.
*
* @param adsId The new ads ID.
* @param adPlaybackState The ad playback state to copy.
* @return The new ad playback state.
*/
public static AdPlaybackState fromAdPlaybackState(Object adsId, AdPlaybackState adPlaybackState) {
AdGroup[] adGroups =
new AdGroup[adPlaybackState.adGroupCount - adPlaybackState.removedAdGroupCount];
for (int i = 0; i < adGroups.length; i++) {
AdGroup adGroup = adPlaybackState.adGroups[i];
adGroups[i] =
new AdGroup(
adGroup.timeUs,
adGroup.count,
Arrays.copyOf(adGroup.states, adGroup.states.length),
Arrays.copyOf(adGroup.uris, adGroup.uris.length),
Arrays.copyOf(adGroup.durationsUs, adGroup.durationsUs.length),
adGroup.contentResumeOffsetUs,
adGroup.isServerSideInserted);
}
return new AdPlaybackState(
adsId,
adGroups,
adPlaybackState.adResumePositionUs,
adPlaybackState.contentDurationUs,
adPlaybackState.removedAdGroupCount);
}
@Override @Override
public boolean equals(@Nullable Object o) { public boolean equals(@Nullable Object o) {
if (this == o) { if (this == o) {
......
...@@ -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() {
......
...@@ -92,7 +92,9 @@ public final class DrmInitData implements Comparator<SchemeData>, Parcelable { ...@@ -92,7 +92,9 @@ public final class DrmInitData implements Comparator<SchemeData>, Parcelable {
/** Number of {@link SchemeData}s. */ /** Number of {@link SchemeData}s. */
public final int schemeDataCount; public final int schemeDataCount;
/** @param schemeDatas Scheme initialization data for possibly multiple DRM schemes. */ /**
* @param schemeDatas Scheme initialization data for possibly multiple DRM schemes.
*/
public DrmInitData(List<SchemeData> schemeDatas) { public DrmInitData(List<SchemeData> schemeDatas) {
this(null, false, schemeDatas.toArray(new SchemeData[0])); this(null, false, schemeDatas.toArray(new SchemeData[0]));
} }
...@@ -105,7 +107,9 @@ public final class DrmInitData implements Comparator<SchemeData>, Parcelable { ...@@ -105,7 +107,9 @@ public final class DrmInitData implements Comparator<SchemeData>, Parcelable {
this(schemeType, false, schemeDatas.toArray(new SchemeData[0])); this(schemeType, false, schemeDatas.toArray(new SchemeData[0]));
} }
/** @param schemeDatas Scheme initialization data for possibly multiple DRM schemes. */ /**
* @param schemeDatas Scheme initialization data for possibly multiple DRM schemes.
*/
public DrmInitData(SchemeData... schemeDatas) { public DrmInitData(SchemeData... schemeDatas) {
this(null, schemeDatas); this(null, schemeDatas);
} }
......
...@@ -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, AVI
}) })
public @interface Type {} public @interface Type {}
/** Unknown file type. */ /** Unknown file type. */
...@@ -78,6 +79,10 @@ public final class FileTypes { ...@@ -78,6 +79,10 @@ 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;
/** File type for the AVI format. */
public static final int AVI = 16;
@VisibleForTesting /* package */ static final String HEADER_CONTENT_TYPE = "Content-Type"; @VisibleForTesting /* package */ static final String HEADER_CONTENT_TYPE = "Content-Type";
...@@ -89,6 +94,9 @@ public final class FileTypes { ...@@ -89,6 +94,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";
...@@ -110,6 +118,7 @@ public final class FileTypes { ...@@ -110,6 +118,7 @@ public final class FileTypes {
private static final String EXTENSION_WEBVTT = ".webvtt"; private static final String EXTENSION_WEBVTT = ".webvtt";
private static final String EXTENSION_JPG = ".jpg"; private static final String EXTENSION_JPG = ".jpg";
private static final String EXTENSION_JPEG = ".jpeg"; private static final String EXTENSION_JPEG = ".jpeg";
private static final String EXTENSION_AVI = ".avi";
private FileTypes() {} private FileTypes() {}
...@@ -147,6 +156,8 @@ public final class FileTypes { ...@@ -147,6 +156,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:
...@@ -171,6 +182,8 @@ public final class FileTypes { ...@@ -171,6 +182,8 @@ public final class FileTypes {
return FileTypes.WEBVTT; return FileTypes.WEBVTT;
case MimeTypes.IMAGE_JPEG: case MimeTypes.IMAGE_JPEG:
return FileTypes.JPEG; return FileTypes.JPEG;
case MimeTypes.VIDEO_AVI:
return FileTypes.AVI;
default: default:
return FileTypes.UNKNOWN; return FileTypes.UNKNOWN;
} }
...@@ -193,6 +206,10 @@ public final class FileTypes { ...@@ -193,6 +206,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))
...@@ -232,6 +249,8 @@ public final class FileTypes { ...@@ -232,6 +249,8 @@ public final class FileTypes {
return FileTypes.WEBVTT; return FileTypes.WEBVTT;
} else if (filename.endsWith(EXTENSION_JPG) || filename.endsWith(EXTENSION_JPEG)) { } else if (filename.endsWith(EXTENSION_JPG) || filename.endsWith(EXTENSION_JPEG)) {
return FileTypes.JPEG; return FileTypes.JPEG;
} else if (filename.endsWith(EXTENSION_AVI)) {
return FileTypes.AVI;
} else { } else {
return FileTypes.UNKNOWN; return FileTypes.UNKNOWN;
} }
......
...@@ -769,7 +769,9 @@ public final class Format implements Bundleable { ...@@ -769,7 +769,9 @@ public final class Format implements Bundleable {
// Video. // Video.
/** @deprecated Use {@link Format.Builder}. */ /**
* @deprecated Use {@link Format.Builder}.
*/
@UnstableApi @UnstableApi
@Deprecated @Deprecated
public static Format createVideoSampleFormat( public static Format createVideoSampleFormat(
...@@ -798,7 +800,9 @@ public final class Format implements Bundleable { ...@@ -798,7 +800,9 @@ public final class Format implements Bundleable {
.build(); .build();
} }
/** @deprecated Use {@link Format.Builder}. */ /**
* @deprecated Use {@link Format.Builder}.
*/
@UnstableApi @UnstableApi
@Deprecated @Deprecated
public static Format createVideoSampleFormat( public static Format createVideoSampleFormat(
...@@ -833,7 +837,9 @@ public final class Format implements Bundleable { ...@@ -833,7 +837,9 @@ public final class Format implements Bundleable {
// Audio. // Audio.
/** @deprecated Use {@link Format.Builder}. */ /**
* @deprecated Use {@link Format.Builder}.
*/
@UnstableApi @UnstableApi
@Deprecated @Deprecated
public static Format createAudioSampleFormat( public static Format createAudioSampleFormat(
...@@ -864,7 +870,9 @@ public final class Format implements Bundleable { ...@@ -864,7 +870,9 @@ public final class Format implements Bundleable {
.build(); .build();
} }
/** @deprecated Use {@link Format.Builder}. */ /**
* @deprecated Use {@link Format.Builder}.
*/
@UnstableApi @UnstableApi
@Deprecated @Deprecated
public static Format createAudioSampleFormat( public static Format createAudioSampleFormat(
...@@ -899,7 +907,9 @@ public final class Format implements Bundleable { ...@@ -899,7 +907,9 @@ public final class Format implements Bundleable {
// Generic. // Generic.
/** @deprecated Use {@link Format.Builder}. */ /**
* @deprecated Use {@link Format.Builder}.
*/
@UnstableApi @UnstableApi
@Deprecated @Deprecated
public static Format createContainerFormat( public static Format createContainerFormat(
...@@ -926,7 +936,9 @@ public final class Format implements Bundleable { ...@@ -926,7 +936,9 @@ public final class Format implements Bundleable {
.build(); .build();
} }
/** @deprecated Use {@link Format.Builder}. */ /**
* @deprecated Use {@link Format.Builder}.
*/
@UnstableApi @UnstableApi
@Deprecated @Deprecated
public static Format createSampleFormat(@Nullable String id, @Nullable String sampleMimeType) { public static Format createSampleFormat(@Nullable String id, @Nullable String sampleMimeType) {
...@@ -986,28 +998,36 @@ public final class Format implements Bundleable { ...@@ -986,28 +998,36 @@ public final class Format implements Bundleable {
return new Builder(this); return new Builder(this);
} }
/** @deprecated Use {@link #buildUpon()} and {@link Builder#setMaxInputSize(int)}. */ /**
* @deprecated Use {@link #buildUpon()} and {@link Builder#setMaxInputSize(int)}.
*/
@UnstableApi @UnstableApi
@Deprecated @Deprecated
public Format copyWithMaxInputSize(int maxInputSize) { public Format copyWithMaxInputSize(int maxInputSize) {
return buildUpon().setMaxInputSize(maxInputSize).build(); return buildUpon().setMaxInputSize(maxInputSize).build();
} }
/** @deprecated Use {@link #buildUpon()} and {@link Builder#setSubsampleOffsetUs(long)}. */ /**
* @deprecated Use {@link #buildUpon()} and {@link Builder#setSubsampleOffsetUs(long)}.
*/
@UnstableApi @UnstableApi
@Deprecated @Deprecated
public Format copyWithSubsampleOffsetUs(long subsampleOffsetUs) { public Format copyWithSubsampleOffsetUs(long subsampleOffsetUs) {
return buildUpon().setSubsampleOffsetUs(subsampleOffsetUs).build(); return buildUpon().setSubsampleOffsetUs(subsampleOffsetUs).build();
} }
/** @deprecated Use {@link #buildUpon()} and {@link Builder#setLabel(String)} . */ /**
* @deprecated Use {@link #buildUpon()} and {@link Builder#setLabel(String)} .
*/
@UnstableApi @UnstableApi
@Deprecated @Deprecated
public Format copyWithLabel(@Nullable String label) { public Format copyWithLabel(@Nullable String label) {
return buildUpon().setLabel(label).build(); return buildUpon().setLabel(label).build();
} }
/** @deprecated Use {@link #withManifestFormatInfo(Format)}. */ /**
* @deprecated Use {@link #withManifestFormatInfo(Format)}.
*/
@UnstableApi @UnstableApi
@Deprecated @Deprecated
public Format copyWithManifestFormatInfo(Format manifestFormat) { public Format copyWithManifestFormatInfo(Format manifestFormat) {
...@@ -1092,21 +1112,27 @@ public final class Format implements Bundleable { ...@@ -1092,21 +1112,27 @@ public final class Format implements Bundleable {
return buildUpon().setEncoderDelay(encoderDelay).setEncoderPadding(encoderPadding).build(); return buildUpon().setEncoderDelay(encoderDelay).setEncoderPadding(encoderPadding).build();
} }
/** @deprecated Use {@link #buildUpon()} and {@link Builder#setFrameRate(float)}. */ /**
* @deprecated Use {@link #buildUpon()} and {@link Builder#setFrameRate(float)}.
*/
@UnstableApi @UnstableApi
@Deprecated @Deprecated
public Format copyWithFrameRate(float frameRate) { public Format copyWithFrameRate(float frameRate) {
return buildUpon().setFrameRate(frameRate).build(); return buildUpon().setFrameRate(frameRate).build();
} }
/** @deprecated Use {@link #buildUpon()} and {@link Builder#setDrmInitData(DrmInitData)}. */ /**
* @deprecated Use {@link #buildUpon()} and {@link Builder#setDrmInitData(DrmInitData)}.
*/
@UnstableApi @UnstableApi
@Deprecated @Deprecated
public Format copyWithDrmInitData(@Nullable DrmInitData drmInitData) { public Format copyWithDrmInitData(@Nullable DrmInitData drmInitData) {
return buildUpon().setDrmInitData(drmInitData).build(); return buildUpon().setDrmInitData(drmInitData).build();
} }
/** @deprecated Use {@link #buildUpon()} and {@link Builder#setMetadata(Metadata)}. */ /**
* @deprecated Use {@link #buildUpon()} and {@link Builder#setMetadata(Metadata)}.
*/
@UnstableApi @UnstableApi
@Deprecated @Deprecated
public Format copyWithMetadata(@Nullable Metadata metadata) { public Format copyWithMetadata(@Nullable Metadata metadata) {
...@@ -1522,7 +1548,9 @@ public final class Format implements Bundleable { ...@@ -1522,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);
...@@ -1589,11 +1617,13 @@ public final class Format implements Bundleable { ...@@ -1589,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
...@@ -442,26 +481,10 @@ public class ForwardingPlayer implements Player { ...@@ -442,26 +481,10 @@ public class ForwardingPlayer implements Player {
player.release(); player.release();
} }
/** Calls {@link Player#getCurrentTrackGroups()} on the delegate and returns the result. */ /** Calls {@link Player#getCurrentTracks()} on the delegate and returns the result. */
@SuppressWarnings("deprecation") // Forwarding to deprecated method
@Deprecated
@Override @Override
public TrackGroupArray getCurrentTrackGroups() { public Tracks getCurrentTracks() {
return player.getCurrentTrackGroups(); return player.getCurrentTracks();
}
/** Calls {@link Player#getCurrentTrackSelections()} on the delegate and returns the result. */
@SuppressWarnings("deprecation") // Forwarding to deprecated method
@Deprecated
@Override
public TrackSelectionArray getCurrentTrackSelections() {
return player.getCurrentTrackSelections();
}
/** Calls {@link Player#getCurrentTracksInfo()} on the delegate and returns the result. */
@Override
public TracksInfo getCurrentTracksInfo() {
return player.getCurrentTracksInfo();
} }
/** Calls {@link Player#getTrackSelectionParameters()} on the delegate and returns the result. */ /** Calls {@link Player#getTrackSelectionParameters()} on the delegate and returns the result. */
...@@ -513,7 +536,11 @@ public class ForwardingPlayer implements Player { ...@@ -513,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
...@@ -527,7 +554,11 @@ public class ForwardingPlayer implements Player { ...@@ -527,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
...@@ -541,7 +572,11 @@ public class ForwardingPlayer implements Player { ...@@ -541,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
...@@ -604,7 +639,11 @@ public class ForwardingPlayer implements Player { ...@@ -604,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
...@@ -618,7 +657,11 @@ public class ForwardingPlayer implements Player { ...@@ -618,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
...@@ -638,7 +681,11 @@ public class ForwardingPlayer implements Player { ...@@ -638,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
...@@ -768,7 +815,7 @@ public class ForwardingPlayer implements Player { ...@@ -768,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();
} }
...@@ -847,14 +894,8 @@ public class ForwardingPlayer implements Player { ...@@ -847,14 +894,8 @@ public class ForwardingPlayer implements Player {
} }
@Override @Override
@SuppressWarnings("deprecation") public void onTracksChanged(Tracks tracks) {
public void onTracksChanged(TrackGroupArray trackGroups, TrackSelectionArray trackSelections) { listener.onTracksChanged(tracks);
listener.onTracksChanged(trackGroups, trackSelections);
}
@Override
public void onTracksInfoChanged(TracksInfo tracksInfo) {
listener.onTracksInfoChanged(tracksInfo);
} }
@Override @Override
...@@ -1015,6 +1056,11 @@ public class ForwardingPlayer implements Player { ...@@ -1015,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);
} }
......
...@@ -29,11 +29,11 @@ public final class MediaLibraryInfo { ...@@ -29,11 +29,11 @@ public final class MediaLibraryInfo {
/** The version of the library expressed as a string, for example "1.2.3" or "1.2.3-beta01". */ /** The version of the library expressed as a string, for example "1.2.3" or "1.2.3-beta01". */
// Intentionally hardcoded. Do not derive from other constants (e.g. VERSION_INT) or vice versa. // Intentionally hardcoded. Do not derive from other constants (e.g. VERSION_INT) or vice versa.
public static final String VERSION = "1.0.0-alpha03"; public static final String VERSION = "1.0.0-beta01";
/** The version of the library expressed as {@code TAG + "/" + VERSION}. */ /** The version of the library expressed as {@code TAG + "/" + VERSION}. */
// Intentionally hardcoded. Do not derive from other constants (e.g. VERSION) or vice versa. // Intentionally hardcoded. Do not derive from other constants (e.g. VERSION) or vice versa.
public static final String VERSION_SLASHY = "AndroidXMedia3/1.0.0-alpha03"; public static final String VERSION_SLASHY = "AndroidXMedia3/1.0.0-beta01";
/** /**
* The version of the library expressed as an integer, for example 1002003300. * The version of the library expressed as an integer, for example 1002003300.
...@@ -47,7 +47,7 @@ public final class MediaLibraryInfo { ...@@ -47,7 +47,7 @@ public final class MediaLibraryInfo {
* (123-045-006-3-00). * (123-045-006-3-00).
*/ */
// Intentionally hardcoded. Do not derive from other constants (e.g. VERSION) or vice versa. // Intentionally hardcoded. Do not derive from other constants (e.g. VERSION) or vice versa.
public static final int VERSION_INT = 1_000_000_0_03; public static final int VERSION_INT = 1_000_000_1_01;
/** Whether the library was compiled with {@link Assertions} checks enabled. */ /** Whether the library was compiled with {@link Assertions} checks enabled. */
public static final boolean ASSERTIONS_ENABLED = true; public static final boolean ASSERTIONS_ENABLED = true;
......
...@@ -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;
...@@ -248,7 +240,9 @@ public final class MediaMetadata implements Bundleable { ...@@ -248,7 +240,9 @@ public final class MediaMetadata implements Bundleable {
return this; return this;
} }
/** @deprecated Use {@link #setRecordingYear(Integer)} instead. */ /**
* @deprecated Use {@link #setRecordingYear(Integer)} instead.
*/
@UnstableApi @UnstableApi
@Deprecated @Deprecated
public Builder setYear(@Nullable Integer year) { public Builder setYear(@Nullable Integer year) {
...@@ -429,9 +423,6 @@ public final class MediaMetadata implements Bundleable { ...@@ -429,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);
} }
...@@ -634,8 +625,6 @@ public final class MediaMetadata implements Bundleable { ...@@ -634,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}. */
...@@ -654,7 +643,9 @@ public final class MediaMetadata implements Bundleable { ...@@ -654,7 +643,9 @@ public final class MediaMetadata implements Bundleable {
@Nullable public final @FolderType Integer folderType; @Nullable public final @FolderType Integer folderType;
/** Optional boolean for media playability. */ /** Optional boolean for media playability. */
@Nullable public final Boolean isPlayable; @Nullable public final Boolean isPlayable;
/** @deprecated Use {@link #recordingYear} instead. */ /**
* @deprecated Use {@link #recordingYear} instead.
*/
@UnstableApi @Deprecated @Nullable public final Integer year; @UnstableApi @Deprecated @Nullable public final Integer year;
/** Optional year of the recording date. */ /** Optional year of the recording date. */
@Nullable public final Integer recordingYear; @Nullable public final Integer recordingYear;
...@@ -718,7 +709,6 @@ public final class MediaMetadata implements Bundleable { ...@@ -718,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;
...@@ -767,7 +757,6 @@ public final class MediaMetadata implements Bundleable { ...@@ -767,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)
...@@ -803,7 +792,6 @@ public final class MediaMetadata implements Bundleable { ...@@ -803,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),
...@@ -914,7 +902,6 @@ public final class MediaMetadata implements Bundleable { ...@@ -914,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);
...@@ -988,7 +975,6 @@ public final class MediaMetadata implements Bundleable { ...@@ -988,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))
......
...@@ -62,12 +62,16 @@ public final class Metadata implements Parcelable { ...@@ -62,12 +62,16 @@ public final class Metadata implements Parcelable {
private final Entry[] entries; private final Entry[] entries;
/** @param entries The metadata entries. */ /**
* @param entries The metadata entries.
*/
public Metadata(Entry... entries) { public Metadata(Entry... entries) {
this.entries = entries; this.entries = entries;
} }
/** @param entries The metadata entries. */ /**
* @param entries The metadata entries.
*/
public Metadata(List<? extends Entry> entries) { public Metadata(List<? extends Entry> entries) {
this.entries = entries.toArray(new Entry[0]); this.entries = entries.toArray(new Entry[0]);
} }
......
...@@ -56,6 +56,10 @@ public final class MimeTypes { ...@@ -56,6 +56,10 @@ public final class MimeTypes {
@UnstableApi public static final String VIDEO_FLV = BASE_TYPE_VIDEO + "/x-flv"; @UnstableApi public static final String VIDEO_FLV = BASE_TYPE_VIDEO + "/x-flv";
public static final String VIDEO_DOLBY_VISION = BASE_TYPE_VIDEO + "/dolby-vision"; public static final String VIDEO_DOLBY_VISION = BASE_TYPE_VIDEO + "/dolby-vision";
public static final String VIDEO_OGG = BASE_TYPE_VIDEO + "/ogg"; public static final String VIDEO_OGG = BASE_TYPE_VIDEO + "/ogg";
public static final String VIDEO_AVI = BASE_TYPE_VIDEO + "/x-msvideo";
public static final String VIDEO_MJPEG = BASE_TYPE_VIDEO + "/mjpeg";
public static final String VIDEO_MP42 = BASE_TYPE_VIDEO + "/mp42";
public static final String VIDEO_MP43 = BASE_TYPE_VIDEO + "/mp43";
@UnstableApi public static final String VIDEO_UNKNOWN = BASE_TYPE_VIDEO + "/x-unknown"; @UnstableApi public static final String VIDEO_UNKNOWN = BASE_TYPE_VIDEO + "/x-unknown";
// audio/ MIME types // audio/ MIME types
...@@ -87,6 +91,7 @@ public final class MimeTypes { ...@@ -87,6 +91,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";
...@@ -108,11 +113,10 @@ public final class MimeTypes { ...@@ -108,11 +113,10 @@ public final class MimeTypes {
public static final String APPLICATION_MP4 = BASE_TYPE_APPLICATION + "/mp4"; public static final String APPLICATION_MP4 = BASE_TYPE_APPLICATION + "/mp4";
public static final String APPLICATION_WEBM = BASE_TYPE_APPLICATION + "/webm"; public static final String APPLICATION_WEBM = BASE_TYPE_APPLICATION + "/webm";
@UnstableApi
public static final String APPLICATION_MATROSKA = BASE_TYPE_APPLICATION + "/x-matroska"; public static final String APPLICATION_MATROSKA = BASE_TYPE_APPLICATION + "/x-matroska";
public static final String APPLICATION_MPD = BASE_TYPE_APPLICATION + "/dash+xml"; public static final String APPLICATION_MPD = BASE_TYPE_APPLICATION + "/dash+xml";
@UnstableApi public static final String APPLICATION_M3U8 = BASE_TYPE_APPLICATION + "/x-mpegURL"; public static final String APPLICATION_M3U8 = BASE_TYPE_APPLICATION + "/x-mpegURL";
public static final String APPLICATION_SS = BASE_TYPE_APPLICATION + "/vnd.ms-sstr+xml"; public static final String APPLICATION_SS = BASE_TYPE_APPLICATION + "/vnd.ms-sstr+xml";
public static final String APPLICATION_ID3 = BASE_TYPE_APPLICATION + "/id3"; public static final String APPLICATION_ID3 = BASE_TYPE_APPLICATION + "/id3";
public static final String APPLICATION_CEA608 = BASE_TYPE_APPLICATION + "/cea-608"; public static final String APPLICATION_CEA608 = BASE_TYPE_APPLICATION + "/cea-608";
...@@ -135,7 +139,7 @@ public final class MimeTypes { ...@@ -135,7 +139,7 @@ public final class MimeTypes {
@UnstableApi public static final String APPLICATION_EXIF = BASE_TYPE_APPLICATION + "/x-exif"; @UnstableApi public static final String APPLICATION_EXIF = BASE_TYPE_APPLICATION + "/x-exif";
@UnstableApi public static final String APPLICATION_ICY = BASE_TYPE_APPLICATION + "/x-icy"; @UnstableApi public static final String APPLICATION_ICY = BASE_TYPE_APPLICATION + "/x-icy";
public static final String APPLICATION_AIT = BASE_TYPE_APPLICATION + "/vnd.dvb.ait"; public static final String APPLICATION_AIT = BASE_TYPE_APPLICATION + "/vnd.dvb.ait";
@UnstableApi public static final String APPLICATION_RTSP = BASE_TYPE_APPLICATION + "/x-rtsp"; public static final String APPLICATION_RTSP = BASE_TYPE_APPLICATION + "/x-rtsp";
// image/ MIME types // image/ MIME types
......
...@@ -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 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() {}
......
...@@ -44,7 +44,9 @@ public final class StreamKey implements Comparable<StreamKey>, Parcelable { ...@@ -44,7 +44,9 @@ public final class StreamKey implements Comparable<StreamKey>, Parcelable {
/** The stream index. */ /** The stream index. */
public final int streamIndex; public final int streamIndex;
/** @deprecated Use {@link #streamIndex}. */ /**
* @deprecated Use {@link #streamIndex}.
*/
@Deprecated public final int trackIndex; @Deprecated public final int trackIndex;
/** /**
......
...@@ -169,7 +169,9 @@ public abstract class Timeline implements Bundleable { ...@@ -169,7 +169,9 @@ public abstract class Timeline implements Bundleable {
*/ */
public Object uid; public Object uid;
/** @deprecated Use {@link #mediaItem} instead. */ /**
* @deprecated Use {@link #mediaItem} instead.
*/
@UnstableApi @Deprecated @Nullable public Object tag; @UnstableApi @Deprecated @Nullable public Object tag;
/** The {@link MediaItem} associated to the window. Not necessarily unique. */ /** The {@link MediaItem} associated to the window. Not necessarily unique. */
...@@ -212,7 +214,9 @@ public abstract class Timeline implements Bundleable { ...@@ -212,7 +214,9 @@ public abstract class Timeline implements Bundleable {
/** Whether this window may change when the timeline is updated. */ /** Whether this window may change when the timeline is updated. */
public boolean isDynamic; public boolean isDynamic;
/** @deprecated Use {@link #isLive()} instead. */ /**
* @deprecated Use {@link #isLive()} instead.
*/
@UnstableApi @Deprecated public boolean isLive; @UnstableApi @Deprecated public boolean isLive;
/** /**
...@@ -1178,7 +1182,9 @@ public abstract class Timeline implements Bundleable { ...@@ -1178,7 +1182,9 @@ public abstract class Timeline implements Bundleable {
== C.INDEX_UNSET; == C.INDEX_UNSET;
} }
/** @deprecated Use {@link #getPeriodPositionUs(Window, Period, int, long)} instead. */ /**
* @deprecated Use {@link #getPeriodPositionUs(Window, Period, int, long)} instead.
*/
@UnstableApi @UnstableApi
@Deprecated @Deprecated
@InlineMe(replacement = "this.getPeriodPositionUs(window, period, windowIndex, windowPositionUs)") @InlineMe(replacement = "this.getPeriodPositionUs(window, period, windowIndex, windowPositionUs)")
...@@ -1186,7 +1192,9 @@ public abstract class Timeline implements Bundleable { ...@@ -1186,7 +1192,9 @@ public abstract class Timeline implements Bundleable {
Window window, Period period, int windowIndex, long windowPositionUs) { Window window, Period period, int windowIndex, long windowPositionUs) {
return getPeriodPositionUs(window, period, windowIndex, windowPositionUs); return getPeriodPositionUs(window, period, windowIndex, windowPositionUs);
} }
/** @deprecated Use {@link #getPeriodPositionUs(Window, Period, int, long, long)} instead. */ /**
* @deprecated Use {@link #getPeriodPositionUs(Window, Period, int, long, long)} instead.
*/
@UnstableApi @UnstableApi
@Deprecated @Deprecated
@Nullable @Nullable
......
...@@ -34,15 +34,35 @@ import java.lang.annotation.Target; ...@@ -34,15 +34,35 @@ import java.lang.annotation.Target;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
/** Defines an immutable group of tracks identified by their format identity. */ /**
* An immutable group of tracks available within a media stream. All tracks in a group present the
* same content, but their formats may differ.
*
* <p>As an example of how tracks can be grouped, consider an adaptive playback where a main video
* feed is provided in five resolutions, and an alternative video feed (e.g., a different camera
* angle in a sports match) is provided in two resolutions. In this case there will be two video
* track groups, one corresponding to the main video feed containing five tracks, and a second for
* the alternative video feed containing two tracks.
*
* <p>Note that audio tracks whose languages differ are not grouped, because content in different
* languages is not considered to be the same. Conversely, audio tracks in the same language that
* only differ in properties such as bitrate, sampling rate, channel count and so on can be grouped.
* This also applies to text tracks.
*
* <p>Note also that this class only contains information derived from the media itself. Unlike
* {@link Tracks.Group}, it does not include runtime information such as the extent to which
* playback of each track is supported by the device, or which tracks are currently selected.
*/
public final class TrackGroup implements Bundleable { public final class TrackGroup implements Bundleable {
private static final String TAG = "TrackGroup"; private static final String TAG = "TrackGroup";
/** The number of tracks in the group. */ /** The number of tracks in the group. */
public final int length; @UnstableApi public final int length;
/** An identifier for the track group. */ /** An identifier for the track group. */
public final String id; @UnstableApi public final String id;
/** The type of tracks in the group. */
@UnstableApi public final @C.TrackType int type;
private final Format[] formats; private final Format[] formats;
...@@ -71,6 +91,11 @@ public final class TrackGroup implements Bundleable { ...@@ -71,6 +91,11 @@ public final class TrackGroup implements Bundleable {
this.id = id; this.id = id;
this.formats = formats; this.formats = formats;
this.length = formats.length; this.length = formats.length;
@C.TrackType int type = MimeTypes.getTrackType(formats[0].sampleMimeType);
if (type == C.TRACK_TYPE_UNKNOWN) {
type = MimeTypes.getTrackType(formats[0].containerMimeType);
}
this.type = type;
verifyCorrectness(); verifyCorrectness();
} }
...@@ -92,6 +117,7 @@ public final class TrackGroup implements Bundleable { ...@@ -92,6 +117,7 @@ public final class TrackGroup implements Bundleable {
* @param index The index of the track. * @param index The index of the track.
* @return The track's format. * @return The track's format.
*/ */
@UnstableApi
public Format getFormat(int index) { public Format getFormat(int index) {
return formats[index]; return formats[index];
} }
...@@ -105,6 +131,7 @@ public final class TrackGroup implements Bundleable { ...@@ -105,6 +131,7 @@ public final class TrackGroup implements Bundleable {
* @return The index of the track, or {@link C#INDEX_UNSET} if no such track exists. * @return The index of the track, or {@link C#INDEX_UNSET} if no such track exists.
*/ */
@SuppressWarnings("ReferenceEquality") @SuppressWarnings("ReferenceEquality")
@UnstableApi
public int indexOf(Format format) { public int indexOf(Format format) {
for (int i = 0; i < formats.length; i++) { for (int i = 0; i < formats.length; i++) {
if (format == formats[i]) { if (format == formats[i]) {
...@@ -134,7 +161,7 @@ public final class TrackGroup implements Bundleable { ...@@ -134,7 +161,7 @@ public final class TrackGroup implements Bundleable {
return false; return false;
} }
TrackGroup other = (TrackGroup) obj; TrackGroup other = (TrackGroup) obj;
return length == other.length && id.equals(other.id) && Arrays.equals(formats, other.formats); return id.equals(other.id) && Arrays.equals(formats, other.formats);
} }
// Bundleable implementation. // Bundleable implementation.
...@@ -162,11 +189,12 @@ public final class TrackGroup implements Bundleable { ...@@ -162,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]));
}; };
......
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.media3.common;
import static androidx.media3.common.util.Assertions.checkNotNull;
import static java.util.Collections.max;
import static java.util.Collections.min;
import android.os.Bundle;
import androidx.annotation.IntDef;
import androidx.annotation.Nullable;
import androidx.media3.common.util.UnstableApi;
import com.google.common.collect.ImmutableList;
import com.google.common.primitives.Ints;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.List;
/**
* A track selection override, consisting of a {@link TrackGroup} and the indices of the tracks
* within the group that should be selected.
*
* <p>A track selection override is applied during playback if the media being played contains a
* {@link TrackGroup} equal to the one in the override. If a {@link TrackSelectionParameters}
* contains only one override of a given track type that applies to the media, this override will be
* used to control the track selection for that type. If multiple overrides of a given track type
* apply then the player will apply only one of them.
*
* <p>If {@link #trackIndices} is empty then the override specifies that no tracks should be
* selected. Adding an empty override to a {@link TrackSelectionParameters} is similar to {@link
* TrackSelectionParameters.Builder#setTrackTypeDisabled disabling a track type}, except that an
* empty override will only be applied if the media being played contains a {@link TrackGroup} equal
* to the one in the override. Conversely, disabling a track type will prevent selection of tracks
* of that type for all media.
*/
public final class TrackSelectionOverride implements Bundleable {
/** The media {@link TrackGroup} whose {@link #trackIndices} are forced to be selected. */
public final TrackGroup mediaTrackGroup;
/** The indices of tracks in a {@link TrackGroup} to be selected. */
public final ImmutableList<Integer> trackIndices;
@Documented
@Retention(RetentionPolicy.SOURCE)
@IntDef({
FIELD_TRACK_GROUP,
FIELD_TRACKS,
})
private @interface FieldNumber {}
private static final int FIELD_TRACK_GROUP = 0;
private static final int FIELD_TRACKS = 1;
/**
* Constructs an instance to force {@code trackIndex} in {@code trackGroup} to be selected.
*
* @param mediaTrackGroup The media {@link TrackGroup} for which to override the track selection.
* @param trackIndex The index of the track in the {@link TrackGroup} to select.
*/
public TrackSelectionOverride(TrackGroup mediaTrackGroup, int trackIndex) {
this(mediaTrackGroup, ImmutableList.of(trackIndex));
}
/**
* Constructs an instance to force {@code trackIndices} in {@code trackGroup} to be selected.
*
* @param mediaTrackGroup The media {@link TrackGroup} for which to override the track selection.
* @param trackIndices The indices of the tracks in the {@link TrackGroup} to select.
*/
public TrackSelectionOverride(TrackGroup mediaTrackGroup, List<Integer> trackIndices) {
if (!trackIndices.isEmpty()) {
if (min(trackIndices) < 0 || max(trackIndices) >= mediaTrackGroup.length) {
throw new IndexOutOfBoundsException();
}
}
this.mediaTrackGroup = mediaTrackGroup;
this.trackIndices = ImmutableList.copyOf(trackIndices);
}
/** Returns the {@link C.TrackType} of the overridden track group. */
public @C.TrackType int getType() {
return mediaTrackGroup.type;
}
@Override
public boolean equals(@Nullable Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
TrackSelectionOverride that = (TrackSelectionOverride) obj;
return mediaTrackGroup.equals(that.mediaTrackGroup) && trackIndices.equals(that.trackIndices);
}
@Override
public int hashCode() {
return mediaTrackGroup.hashCode() + 31 * trackIndices.hashCode();
}
// Bundleable implementation
@UnstableApi
@Override
public Bundle toBundle() {
Bundle bundle = new Bundle();
bundle.putBundle(keyForField(FIELD_TRACK_GROUP), mediaTrackGroup.toBundle());
bundle.putIntArray(keyForField(FIELD_TRACKS), Ints.toArray(trackIndices));
return bundle;
}
/** Object that can restore {@code TrackSelectionOverride} from a {@link Bundle}. */
@UnstableApi
public static final Creator<TrackSelectionOverride> CREATOR =
bundle -> {
Bundle trackGroupBundle = checkNotNull(bundle.getBundle(keyForField(FIELD_TRACK_GROUP)));
TrackGroup mediaTrackGroup = TrackGroup.CREATOR.fromBundle(trackGroupBundle);
int[] tracks = checkNotNull(bundle.getIntArray(keyForField(FIELD_TRACKS)));
return new TrackSelectionOverride(mediaTrackGroup, Ints.asList(tracks));
};
private static String keyForField(@FieldNumber int field) {
return Integer.toString(field, Character.MAX_RADIX);
}
}
/*
* 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.
......
...@@ -36,10 +36,14 @@ public interface Clock { ...@@ -36,10 +36,14 @@ public interface Clock {
*/ */
long currentTimeMillis(); long currentTimeMillis();
/** @see android.os.SystemClock#elapsedRealtime() */ /**
* @see android.os.SystemClock#elapsedRealtime()
*/
long elapsedRealtime(); long elapsedRealtime();
/** @see android.os.SystemClock#uptimeMillis() */ /**
* @see android.os.SystemClock#uptimeMillis()
*/
long uptimeMillis(); long uptimeMillis();
/** /**
......
...@@ -15,6 +15,8 @@ ...@@ -15,6 +15,8 @@
*/ */
package androidx.media3.common.util; package androidx.media3.common.util;
import static androidx.media3.common.util.Assertions.checkArgument;
import android.util.Pair; import android.util.Pair;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.media3.common.C; import androidx.media3.common.C;
...@@ -31,6 +33,12 @@ public final class CodecSpecificDataUtil { ...@@ -31,6 +33,12 @@ public final class CodecSpecificDataUtil {
private static final String[] HEVC_GENERAL_PROFILE_SPACE_STRINGS = private static final String[] HEVC_GENERAL_PROFILE_SPACE_STRINGS =
new String[] {"", "A", "B", "C"}; new String[] {"", "A", "B", "C"};
// MP4V-ES
private static final int VISUAL_OBJECT_LAYER = 1;
private static final int VISUAL_OBJECT_LAYER_START = 0x20;
private static final int EXTENDED_PAR = 0x0F;
private static final int RECTANGULAR = 0x00;
/** /**
* Parses an ALAC AudioSpecificConfig (i.e. an <a * Parses an ALAC AudioSpecificConfig (i.e. an <a
* href="https://github.com/macosforge/alac/blob/master/ALACMagicCookieDescription.txt">ALACSpecificConfig</a>). * href="https://github.com/macosforge/alac/blob/master/ALACMagicCookieDescription.txt">ALACSpecificConfig</a>).
...@@ -73,6 +81,87 @@ public final class CodecSpecificDataUtil { ...@@ -73,6 +81,87 @@ public final class CodecSpecificDataUtil {
} }
/** /**
* Parses an MPEG-4 Visual configuration information, as defined in ISO/IEC14496-2.
*
* @param videoSpecificConfig A byte array containing the MPEG-4 Visual configuration information
* to parse.
* @return A pair of the video's width and height.
*/
public static Pair<Integer, Integer> getVideoResolutionFromMpeg4VideoConfig(
byte[] videoSpecificConfig) {
int offset = 0;
boolean foundVOL = false;
ParsableByteArray scratchBytes = new ParsableByteArray(videoSpecificConfig);
while (offset + 3 < videoSpecificConfig.length) {
if (scratchBytes.readUnsignedInt24() != VISUAL_OBJECT_LAYER
|| (videoSpecificConfig[offset + 3] & 0xF0) != VISUAL_OBJECT_LAYER_START) {
scratchBytes.setPosition(scratchBytes.getPosition() - 2);
offset++;
continue;
}
foundVOL = true;
break;
}
checkArgument(foundVOL, "Invalid input: VOL not found.");
ParsableBitArray scratchBits = new ParsableBitArray(videoSpecificConfig);
// Skip the start codecs from the bitstream
scratchBits.skipBits((offset + 4) * 8);
scratchBits.skipBits(1); // random_accessible_vol
scratchBits.skipBits(8); // video_object_type_indication
if (scratchBits.readBit()) { // object_layer_identifier
scratchBits.skipBits(4); // video_object_layer_verid
scratchBits.skipBits(3); // video_object_layer_priority
}
int aspectRatioInfo = scratchBits.readBits(4);
if (aspectRatioInfo == EXTENDED_PAR) {
scratchBits.skipBits(8); // par_width
scratchBits.skipBits(8); // par_height
}
if (scratchBits.readBit()) { // vol_control_parameters
scratchBits.skipBits(2); // chroma_format
scratchBits.skipBits(1); // low_delay
if (scratchBits.readBit()) { // vbv_parameters
scratchBits.skipBits(79);
}
}
int videoObjectLayerShape = scratchBits.readBits(2);
checkArgument(
videoObjectLayerShape == RECTANGULAR,
"Only supports rectangular video object layer shape.");
checkArgument(scratchBits.readBit()); // marker_bit
int vopTimeIncrementResolution = scratchBits.readBits(16);
checkArgument(scratchBits.readBit()); // marker_bit
if (scratchBits.readBit()) { // fixed_vop_rate
checkArgument(vopTimeIncrementResolution > 0);
vopTimeIncrementResolution--;
int numBitsToSkip = 0;
while (vopTimeIncrementResolution > 0) {
numBitsToSkip++;
vopTimeIncrementResolution >>= 1;
}
scratchBits.skipBits(numBitsToSkip); // fixed_vop_time_increment
}
checkArgument(scratchBits.readBit()); // marker_bit
int videoObjectLayerWidth = scratchBits.readBits(13);
checkArgument(scratchBits.readBit()); // marker_bit
int videoObjectLayerHeight = scratchBits.readBits(13);
checkArgument(scratchBits.readBit()); // marker_bit
scratchBits.skipBits(1); // interlaced
return Pair.create(videoObjectLayerWidth, videoObjectLayerHeight);
}
/**
* Builds an RFC 6381 AVC codec string using the provided parameters. * Builds an RFC 6381 AVC codec string using the provided parameters.
* *
* @param profileIdc The encoding profile. * @param profileIdc The encoding profile.
......
...@@ -27,7 +27,9 @@ public abstract class LibraryLoader { ...@@ -27,7 +27,9 @@ public abstract class LibraryLoader {
private boolean loadAttempted; private boolean loadAttempted;
private boolean isAvailable; private boolean isAvailable;
/** @param libraries The names of the libraries to load. */ /**
* @param libraries The names of the libraries to load.
*/
public LibraryLoader(String... libraries) { public LibraryLoader(String... libraries) {
nativeLibraries = libraries; nativeLibraries = libraries;
} }
......
...@@ -82,7 +82,9 @@ public final class Log { ...@@ -82,7 +82,9 @@ public final class Log {
Log.logStackTraces = logStackTraces; Log.logStackTraces = logStackTraces;
} }
/** @see android.util.Log#d(String, String) */ /**
* @see android.util.Log#d(String, String)
*/
@Pure @Pure
public static void d(@Size(max = 23) String tag, String message) { public static void d(@Size(max = 23) String tag, String message) {
if (logLevel == LOG_LEVEL_ALL) { if (logLevel == LOG_LEVEL_ALL) {
...@@ -90,13 +92,17 @@ public final class Log { ...@@ -90,13 +92,17 @@ public final class Log {
} }
} }
/** @see android.util.Log#d(String, String, Throwable) */ /**
* @see android.util.Log#d(String, String, Throwable)
*/
@Pure @Pure
public static void d(@Size(max = 23) String tag, String message, @Nullable Throwable throwable) { public static void d(@Size(max = 23) String tag, String message, @Nullable Throwable throwable) {
d(tag, appendThrowableString(message, throwable)); d(tag, appendThrowableString(message, throwable));
} }
/** @see android.util.Log#i(String, String) */ /**
* @see android.util.Log#i(String, String)
*/
@Pure @Pure
public static void i(@Size(max = 23) String tag, String message) { public static void i(@Size(max = 23) String tag, String message) {
if (logLevel <= LOG_LEVEL_INFO) { if (logLevel <= LOG_LEVEL_INFO) {
...@@ -104,13 +110,17 @@ public final class Log { ...@@ -104,13 +110,17 @@ public final class Log {
} }
} }
/** @see android.util.Log#i(String, String, Throwable) */ /**
* @see android.util.Log#i(String, String, Throwable)
*/
@Pure @Pure
public static void i(@Size(max = 23) String tag, String message, @Nullable Throwable throwable) { public static void i(@Size(max = 23) String tag, String message, @Nullable Throwable throwable) {
i(tag, appendThrowableString(message, throwable)); i(tag, appendThrowableString(message, throwable));
} }
/** @see android.util.Log#w(String, String) */ /**
* @see android.util.Log#w(String, String)
*/
@Pure @Pure
public static void w(@Size(max = 23) String tag, String message) { public static void w(@Size(max = 23) String tag, String message) {
if (logLevel <= LOG_LEVEL_WARNING) { if (logLevel <= LOG_LEVEL_WARNING) {
...@@ -118,13 +128,17 @@ public final class Log { ...@@ -118,13 +128,17 @@ public final class Log {
} }
} }
/** @see android.util.Log#w(String, String, Throwable) */ /**
* @see android.util.Log#w(String, String, Throwable)
*/
@Pure @Pure
public static void w(@Size(max = 23) String tag, String message, @Nullable Throwable throwable) { public static void w(@Size(max = 23) String tag, String message, @Nullable Throwable throwable) {
w(tag, appendThrowableString(message, throwable)); w(tag, appendThrowableString(message, throwable));
} }
/** @see android.util.Log#e(String, String) */ /**
* @see android.util.Log#e(String, String)
*/
@Pure @Pure
public static void e(@Size(max = 23) String tag, String message) { public static void e(@Size(max = 23) String tag, String message) {
if (logLevel <= LOG_LEVEL_ERROR) { if (logLevel <= LOG_LEVEL_ERROR) {
...@@ -132,7 +146,9 @@ public final class Log { ...@@ -132,7 +146,9 @@ public final class Log {
} }
} }
/** @see android.util.Log#e(String, String, Throwable) */ /**
* @see android.util.Log#e(String, String, Throwable)
*/
@Pure @Pure
public static void e(@Size(max = 23) String tag, String message, @Nullable Throwable throwable) { public static void e(@Size(max = 23) String tag, String message, @Nullable Throwable throwable) {
e(tag, appendThrowableString(message, throwable)); e(tag, appendThrowableString(message, throwable));
......
...@@ -30,7 +30,9 @@ public final class LongArray { ...@@ -30,7 +30,9 @@ public final class LongArray {
this(DEFAULT_INITIAL_CAPACITY); this(DEFAULT_INITIAL_CAPACITY);
} }
/** @param initialCapacity The initial capacity of the array. */ /**
* @param initialCapacity The initial capacity of the array.
*/
public LongArray(int initialCapacity) { public LongArray(int initialCapacity) {
values = new long[initialCapacity]; values = new long[initialCapacity];
} }
......
...@@ -47,6 +47,19 @@ public final class MediaFormatUtil { ...@@ -47,6 +47,19 @@ public final class MediaFormatUtil {
// The constant value must not be changed, because it's also set by the framework MediaParser API. // The constant value must not be changed, because it's also set by the framework MediaParser API.
public static final String KEY_PCM_ENCODING_EXTENDED = "exo-pcm-encoding-int"; public static final String KEY_PCM_ENCODING_EXTENDED = "exo-pcm-encoding-int";
/**
* The {@link MediaFormat} key for the maximum bitrate in bits per second.
*
* <p>The associated value is an integer.
*
* <p>The key string constant is the same as {@code MediaFormat#KEY_MAX_BITRATE}. Values for it
* are already returned by the framework MediaExtractor; the key is a hidden field in {@code
* MediaFormat} though, which is why it's being replicated here.
*/
// The constant value must not be changed, because it's also set by the framework MediaParser and
// MediaExtractor APIs.
public static final String KEY_MAX_BIT_RATE = "max-bitrate";
private static final int MAX_POWER_OF_TWO_INT = 1 << 30; private static final int MAX_POWER_OF_TWO_INT = 1 << 30;
/** /**
...@@ -63,6 +76,7 @@ public final class MediaFormatUtil { ...@@ -63,6 +76,7 @@ public final class MediaFormatUtil {
public static MediaFormat createMediaFormatFromFormat(Format format) { public static MediaFormat createMediaFormatFromFormat(Format format) {
MediaFormat result = new MediaFormat(); MediaFormat result = new MediaFormat();
maybeSetInteger(result, MediaFormat.KEY_BIT_RATE, format.bitrate); maybeSetInteger(result, MediaFormat.KEY_BIT_RATE, format.bitrate);
maybeSetInteger(result, KEY_MAX_BIT_RATE, format.peakBitrate);
maybeSetInteger(result, MediaFormat.KEY_CHANNEL_COUNT, format.channelCount); maybeSetInteger(result, MediaFormat.KEY_CHANNEL_COUNT, format.channelCount);
maybeSetColorInfo(result, format.colorInfo); maybeSetColorInfo(result, format.colorInfo);
......
...@@ -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);
}
} }
} }
} }
...@@ -54,17 +54,29 @@ public final class NotificationUtil { ...@@ -54,17 +54,29 @@ public final class NotificationUtil {
IMPORTANCE_HIGH IMPORTANCE_HIGH
}) })
public @interface Importance {} public @interface Importance {}
/** @see NotificationManager#IMPORTANCE_UNSPECIFIED */ /**
* @see NotificationManager#IMPORTANCE_UNSPECIFIED
*/
public static final int IMPORTANCE_UNSPECIFIED = NotificationManager.IMPORTANCE_UNSPECIFIED; public static final int IMPORTANCE_UNSPECIFIED = NotificationManager.IMPORTANCE_UNSPECIFIED;
/** @see NotificationManager#IMPORTANCE_NONE */ /**
* @see NotificationManager#IMPORTANCE_NONE
*/
public static final int IMPORTANCE_NONE = NotificationManager.IMPORTANCE_NONE; public static final int IMPORTANCE_NONE = NotificationManager.IMPORTANCE_NONE;
/** @see NotificationManager#IMPORTANCE_MIN */ /**
* @see NotificationManager#IMPORTANCE_MIN
*/
public static final int IMPORTANCE_MIN = NotificationManager.IMPORTANCE_MIN; public static final int IMPORTANCE_MIN = NotificationManager.IMPORTANCE_MIN;
/** @see NotificationManager#IMPORTANCE_LOW */ /**
* @see NotificationManager#IMPORTANCE_LOW
*/
public static final int IMPORTANCE_LOW = NotificationManager.IMPORTANCE_LOW; public static final int IMPORTANCE_LOW = NotificationManager.IMPORTANCE_LOW;
/** @see NotificationManager#IMPORTANCE_DEFAULT */ /**
* @see NotificationManager#IMPORTANCE_DEFAULT
*/
public static final int IMPORTANCE_DEFAULT = NotificationManager.IMPORTANCE_DEFAULT; public static final int IMPORTANCE_DEFAULT = NotificationManager.IMPORTANCE_DEFAULT;
/** @see NotificationManager#IMPORTANCE_HIGH */ /**
* @see NotificationManager#IMPORTANCE_HIGH
*/
public static final int IMPORTANCE_HIGH = NotificationManager.IMPORTANCE_HIGH; public static final int IMPORTANCE_HIGH = NotificationManager.IMPORTANCE_HIGH;
/** /**
......
...@@ -47,8 +47,27 @@ import java.lang.annotation.Target; ...@@ -47,8 +47,27 @@ import java.lang.annotation.Target;
* Android Studio, in order to alert developers to the risk of breaking changes. * Android Studio, in order to alert developers to the risk of breaking changes.
* *
* <p>Individual usage sites can be opted-in to suppress the lint error by using the {@link * <p>Individual usage sites can be opted-in to suppress the lint error by using the {@link
* androidx.annotation.OptIn} annotation: {@code @androidx.annotation.OptIn(markerClass = * androidx.annotation.OptIn} annotation.
* androidx.media3.common.util.UnstableApi.class)}. *
* <p>In Java:
*
* <pre>{@code
* import androidx.annotation.OptIn;
* import androidx.media3.common.util.UnstableApi;
* ...
* @OptIn(markerClass = UnstableApi.class)
* private void methodUsingUnstableApis() { ... }
* }</pre>
*
* <p>In Kotlin:
*
* <pre>{@code
* import androidx.annotation.OptIn
* import androidx.media3.common.util.UnstableApi
* ...
* @OptIn(UnstableApi::class)
* private fun methodUsingUnstableApis() { ... }
* }</pre>
* *
* <p>Whole projects can be opted-in by suppressing the specific lint error in their <a * <p>Whole projects can be opted-in by suppressing the specific lint error in their <a
* href="https://developer.android.com/studio/write/lint#pref">{@code lint.xml} file</a>: * href="https://developer.android.com/studio/write/lint#pref">{@code lint.xml} file</a>:
......
...@@ -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();
......
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