Commit f0d7d963 by manisha_jajoo Committed by GitHub

Merge branch 'main' into rtp_opus

parents 3a87039b f75710be
Showing with 1087 additions and 949 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-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
......@@ -94,15 +94,13 @@ to prevent build errors.
Cloning the repository and depending on the modules locally is required when
using some libraries. It's also a suitable approach if you want to make local
changes, or if you want to use the main branch.
changes, or if you want to use the `main` branch.
First, clone the repository into a local directory and checkout the desired
branch:
First, clone the repository into a local directory:
```sh
git clone https://github.com/androidx/media.git
cd media
git checkout main
```
Next, add the following to your project's `settings.gradle` file, replacing
......@@ -129,7 +127,7 @@ implementation project(':media-lib-ui')
Development work happens on the `main` branch. Pull requests should normally be
made to this branch.
We plan to add a release branch soon.
The `release` branch holds the most recent stable release.
#### Using Android Studio
......
......@@ -12,8 +12,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.
project.ext {
releaseVersion = '1.0.0-alpha01'
releaseVersionCode = 1000000
releaseVersion = '1.0.0-alpha03'
releaseVersionCode = 1_000_000_0_03
minSdkVersion = 16
appTargetSdkVersion = 29
// Upgrading this requires [Internal ref: b/193254928] to be fixed, or some
......@@ -26,7 +26,7 @@ project.ext {
// https://cs.android.com/android/platform/superproject/+/master:external/guava/METADATA
guavaVersion = '31.0.1-android'
mockitoVersion = '3.12.4'
robolectricVersion = '4.6.1'
robolectricVersion = '4.8-alpha-1'
// Keep this in sync with Google's internal Checker Framework version.
checkerframeworkVersion = '3.13.0'
checkerframeworkCompatVersion = '2.5.5'
......
......@@ -87,3 +87,7 @@ include modulePrefix + 'test-data'
project(modulePrefix + 'test-data').projectDir = new File(rootDir, 'libraries/test_data')
include modulePrefix + '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')
......@@ -119,8 +119,8 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
// Run the shader program.
GlProgram program = checkNotNull(this.program);
program.setSamplerTexIdUniform("uTexSampler0", frameTexture, /* unit= */ 0);
program.setSamplerTexIdUniform("uTexSampler1", textures[0], /* unit= */ 1);
program.setSamplerTexIdUniform("uTexSampler0", frameTexture, /* texUnitIndex= */ 0);
program.setSamplerTexIdUniform("uTexSampler1", textures[0], /* texUnitIndex= */ 1);
program.setFloatUniform("uScaleX", bitmapScaleX);
program.setFloatUniform("uScaleY", bitmapScaleY);
program.setFloatsUniform("uTexTransform", transformMatrix);
......
......@@ -32,7 +32,6 @@ import androidx.media3.common.util.Util;
import androidx.media3.datasource.DataSource;
import androidx.media3.datasource.DefaultDataSource;
import androidx.media3.datasource.DefaultHttpDataSource;
import androidx.media3.datasource.HttpDataSource;
import androidx.media3.exoplayer.ExoPlayer;
import androidx.media3.exoplayer.dash.DashMediaSource;
import androidx.media3.exoplayer.drm.DefaultDrmSessionManager;
......@@ -144,7 +143,7 @@ public final class MainActivity extends Activity {
String drmScheme = Assertions.checkNotNull(intent.getStringExtra(DRM_SCHEME_EXTRA));
String drmLicenseUrl = Assertions.checkNotNull(intent.getStringExtra(DRM_LICENSE_URL_EXTRA));
UUID drmSchemeUuid = Assertions.checkNotNull(Util.getDrmUuid(drmScheme));
HttpDataSource.Factory licenseDataSourceFactory = new DefaultHttpDataSource.Factory();
DataSource.Factory licenseDataSourceFactory = new DefaultHttpDataSource.Factory();
HttpMediaDrmCallback drmCallback =
new HttpMediaDrmCallback(drmLicenseUrl, licenseDataSourceFactory);
drmSessionManager =
......
......@@ -20,6 +20,7 @@ import static androidx.media3.demo.main.DemoUtil.DOWNLOAD_NOTIFICATION_CHANNEL_I
import android.app.Notification;
import android.content.Context;
import androidx.annotation.Nullable;
import androidx.annotation.OptIn;
import androidx.media3.common.util.NotificationUtil;
import androidx.media3.common.util.Util;
import androidx.media3.exoplayer.offline.Download;
......@@ -32,6 +33,7 @@ import androidx.media3.exoplayer.scheduler.Scheduler;
import java.util.List;
/** A service for downloading media. */
@OptIn(markerClass = androidx.media3.common.util.UnstableApi.class)
public class DemoDownloadService extends DownloadService {
private static final int JOB_ID = 1;
......
......@@ -16,12 +16,12 @@
package androidx.media3.demo.main;
import android.content.Context;
import androidx.annotation.OptIn;
import androidx.media3.database.DatabaseProvider;
import androidx.media3.database.StandaloneDatabaseProvider;
import androidx.media3.datasource.DataSource;
import androidx.media3.datasource.DefaultDataSource;
import androidx.media3.datasource.DefaultHttpDataSource;
import androidx.media3.datasource.HttpDataSource;
import androidx.media3.datasource.cache.Cache;
import androidx.media3.datasource.cache.CacheDataSource;
import androidx.media3.datasource.cache.NoOpCacheEvictor;
......@@ -59,7 +59,7 @@ public final class DemoUtil {
private static final String DOWNLOAD_CONTENT_DIRECTORY = "downloads";
private static DataSource.@MonotonicNonNull Factory dataSourceFactory;
private static HttpDataSource.@MonotonicNonNull Factory httpDataSourceFactory;
private static DataSource.@MonotonicNonNull Factory httpDataSourceFactory;
private static @MonotonicNonNull DatabaseProvider databaseProvider;
private static @MonotonicNonNull File downloadDirectory;
private static @MonotonicNonNull Cache downloadCache;
......@@ -72,6 +72,7 @@ public final class DemoUtil {
return BuildConfig.USE_DECODER_EXTENSIONS;
}
@OptIn(markerClass = androidx.media3.common.util.UnstableApi.class)
public static RenderersFactory buildRenderersFactory(
Context context, boolean preferExtensionRenderer) {
@DefaultRenderersFactory.ExtensionRendererMode
......@@ -85,7 +86,7 @@ public final class DemoUtil {
.setExtensionRendererMode(extensionRendererMode);
}
public static synchronized HttpDataSource.Factory getHttpDataSourceFactory(Context context) {
public static synchronized DataSource.Factory getHttpDataSourceFactory(Context context) {
if (httpDataSourceFactory == null) {
if (USE_CRONET_FOR_NETWORKING) {
context = context.getApplicationContext();
......@@ -117,6 +118,7 @@ public final class DemoUtil {
return dataSourceFactory;
}
@OptIn(markerClass = androidx.media3.common.util.UnstableApi.class)
public static synchronized DownloadNotificationHelper getDownloadNotificationHelper(
Context context) {
if (downloadNotificationHelper == null) {
......@@ -136,6 +138,7 @@ public final class DemoUtil {
return downloadTracker;
}
@OptIn(markerClass = androidx.media3.common.util.UnstableApi.class)
private static synchronized Cache getDownloadCache(Context context) {
if (downloadCache == null) {
File downloadContentDirectory =
......@@ -147,6 +150,7 @@ public final class DemoUtil {
return downloadCache;
}
@OptIn(markerClass = androidx.media3.common.util.UnstableApi.class)
private static synchronized void ensureDownloadManagerInitialized(Context context) {
if (downloadManager == null) {
downloadManager =
......@@ -161,6 +165,7 @@ public final class DemoUtil {
}
}
@OptIn(markerClass = androidx.media3.common.util.UnstableApi.class)
private static synchronized DatabaseProvider getDatabaseProvider(Context context) {
if (databaseProvider == null) {
databaseProvider = new StandaloneDatabaseProvider(context);
......@@ -178,6 +183,7 @@ public final class DemoUtil {
return downloadDirectory;
}
@OptIn(markerClass = androidx.media3.common.util.UnstableApi.class)
private static CacheDataSource.Factory buildReadOnlyCacheDataSource(
DataSource.Factory upstreamFactory, Cache cache) {
return new CacheDataSource.Factory()
......
......@@ -15,8 +15,7 @@
*/
package androidx.media3.demo.main;
import static androidx.media3.common.util.Assertions.checkNotNull;
import static androidx.media3.common.util.Assertions.checkStateNotNull;
import static com.google.common.base.Preconditions.checkNotNull;
import android.content.Context;
import android.content.DialogInterface;
......@@ -24,16 +23,18 @@ import android.net.Uri;
import android.os.AsyncTask;
import android.widget.Toast;
import androidx.annotation.Nullable;
import androidx.annotation.OptIn;
import androidx.annotation.RequiresApi;
import androidx.fragment.app.FragmentManager;
import androidx.media3.common.DrmInitData;
import androidx.media3.common.Format;
import androidx.media3.common.MediaItem;
import androidx.media3.common.TrackGroup;
import androidx.media3.common.TrackGroupArray;
import androidx.media3.common.TrackSelectionParameters;
import androidx.media3.common.TracksInfo;
import androidx.media3.common.util.Log;
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.drm.DrmSession;
import androidx.media3.exoplayer.drm.DrmSessionEventListener;
......@@ -46,13 +47,14 @@ import androidx.media3.exoplayer.offline.DownloadIndex;
import androidx.media3.exoplayer.offline.DownloadManager;
import androidx.media3.exoplayer.offline.DownloadRequest;
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 java.io.IOException;
import java.util.HashMap;
import java.util.concurrent.CopyOnWriteArraySet;
/** Tracks media that has been downloaded. */
@OptIn(markerClass = androidx.media3.common.util.UnstableApi.class)
public class DownloadTracker {
/** Listens for changes in the tracked downloads. */
......@@ -65,31 +67,26 @@ public class DownloadTracker {
private static final String TAG = "DownloadTracker";
private final Context context;
private final HttpDataSource.Factory httpDataSourceFactory;
private final DataSource.Factory dataSourceFactory;
private final CopyOnWriteArraySet<Listener> listeners;
private final HashMap<Uri, Download> downloads;
private final DownloadIndex downloadIndex;
private final DefaultTrackSelector.Parameters trackSelectorParameters;
@Nullable private StartDownloadDialogHelper startDownloadDialogHelper;
public DownloadTracker(
Context context,
HttpDataSource.Factory httpDataSourceFactory,
DownloadManager downloadManager) {
Context context, DataSource.Factory dataSourceFactory, DownloadManager downloadManager) {
this.context = context.getApplicationContext();
this.httpDataSourceFactory = httpDataSourceFactory;
this.dataSourceFactory = dataSourceFactory;
listeners = new CopyOnWriteArraySet<>();
downloads = new HashMap<>();
downloadIndex = downloadManager.getDownloadIndex();
trackSelectorParameters = DownloadHelper.getDefaultTrackSelectorParameters(context);
downloadManager.addListener(new DownloadManagerListener());
loadDownloads();
}
public void addListener(Listener listener) {
checkNotNull(listener);
listeners.add(listener);
listeners.add(checkNotNull(listener));
}
public void removeListener(Listener listener) {
......@@ -120,8 +117,7 @@ public class DownloadTracker {
startDownloadDialogHelper =
new StartDownloadDialogHelper(
fragmentManager,
DownloadHelper.forMediaItem(
context, mediaItem, renderersFactory, httpDataSourceFactory),
DownloadHelper.forMediaItem(context, mediaItem, renderersFactory, dataSourceFactory),
mediaItem);
}
}
......@@ -159,7 +155,7 @@ public class DownloadTracker {
private final class StartDownloadDialogHelper
implements DownloadHelper.Callback,
DialogInterface.OnClickListener,
TrackSelectionDialog.TrackSelectionListener,
DialogInterface.OnDismissListener {
private final FragmentManager fragmentManager;
......@@ -167,7 +163,6 @@ public class DownloadTracker {
private final MediaItem mediaItem;
private TrackSelectionDialog trackSelectionDialog;
private MappedTrackInfo mappedTrackInfo;
private WidevineOfflineLicenseFetchTask widevineOfflineLicenseFetchTask;
@Nullable private byte[] keySetId;
......@@ -220,7 +215,7 @@ public class DownloadTracker {
new WidevineOfflineLicenseFetchTask(
format,
mediaItem.localConfiguration.drmConfiguration,
httpDataSourceFactory,
dataSourceFactory,
/* dialogHelper= */ this,
helper);
widevineOfflineLicenseFetchTask.execute();
......@@ -237,21 +232,13 @@ public class DownloadTracker {
Log.e(TAG, logMessage, e);
}
// DialogInterface.OnClickListener implementation.
// TrackSelectionListener implementation.
@Override
public void onClick(DialogInterface dialog, int which) {
public void onTracksSelected(TrackSelectionParameters trackSelectionParameters) {
for (int periodIndex = 0; periodIndex < downloadHelper.getPeriodCount(); periodIndex++) {
downloadHelper.clearTrackSelections(periodIndex);
for (int i = 0; i < mappedTrackInfo.getRendererCount(); i++) {
if (!trackSelectionDialog.getIsDisabled(/* rendererIndex= */ i)) {
downloadHelper.addTrackSelectionForSingleRenderer(
periodIndex,
/* rendererIndex= */ i,
trackSelectorParameters,
trackSelectionDialog.getOverrides(/* rendererIndex= */ i));
}
}
downloadHelper.addTrackSelection(periodIndex, trackSelectionParameters);
}
DownloadRequest downloadRequest = buildDownloadRequest();
if (downloadRequest.streamKeys.isEmpty()) {
......@@ -316,21 +303,21 @@ public class DownloadTracker {
return;
}
mappedTrackInfo = downloadHelper.getMappedTrackInfo(/* periodIndex= */ 0);
if (!TrackSelectionDialog.willHaveContent(mappedTrackInfo)) {
TracksInfo tracksInfo = downloadHelper.getTracksInfo(/* periodIndex= */ 0);
if (!TrackSelectionDialog.willHaveContent(tracksInfo)) {
Log.d(TAG, "No dialog content. Downloading entire stream.");
startDownload();
downloadHelper.release();
return;
}
trackSelectionDialog =
TrackSelectionDialog.createForMappedTrackInfoAndParameters(
TrackSelectionDialog.createForTracksInfoAndParameters(
/* titleId= */ R.string.exo_download_description,
mappedTrackInfo,
trackSelectorParameters,
tracksInfo,
DownloadHelper.getDefaultTrackSelectorParameters(context),
/* allowAdaptiveSelections= */ false,
/* allowMultipleOverrides= */ true,
/* onClickListener= */ this,
/* onTracksSelectedListener= */ this,
/* onDismissListener= */ this);
trackSelectionDialog.show(fragmentManager, /* tag= */ null);
}
......@@ -371,7 +358,7 @@ public class DownloadTracker {
private final Format format;
private final MediaItem.DrmConfiguration drmConfiguration;
private final HttpDataSource.Factory httpDataSourceFactory;
private final DataSource.Factory dataSourceFactory;
private final StartDownloadDialogHelper dialogHelper;
private final DownloadHelper downloadHelper;
......@@ -381,12 +368,12 @@ public class DownloadTracker {
public WidevineOfflineLicenseFetchTask(
Format format,
MediaItem.DrmConfiguration drmConfiguration,
HttpDataSource.Factory httpDataSourceFactory,
DataSource.Factory dataSourceFactory,
StartDownloadDialogHelper dialogHelper,
DownloadHelper downloadHelper) {
this.format = format;
this.drmConfiguration = drmConfiguration;
this.httpDataSourceFactory = httpDataSourceFactory;
this.dataSourceFactory = dataSourceFactory;
this.dialogHelper = dialogHelper;
this.downloadHelper = downloadHelper;
}
......@@ -397,7 +384,7 @@ public class DownloadTracker {
OfflineLicenseHelper.newWidevineInstance(
drmConfiguration.licenseUri.toString(),
drmConfiguration.forceDefaultLicenseUri,
httpDataSourceFactory,
dataSourceFactory,
drmConfiguration.licenseRequestHeaders,
new DrmSessionEventListener.EventDispatcher());
try {
......@@ -415,7 +402,7 @@ public class DownloadTracker {
if (drmSessionException != null) {
dialogHelper.onOfflineLicenseFetchedError(drmSessionException);
} else {
dialogHelper.onOfflineLicenseFetched(downloadHelper, checkStateNotNull(keySetId));
dialogHelper.onOfflineLicenseFetched(downloadHelper, checkNotNull(keySetId));
}
}
}
......
......@@ -15,8 +15,9 @@
*/
package androidx.media3.demo.main;
import static androidx.media3.common.util.Assertions.checkNotNull;
import static androidx.media3.common.util.Assertions.checkState;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import android.content.Intent;
import android.net.Uri;
......@@ -26,7 +27,6 @@ import androidx.media3.common.MediaItem;
import androidx.media3.common.MediaItem.ClippingConfiguration;
import androidx.media3.common.MediaItem.SubtitleConfiguration;
import androidx.media3.common.MediaMetadata;
import androidx.media3.common.util.Assertions;
import androidx.media3.common.util.Util;
import com.google.common.collect.ImmutableList;
import java.util.ArrayList;
......@@ -86,7 +86,7 @@ public class IntentUtil {
/** Populates the intent with the given list of {@link MediaItem media items}. */
public static void addToIntent(List<MediaItem> mediaItems, Intent intent) {
Assertions.checkArgument(!mediaItems.isEmpty());
checkArgument(!mediaItems.isEmpty());
if (mediaItems.size() == 1) {
MediaItem mediaItem = mediaItems.get(0);
MediaItem.LocalConfiguration localConfiguration = checkNotNull(mediaItem.localConfiguration);
......@@ -241,7 +241,7 @@ public class IntentUtil {
drmConfiguration.forcedSessionTrackTypes;
if (!forcedDrmSessionTrackTypes.isEmpty()) {
// Only video and audio together are supported.
Assertions.checkState(
checkState(
forcedDrmSessionTrackTypes.size() == 2
&& forcedDrmSessionTrackTypes.contains(C.TRACK_TYPE_VIDEO)
&& forcedDrmSessionTrackTypes.contains(C.TRACK_TYPE_AUDIO));
......
......@@ -17,6 +17,7 @@ package androidx.media3.demo.main;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Bundle;
import android.util.Pair;
import android.view.KeyEvent;
......@@ -27,6 +28,7 @@ import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.Nullable;
import androidx.annotation.OptIn;
import androidx.appcompat.app.AppCompatActivity;
import androidx.media3.common.AudioAttributes;
import androidx.media3.common.C;
......@@ -34,6 +36,7 @@ import androidx.media3.common.ErrorMessageProvider;
import androidx.media3.common.MediaItem;
import androidx.media3.common.PlaybackException;
import androidx.media3.common.Player;
import androidx.media3.common.TrackSelectionParameters;
import androidx.media3.common.TracksInfo;
import androidx.media3.common.util.Util;
import androidx.media3.datasource.DataSource;
......@@ -48,7 +51,6 @@ import androidx.media3.exoplayer.offline.DownloadRequest;
import androidx.media3.exoplayer.source.DefaultMediaSourceFactory;
import androidx.media3.exoplayer.source.MediaSource;
import androidx.media3.exoplayer.source.ads.AdsLoader;
import androidx.media3.exoplayer.trackselection.DefaultTrackSelector;
import androidx.media3.exoplayer.util.DebugTextViewHelper;
import androidx.media3.exoplayer.util.EventLogger;
import androidx.media3.ui.PlayerControlView;
......@@ -79,8 +81,7 @@ public class PlayerActivity extends AppCompatActivity
private Button selectTracksButton;
private DataSource.Factory dataSourceFactory;
private List<MediaItem> mediaItems;
private DefaultTrackSelector trackSelector;
private DefaultTrackSelector.Parameters trackSelectionParameters;
private TrackSelectionParameters trackSelectionParameters;
private DebugTextViewHelper debugViewHelper;
private TracksInfo lastSeenTracksInfo;
private boolean startAutoPlay;
......@@ -113,9 +114,8 @@ public class PlayerActivity extends AppCompatActivity
playerView.requestFocus();
if (savedInstanceState != null) {
// Restore as DefaultTrackSelector.Parameters in case ExoPlayer specific parameters were set.
trackSelectionParameters =
DefaultTrackSelector.Parameters.CREATOR.fromBundle(
TrackSelectionParameters.CREATOR.fromBundle(
savedInstanceState.getBundle(KEY_TRACK_SELECTION_PARAMETERS));
startAutoPlay = savedInstanceState.getBoolean(KEY_AUTO_PLAY);
startItemIndex = savedInstanceState.getInt(KEY_ITEM_INDEX);
......@@ -127,8 +127,7 @@ public class PlayerActivity extends AppCompatActivity
adsLoaderStateBundle);
}
} else {
trackSelectionParameters =
new DefaultTrackSelector.ParametersBuilder(/* context= */ this).build();
trackSelectionParameters = new TrackSelectionParameters.Builder(/* context= */ this).build();
clearStartPosition();
}
}
......@@ -145,7 +144,7 @@ public class PlayerActivity extends AppCompatActivity
@Override
public void onStart() {
super.onStart();
if (Util.SDK_INT > 23) {
if (Build.VERSION.SDK_INT > 23) {
initializePlayer();
if (playerView != null) {
playerView.onResume();
......@@ -156,7 +155,7 @@ public class PlayerActivity extends AppCompatActivity
@Override
public void onResume() {
super.onResume();
if (Util.SDK_INT <= 23 || player == null) {
if (Build.VERSION.SDK_INT <= 23 || player == null) {
initializePlayer();
if (playerView != null) {
playerView.onResume();
......@@ -167,7 +166,7 @@ public class PlayerActivity extends AppCompatActivity
@Override
public void onPause() {
super.onPause();
if (Util.SDK_INT <= 23) {
if (Build.VERSION.SDK_INT <= 23) {
if (playerView != null) {
playerView.onPause();
}
......@@ -178,7 +177,7 @@ public class PlayerActivity extends AppCompatActivity
@Override
public void onStop() {
super.onStop();
if (Util.SDK_INT > 23) {
if (Build.VERSION.SDK_INT > 23) {
if (playerView != null) {
playerView.onPause();
}
......@@ -237,11 +236,11 @@ public class PlayerActivity extends AppCompatActivity
public void onClick(View view) {
if (view == selectTracksButton
&& !isShowingTrackSelectionDialog
&& TrackSelectionDialog.willHaveContent(trackSelector)) {
&& TrackSelectionDialog.willHaveContent(player)) {
isShowingTrackSelectionDialog = true;
TrackSelectionDialog trackSelectionDialog =
TrackSelectionDialog.createForTrackSelector(
trackSelector,
TrackSelectionDialog.createForPlayer(
player,
/* onDismissListener= */ dismissedDialog -> isShowingTrackSelectionDialog = false);
trackSelectionDialog.show(getSupportFragmentManager(), /* tag= */ null);
}
......@@ -277,13 +276,11 @@ public class PlayerActivity extends AppCompatActivity
RenderersFactory renderersFactory =
DemoUtil.buildRenderersFactory(/* context= */ this, preferExtensionDecoders);
trackSelector = new DefaultTrackSelector(/* context= */ this);
lastSeenTracksInfo = TracksInfo.EMPTY;
player =
new ExoPlayer.Builder(/* context= */ this)
.setRenderersFactory(renderersFactory)
.setMediaSourceFactory(createMediaSourceFactory())
.setTrackSelector(trackSelector)
.build();
player.setTrackSelectionParameters(trackSelectionParameters);
player.addListener(new PlayerEventListener());
......@@ -347,7 +344,7 @@ public class PlayerActivity extends AppCompatActivity
MediaItem.DrmConfiguration drmConfiguration = mediaItem.localConfiguration.drmConfiguration;
if (drmConfiguration != null) {
if (Util.SDK_INT < 18) {
if (Build.VERSION.SDK_INT < 18) {
showToast(R.string.error_drm_unsupported_before_api_18);
finish();
return Collections.emptyList();
......@@ -400,10 +397,7 @@ public class PlayerActivity extends AppCompatActivity
private void updateTrackSelectorParameters() {
if (player != null) {
// Until the demo app is fully migrated to TrackSelectionParameters, rely on ExoPlayer to use
// DefaultTrackSelector by default.
trackSelectionParameters =
(DefaultTrackSelector.Parameters) player.getTrackSelectionParameters();
trackSelectionParameters = player.getTrackSelectionParameters();
}
}
......@@ -424,8 +418,7 @@ public class PlayerActivity extends AppCompatActivity
// User controls
private void updateButtonVisibility() {
selectTracksButton.setEnabled(
player != null && TrackSelectionDialog.willHaveContent(trackSelector));
selectTracksButton.setEnabled(player != null && TrackSelectionDialog.willHaveContent(player));
}
private void showControls() {
......@@ -517,29 +510,32 @@ public class PlayerActivity extends AppCompatActivity
private static List<MediaItem> createMediaItems(Intent intent, DownloadTracker downloadTracker) {
List<MediaItem> mediaItems = new ArrayList<>();
for (MediaItem item : IntentUtil.createMediaItemsFromIntent(intent)) {
@Nullable
DownloadRequest downloadRequest =
downloadTracker.getDownloadRequest(item.localConfiguration.uri);
if (downloadRequest != null) {
MediaItem.Builder builder = item.buildUpon();
builder
.setMediaId(downloadRequest.id)
.setUri(downloadRequest.uri)
.setCustomCacheKey(downloadRequest.customCacheKey)
.setMimeType(downloadRequest.mimeType)
.setStreamKeys(downloadRequest.streamKeys);
@Nullable
MediaItem.DrmConfiguration drmConfiguration = item.localConfiguration.drmConfiguration;
if (drmConfiguration != null) {
builder.setDrmConfiguration(
drmConfiguration.buildUpon().setKeySetId(downloadRequest.keySetId).build());
}
mediaItems.add(builder.build());
} else {
mediaItems.add(item);
}
mediaItems.add(
maybeSetDownloadProperties(
item, downloadTracker.getDownloadRequest(item.localConfiguration.uri)));
}
return mediaItems;
}
@OptIn(markerClass = androidx.media3.common.util.UnstableApi.class)
private static MediaItem maybeSetDownloadProperties(
MediaItem item, @Nullable DownloadRequest downloadRequest) {
if (downloadRequest == null) {
return item;
}
MediaItem.Builder builder = item.buildUpon();
builder
.setMediaId(downloadRequest.id)
.setUri(downloadRequest.uri)
.setCustomCacheKey(downloadRequest.customCacheKey)
.setMimeType(downloadRequest.mimeType)
.setStreamKeys(downloadRequest.streamKeys);
@Nullable
MediaItem.DrmConfiguration drmConfiguration = item.localConfiguration.drmConfiguration;
if (drmConfiguration != null) {
builder.setDrmConfiguration(
drmConfiguration.buildUpon().setKeySetId(downloadRequest.keySetId).build());
}
return builder.build();
}
}
......@@ -15,9 +15,9 @@
*/
package androidx.media3.demo.main;
import static androidx.media3.common.util.Assertions.checkArgument;
import static androidx.media3.common.util.Assertions.checkNotNull;
import static androidx.media3.common.util.Assertions.checkState;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import android.content.Context;
import android.content.Intent;
......@@ -41,6 +41,7 @@ import android.widget.ImageButton;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.Nullable;
import androidx.annotation.OptIn;
import androidx.appcompat.app.AppCompatActivity;
import androidx.media3.common.MediaItem;
import androidx.media3.common.MediaItem.ClippingConfiguration;
......@@ -53,6 +54,7 @@ import androidx.media3.datasource.DataSourceUtil;
import androidx.media3.datasource.DataSpec;
import androidx.media3.exoplayer.RenderersFactory;
import androidx.media3.exoplayer.offline.DownloadService;
import com.google.common.base.Objects;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import java.io.IOException;
......@@ -116,8 +118,12 @@ public class SampleChooserActivity extends AppCompatActivity
useExtensionRenderers = DemoUtil.useExtensionRenderers();
downloadTracker = DemoUtil.getDownloadTracker(/* context= */ this);
loadSample();
startDownloadService();
}
// Start the download service if it should be running but it's not currently.
/** Start the download service if it should be running but it's not currently. */
@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
// action. Starting it in the background throws an exception if the app is in the background too
// (e.g. if device screen is locked).
......@@ -271,6 +277,7 @@ public class SampleChooserActivity extends AppCompatActivity
private boolean sawError;
@OptIn(markerClass = androidx.media3.common.util.UnstableApi.class)
@Override
protected List<PlaylistGroup> doInBackground(String... uris) {
List<PlaylistGroup> result = new ArrayList<>();
......@@ -481,7 +488,7 @@ public class SampleChooserActivity extends AppCompatActivity
private PlaylistGroup getGroup(String groupName, List<PlaylistGroup> groups) {
for (int i = 0; i < groups.size(); i++) {
if (Util.areEqual(groupName, groups.get(i).title)) {
if (Objects.equal(groupName, groups.get(i).title)) {
return groups.get(i);
}
}
......
......@@ -30,6 +30,7 @@ import android.widget.ListView
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import androidx.media3.common.MediaItem
import androidx.media3.common.Player
import androidx.media3.session.MediaBrowser
import androidx.media3.session.SessionToken
import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
......@@ -179,6 +180,9 @@ class PlayableFolderActivity : AppCompatActivity() {
returnConvertView.findViewById<TextView>(R.id.add_button).setOnClickListener {
val browser = this@PlayableFolderActivity.browser ?: return@setOnClickListener
browser.addMediaItem(mediaItem)
if (browser.playbackState == Player.STATE_IDLE) {
browser.prepare()
}
Snackbar.make(
findViewById<LinearLayout>(R.id.linear_layout),
getString(R.string.added_media_item_format, mediaItem.mediaMetadata.title),
......
......@@ -96,7 +96,6 @@ class PlaybackService : MediaLibraryService() {
val item = MediaItemTree.getItemFromTitle(mediaTitle) ?: MediaItemTree.getRandomItem()
player.setMediaItem(item)
player.prepare()
}
override fun onSetMediaUri(
......
......@@ -35,7 +35,6 @@ import androidx.media3.common.util.Util;
import androidx.media3.datasource.DataSource;
import androidx.media3.datasource.DefaultDataSource;
import androidx.media3.datasource.DefaultHttpDataSource;
import androidx.media3.datasource.HttpDataSource;
import androidx.media3.exoplayer.ExoPlayer;
import androidx.media3.exoplayer.dash.DashMediaSource;
import androidx.media3.exoplayer.drm.DefaultDrmSessionManager;
......@@ -189,7 +188,7 @@ public final class MainActivity extends Activity {
String drmScheme = Assertions.checkNotNull(intent.getStringExtra(DRM_SCHEME_EXTRA));
String drmLicenseUrl = Assertions.checkNotNull(intent.getStringExtra(DRM_LICENSE_URL_EXTRA));
UUID drmSchemeUuid = Assertions.checkNotNull(Util.getDrmUuid(drmScheme));
HttpDataSource.Factory licenseDataSourceFactory = new DefaultHttpDataSource.Factory();
DataSource.Factory licenseDataSourceFactory = new DefaultHttpDataSource.Factory();
HttpMediaDrmCallback drmCallback =
new HttpMediaDrmCallback(drmLicenseUrl, licenseDataSourceFactory);
drmSessionManager =
......
......@@ -50,12 +50,11 @@ public final class ConfigurationActivity extends AppCompatActivity {
public static final String AUDIO_MIME_TYPE = "audio_mime_type";
public static final String VIDEO_MIME_TYPE = "video_mime_type";
public static final String RESOLUTION_HEIGHT = "resolution_height";
public static final String TRANSLATE_X = "translate_x";
public static final String TRANSLATE_Y = "translate_y";
public static final String SCALE_X = "scale_x";
public static final String SCALE_Y = "scale_y";
public static final String ROTATE_DEGREES = "rotate_degrees";
public static final String ENABLE_FALLBACK = "enable_fallback";
public static final String ENABLE_REQUEST_SDR_TONE_MAPPING = "enable_request_sdr_tone_mapping";
public static final String ENABLE_HDR_EDITING = "enable_hdr_editing";
private static final String[] INPUT_URIS = {
"https://html5demos.com/assets/dizzy.mp4",
......@@ -63,6 +62,11 @@ public final class ConfigurationActivity extends AppCompatActivity {
"https://storage.googleapis.com/exoplayer-test-media-0/BigBuckBunny_320x180.mp4",
"https://html5demos.com/assets/dizzy.webm",
"https://storage.googleapis.com/exoplayer-test-media-1/mp4/portrait_4k60.mp4",
"https://storage.googleapis.com/exoplayer-test-media-1/mp4/8k24fps_4s.mp4",
"https://storage.googleapis.com/exoplayer-test-media-1/mp4/portrait_avc_aac.mp4",
"https://storage.googleapis.com/exoplayer-test-media-1/mp4/portrait_rotated_avc_aac.mp4",
"https://storage.googleapis.com/exoplayer-test-media-1/mp4/slow-motion/slowMotion_stopwatch_240fps_long.mp4",
"https://storage.googleapis.com/exoplayer-test-media-1/mp4/samsung-hdr-hdr10.mp4",
};
private static final String[] URI_DESCRIPTIONS = { // same order as INPUT_URIS
"MP4 with H264 video and AAC audio",
......@@ -70,6 +74,11 @@ public final class ConfigurationActivity extends AppCompatActivity {
"Long MP4 with H264 video and AAC audio",
"WebM with VP8 video and Vorbis audio",
"4K 60fps MP4 with H264 video and AAC audio (portrait, timestamps always increase)",
"8k 24fps MP4 with H265 video and AAC audio",
"MP4 with H264 video and AAC audio (portrait, H > W, 0\u00B0)",
"MP4 with H264 video and AAC audio (portrait, H < W, 90\u00B0)",
"SEF slow motion with 240 fps",
"MP4 with HDR (HDR10) H265 video (encoding may fail)",
};
private static final String SAME_AS_INPUT_OPTION = "same as input";
......@@ -81,10 +90,10 @@ public final class ConfigurationActivity extends AppCompatActivity {
private @MonotonicNonNull Spinner audioMimeSpinner;
private @MonotonicNonNull Spinner videoMimeSpinner;
private @MonotonicNonNull Spinner resolutionHeightSpinner;
private @MonotonicNonNull Spinner translateSpinner;
private @MonotonicNonNull Spinner scaleSpinner;
private @MonotonicNonNull Spinner rotateSpinner;
private @MonotonicNonNull CheckBox enableFallbackCheckBox;
private @MonotonicNonNull CheckBox enableRequestSdrToneMappingCheckBox;
private @MonotonicNonNull CheckBox enableHdrEditingCheckBox;
private int inputUriPosition;
......@@ -136,14 +145,6 @@ public final class ConfigurationActivity extends AppCompatActivity {
resolutionHeightAdapter.addAll(
SAME_AS_INPUT_OPTION, "144", "240", "360", "480", "720", "1080", "1440", "2160");
ArrayAdapter<String> translateAdapter =
new ArrayAdapter<>(/* context= */ this, R.layout.spinner_item);
translateAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
translateSpinner = findViewById(R.id.translate_spinner);
translateSpinner.setAdapter(translateAdapter);
translateAdapter.addAll(
SAME_AS_INPUT_OPTION, "-.1, -.1", "0, 0", ".5, 0", "0, .5", "1, 1", "1.9, 0", "0, 1.9");
ArrayAdapter<String> scaleAdapter =
new ArrayAdapter<>(/* context= */ this, R.layout.spinner_item);
scaleAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
......@@ -159,6 +160,9 @@ public final class ConfigurationActivity extends AppCompatActivity {
rotateAdapter.addAll(SAME_AS_INPUT_OPTION, "0", "10", "45", "60", "90", "180");
enableFallbackCheckBox = findViewById(R.id.enable_fallback_checkbox);
enableRequestSdrToneMappingCheckBox = findViewById(R.id.request_sdr_tone_mapping_checkbox);
enableRequestSdrToneMappingCheckBox.setEnabled(isRequestSdrToneMappingSupported());
findViewById(R.id.request_sdr_tone_mapping).setEnabled(isRequestSdrToneMappingSupported());
enableHdrEditingCheckBox = findViewById(R.id.hdr_editing_checkbox);
}
......@@ -185,10 +189,10 @@ public final class ConfigurationActivity extends AppCompatActivity {
"audioMimeSpinner",
"videoMimeSpinner",
"resolutionHeightSpinner",
"translateSpinner",
"scaleSpinner",
"rotateSpinner",
"enableFallbackCheckBox",
"enableRequestSdrToneMappingCheckBox",
"enableHdrEditingCheckBox"
})
private void startTransformation(View view) {
......@@ -209,13 +213,6 @@ public final class ConfigurationActivity extends AppCompatActivity {
if (!SAME_AS_INPUT_OPTION.equals(selectedResolutionHeight)) {
bundle.putInt(RESOLUTION_HEIGHT, Integer.parseInt(selectedResolutionHeight));
}
String selectedTranslate = String.valueOf(translateSpinner.getSelectedItem());
if (!SAME_AS_INPUT_OPTION.equals(selectedTranslate)) {
List<String> translateXY = Arrays.asList(selectedTranslate.split(", "));
checkState(translateXY.size() == 2);
bundle.putFloat(TRANSLATE_X, Float.parseFloat(translateXY.get(0)));
bundle.putFloat(TRANSLATE_Y, Float.parseFloat(translateXY.get(1)));
}
String selectedScale = String.valueOf(scaleSpinner.getSelectedItem());
if (!SAME_AS_INPUT_OPTION.equals(selectedScale)) {
List<String> scaleXY = Arrays.asList(selectedScale.split(", "));
......@@ -228,6 +225,8 @@ public final class ConfigurationActivity extends AppCompatActivity {
bundle.putFloat(ROTATE_DEGREES, Float.parseFloat(selectedRotate));
}
bundle.putBoolean(ENABLE_FALLBACK, enableFallbackCheckBox.isChecked());
bundle.putBoolean(
ENABLE_REQUEST_SDR_TONE_MAPPING, enableRequestSdrToneMappingCheckBox.isChecked());
bundle.putBoolean(ENABLE_HDR_EDITING, enableHdrEditingCheckBox.isChecked());
transformerIntent.putExtras(bundle);
......@@ -258,9 +257,9 @@ public final class ConfigurationActivity extends AppCompatActivity {
"audioMimeSpinner",
"videoMimeSpinner",
"resolutionHeightSpinner",
"translateSpinner",
"scaleSpinner",
"rotateSpinner",
"enableRequestSdrToneMappingCheckBox",
"enableHdrEditingCheckBox"
})
private void onRemoveAudio(View view) {
......@@ -277,9 +276,9 @@ public final class ConfigurationActivity extends AppCompatActivity {
"audioMimeSpinner",
"videoMimeSpinner",
"resolutionHeightSpinner",
"translateSpinner",
"scaleSpinner",
"rotateSpinner",
"enableRequestSdrToneMappingCheckBox",
"enableHdrEditingCheckBox"
})
private void onRemoveVideo(View view) {
......@@ -295,26 +294,32 @@ public final class ConfigurationActivity extends AppCompatActivity {
"audioMimeSpinner",
"videoMimeSpinner",
"resolutionHeightSpinner",
"translateSpinner",
"scaleSpinner",
"rotateSpinner",
"enableRequestSdrToneMappingCheckBox",
"enableHdrEditingCheckBox"
})
private void enableTrackSpecificOptions(boolean isAudioEnabled, boolean isVideoEnabled) {
audioMimeSpinner.setEnabled(isAudioEnabled);
videoMimeSpinner.setEnabled(isVideoEnabled);
resolutionHeightSpinner.setEnabled(isVideoEnabled);
translateSpinner.setEnabled(isVideoEnabled);
scaleSpinner.setEnabled(isVideoEnabled);
rotateSpinner.setEnabled(isVideoEnabled);
enableRequestSdrToneMappingCheckBox.setEnabled(
isRequestSdrToneMappingSupported() && isVideoEnabled);
enableHdrEditingCheckBox.setEnabled(isVideoEnabled);
findViewById(R.id.audio_mime_text_view).setEnabled(isAudioEnabled);
findViewById(R.id.video_mime_text_view).setEnabled(isVideoEnabled);
findViewById(R.id.resolution_height_text_view).setEnabled(isVideoEnabled);
findViewById(R.id.translate).setEnabled(isVideoEnabled);
findViewById(R.id.scale).setEnabled(isVideoEnabled);
findViewById(R.id.rotate).setEnabled(isVideoEnabled);
findViewById(R.id.request_sdr_tone_mapping)
.setEnabled(isRequestSdrToneMappingSupported() && isVideoEnabled);
findViewById(R.id.hdr_editing).setEnabled(isVideoEnabled);
}
private static boolean isRequestSdrToneMappingSupported() {
return Util.SDK_INT >= 31;
}
}
......@@ -21,7 +21,6 @@ import static androidx.media3.common.util.Assertions.checkNotNull;
import android.app.Activity;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.graphics.Matrix;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
......@@ -217,10 +216,17 @@ public final class TransformerActivity extends AppCompatActivity {
if (resolutionHeight != C.LENGTH_UNSET) {
requestBuilder.setResolution(resolutionHeight);
}
Matrix transformationMatrix = getTransformationMatrix(bundle);
if (!transformationMatrix.isIdentity()) {
requestBuilder.setTransformationMatrix(transformationMatrix);
}
float scaleX = bundle.getFloat(ConfigurationActivity.SCALE_X, /* defaultValue= */ 1);
float scaleY = bundle.getFloat(ConfigurationActivity.SCALE_Y, /* defaultValue= */ 1);
requestBuilder.setScale(scaleX, scaleY);
float rotateDegrees =
bundle.getFloat(ConfigurationActivity.ROTATE_DEGREES, /* defaultValue= */ 0);
requestBuilder.setRotationDegrees(rotateDegrees);
requestBuilder.setEnableRequestSdrToneMapping(
bundle.getBoolean(ConfigurationActivity.ENABLE_REQUEST_SDR_TONE_MAPPING));
requestBuilder.experimental_setEnableHdrEditing(
bundle.getBoolean(ConfigurationActivity.ENABLE_HDR_EDITING));
transformerBuilder
......@@ -251,27 +257,6 @@ public final class TransformerActivity extends AppCompatActivity {
.build();
}
private static Matrix getTransformationMatrix(Bundle bundle) {
Matrix transformationMatrix = new Matrix();
float translateX = bundle.getFloat(ConfigurationActivity.TRANSLATE_X, /* defaultValue= */ 0);
float translateY = bundle.getFloat(ConfigurationActivity.TRANSLATE_Y, /* defaultValue= */ 0);
// TODO(b/201293185): Implement an AdvancedFrameEditor to handle translation, as the current
// transformationMatrix is automatically adjusted to focus on the original pixels and
// effectively undo translations.
transformationMatrix.postTranslate(translateX, translateY);
float scaleX = bundle.getFloat(ConfigurationActivity.SCALE_X, /* defaultValue= */ 1);
float scaleY = bundle.getFloat(ConfigurationActivity.SCALE_Y, /* defaultValue= */ 1);
transformationMatrix.postScale(scaleX, scaleY);
float rotateDegrees =
bundle.getFloat(ConfigurationActivity.ROTATE_DEGREES, /* defaultValue= */ 0);
transformationMatrix.postRotate(rotateDegrees);
return transformationMatrix;
}
@RequiresNonNull({
"informationTextView",
"progressViewGroup",
......
......@@ -141,17 +141,6 @@
android:layout_weight="1"
android:gravity="center_vertical" >
<TextView
android:id="@+id/translate"
android:text="@string/translate"/>
<Spinner
android:id="@+id/translate_spinner"
android:layout_gravity="right|center_vertical"
android:gravity="right" />
</TableRow>
<TableRow
android:layout_weight="1"
android:gravity="center_vertical" >
<TextView
android:id="@+id/scale"
android:text="@string/scale"/>
<Spinner
......@@ -184,6 +173,16 @@
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:text="@string/hdr_editing" />
<CheckBox
......
......@@ -24,12 +24,12 @@
<string name="audio_mime" translatable="false">Output audio MIME type</string>
<string name="video_mime" translatable="false">Output video MIME type</string>
<string name="resolution_height" translatable="false">Output video resolution</string>
<string name="translate" translatable="false">Translate video</string>
<string name="scale" translatable="false">Scale video</string>
<string name="rotate" translatable="false">Rotate video (degrees)</string>
<string name="enable_fallback" translatable="false">Enable fallback</string>
<string name="transform" translatable="false">Transform</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="transform" translatable="false">Transform</string>
<string name="debug_preview" translatable="false">Debug preview:</string>
<string name="debug_preview_not_available" translatable="false">No debug preview available.</string>
<string name="transformation_started" translatable="false">Transformation started</string>
......
#Wed Mar 04 12:41:50 GMT 2020
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-all.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https://services.gradle.org/distributions/gradle-7.3.3-all.zip
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windowz variants
if not "%OS%" == "Windows_NT" goto win9xME_args
if "%@eval[2+2]" == "4" goto 4NT_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
goto execute
:4NT_args
@rem Get arguments from the 4NT Shell from JP Software
set CMD_LINE_ARGS=%$
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega
......@@ -34,17 +34,14 @@ import androidx.media3.common.DeviceInfo;
import androidx.media3.common.MediaItem;
import androidx.media3.common.MediaLibraryInfo;
import androidx.media3.common.MediaMetadata;
import androidx.media3.common.MimeTypes;
import androidx.media3.common.PlaybackException;
import androidx.media3.common.PlaybackParameters;
import androidx.media3.common.Player;
import androidx.media3.common.Timeline;
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.TracksInfo;
import androidx.media3.common.TracksInfo.TrackGroupInfo;
import androidx.media3.common.VideoSize;
import androidx.media3.common.text.Cue;
import androidx.media3.common.util.Assertions;
......@@ -68,7 +65,6 @@ import com.google.android.gms.common.api.PendingResult;
import com.google.android.gms.common.api.ResultCallback;
import com.google.common.collect.ImmutableList;
import java.util.List;
import org.checkerframework.checker.nullness.compatqual.NullableType;
import org.checkerframework.checker.nullness.qual.RequiresNonNull;
/**
......@@ -115,13 +111,7 @@ public final class CastPlayer extends BasePlayer {
private static final String TAG = "CastPlayer";
private static final int RENDERER_COUNT = 3;
private static final int RENDERER_INDEX_VIDEO = 0;
private static final int RENDERER_INDEX_AUDIO = 1;
private static final int RENDERER_INDEX_TEXT = 2;
private static final long PROGRESS_REPORT_PERIOD_MS = 1000;
private static final TrackSelectionArray EMPTY_TRACK_SELECTION_ARRAY =
new TrackSelectionArray(null, null, null);
private static final long[] EMPTY_TRACK_ID_ARRAY = new long[0];
private final CastContext castContext;
......@@ -146,8 +136,6 @@ public final class CastPlayer extends BasePlayer {
private final StateHolder<PlaybackParameters> playbackParameters;
@Nullable private RemoteMediaClient remoteMediaClient;
private CastTimeline currentTimeline;
private TrackGroupArray currentTrackGroups;
private TrackSelectionArray currentTrackSelection;
private TracksInfo currentTracksInfo;
private Commands availableCommands;
private @Player.State int playbackState;
......@@ -224,8 +212,6 @@ public final class CastPlayer extends BasePlayer {
playbackParameters = new StateHolder<>(PlaybackParameters.DEFAULT);
playbackState = STATE_IDLE;
currentTimeline = CastTimeline.EMPTY_CAST_TIMELINE;
currentTrackGroups = TrackGroupArray.EMPTY;
currentTrackSelection = EMPTY_TRACK_SELECTION_ARRAY;
currentTracksInfo = TracksInfo.EMPTY;
availableCommands = new Commands.Builder().addAll(PERMANENT_AVAILABLE_COMMANDS).build();
pendingSeekWindowIndex = C.INDEX_UNSET;
......@@ -558,16 +544,6 @@ public final class CastPlayer extends BasePlayer {
}
@Override
public TrackGroupArray getCurrentTrackGroups() {
return currentTrackGroups;
}
@Override
public TrackSelectionArray getCurrentTrackSelections() {
return currentTrackSelection;
}
@Override
public TracksInfo getCurrentTracksInfo() {
return currentTracksInfo;
}
......@@ -842,9 +818,6 @@ public final class CastPlayer extends BasePlayer {
}
if (updateTracksAndSelectionsAndNotifyIfChanged()) {
listeners.queueEvent(
Player.EVENT_TRACKS_CHANGED,
listener -> listener.onTracksChanged(currentTrackGroups, currentTrackSelection));
listeners.queueEvent(
Player.EVENT_TRACKS_CHANGED, listener -> listener.onTracksInfoChanged(currentTracksInfo));
}
updateAvailableCommandsAndNotifyIfChanged();
......@@ -1000,55 +973,33 @@ public final class CastPlayer extends BasePlayer {
return false;
}
MediaStatus mediaStatus = getMediaStatus();
MediaInfo mediaInfo = mediaStatus != null ? mediaStatus.getMediaInfo() : null;
@Nullable MediaStatus mediaStatus = getMediaStatus();
@Nullable MediaInfo mediaInfo = mediaStatus != null ? mediaStatus.getMediaInfo() : null;
@Nullable
List<MediaTrack> castMediaTracks = mediaInfo != null ? mediaInfo.getMediaTracks() : null;
if (castMediaTracks == null || castMediaTracks.isEmpty()) {
boolean hasChanged = !currentTrackGroups.isEmpty();
currentTrackGroups = TrackGroupArray.EMPTY;
currentTrackSelection = EMPTY_TRACK_SELECTION_ARRAY;
boolean hasChanged = !TracksInfo.EMPTY.equals(currentTracksInfo);
currentTracksInfo = TracksInfo.EMPTY;
return hasChanged;
}
long[] activeTrackIds = mediaStatus.getActiveTrackIds();
@Nullable long[] activeTrackIds = mediaStatus.getActiveTrackIds();
if (activeTrackIds == null) {
activeTrackIds = EMPTY_TRACK_ID_ARRAY;
}
TrackGroup[] trackGroups = new TrackGroup[castMediaTracks.size()];
@NullableType TrackSelection[] trackSelections = new TrackSelection[RENDERER_COUNT];
TracksInfo.TrackGroupInfo[] trackGroupInfos =
new TracksInfo.TrackGroupInfo[castMediaTracks.size()];
TrackGroupInfo[] trackGroupInfos = new TrackGroupInfo[castMediaTracks.size()];
for (int i = 0; i < castMediaTracks.size(); i++) {
MediaTrack mediaTrack = castMediaTracks.get(i);
trackGroups[i] =
TrackGroup trackGroup =
new TrackGroup(/* id= */ Integer.toString(i), CastUtils.mediaTrackToFormat(mediaTrack));
long id = mediaTrack.getId();
@C.TrackType int trackType = MimeTypes.getTrackType(mediaTrack.getContentType());
int rendererIndex = getRendererIndexForTrackType(trackType);
boolean supported = rendererIndex != C.INDEX_UNSET;
boolean selected =
isTrackActive(id, activeTrackIds) && supported && trackSelections[rendererIndex] == null;
if (selected) {
trackSelections[rendererIndex] = new CastTrackSelection(trackGroups[i]);
}
@C.FormatSupport
int[] trackSupport = new int[] {supported ? C.FORMAT_HANDLED : C.FORMAT_UNSUPPORTED_TYPE};
final boolean[] trackSelected = new boolean[] {selected};
@C.FormatSupport int[] trackSupport = new int[] {C.FORMAT_HANDLED};
boolean[] trackSelected = new boolean[] {isTrackActive(mediaTrack.getId(), activeTrackIds)};
trackGroupInfos[i] =
new TracksInfo.TrackGroupInfo(
trackGroups[i], /* adaptiveSupported= */ false, trackSupport, trackSelected);
new TrackGroupInfo(
trackGroup, /* adaptiveSupported= */ false, trackSupport, trackSelected);
}
TrackGroupArray newTrackGroups = new TrackGroupArray(trackGroups);
TrackSelectionArray newTrackSelections = new TrackSelectionArray(trackSelections);
TracksInfo newTracksInfo = new TracksInfo(ImmutableList.copyOf(trackGroupInfos));
if (!newTrackGroups.equals(currentTrackGroups)
|| !newTrackSelections.equals(currentTrackSelection)
|| !newTracksInfo.equals(currentTracksInfo)) {
currentTrackSelection = newTrackSelections;
currentTrackGroups = newTrackGroups;
if (!newTracksInfo.equals(currentTracksInfo)) {
currentTracksInfo = newTracksInfo;
return true;
}
......@@ -1307,14 +1258,6 @@ public final class CastPlayer extends BasePlayer {
return false;
}
private static int getRendererIndexForTrackType(@C.TrackType int trackType) {
return trackType == C.TRACK_TYPE_VIDEO
? RENDERER_INDEX_VIDEO
: trackType == C.TRACK_TYPE_AUDIO
? RENDERER_INDEX_AUDIO
: trackType == C.TRACK_TYPE_TEXT ? RENDERER_INDEX_TEXT : C.INDEX_UNSET;
}
private static int getCastRepeatMode(@RepeatMode int repeatMode) {
switch (repeatMode) {
case REPEAT_MODE_ONE:
......
/*
* 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;
}
}
/*
* 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);
}
}
......@@ -827,6 +827,36 @@ public final class AdPlaybackState implements Bundleable {
adsId, adGroups, adResumePositionUs, contentDurationUs, removedAdGroupCount);
}
/**
* Returns a copy of the ad playback state with the given ads ID.
*
* @param adsId The new ads ID.
* @param adPlaybackState The ad playback state to copy.
* @return The new ad playback state.
*/
public static AdPlaybackState fromAdPlaybackState(Object adsId, AdPlaybackState adPlaybackState) {
AdGroup[] adGroups =
new AdGroup[adPlaybackState.adGroupCount - adPlaybackState.removedAdGroupCount];
for (int i = 0; i < adGroups.length; i++) {
AdGroup adGroup = adPlaybackState.adGroups[i];
adGroups[i] =
new AdGroup(
adGroup.timeUs,
adGroup.count,
Arrays.copyOf(adGroup.states, adGroup.states.length),
Arrays.copyOf(adGroup.uris, adGroup.uris.length),
Arrays.copyOf(adGroup.durationsUs, adGroup.durationsUs.length),
adGroup.contentResumeOffsetUs,
adGroup.isServerSideInserted);
}
return new AdPlaybackState(
adsId,
adGroups,
adPlaybackState.adResumePositionUs,
adPlaybackState.contentDurationUs,
adPlaybackState.removedAdGroupCount);
}
@Override
public boolean equals(@Nullable Object o) {
if (this == o) {
......
......@@ -732,17 +732,17 @@ public final class C {
@Target({FIELD, METHOD, PARAMETER, LOCAL_VARIABLE, TYPE_USE})
@IntDef({TYPE_DASH, TYPE_SS, TYPE_HLS, TYPE_RTSP, TYPE_OTHER})
public @interface ContentType {}
/** Value returned by {@link Util#inferContentType(String)} for DASH manifests. */
/** Value returned by {@link Util#inferContentType} for DASH manifests. */
@UnstableApi public static final int TYPE_DASH = 0;
/** Value returned by {@link Util#inferContentType(String)} for Smooth Streaming manifests. */
/** Value returned by {@link Util#inferContentType} for Smooth Streaming manifests. */
@UnstableApi public static final int TYPE_SS = 1;
/** Value returned by {@link Util#inferContentType(String)} for HLS manifests. */
/** Value returned by {@link Util#inferContentType} for HLS manifests. */
@UnstableApi public static final int TYPE_HLS = 2;
/** Value returned by {@link Util#inferContentType(String)} for RTSP. */
/** Value returned by {@link Util#inferContentType} for RTSP. */
@UnstableApi public static final int TYPE_RTSP = 3;
/**
* Value returned by {@link Util#inferContentType(String)} for files other than DASH, HLS or
* Smooth Streaming manifests, or RTSP URIs.
* Value returned by {@link Util#inferContentType} for files other than DASH, HLS or Smooth
* Streaming manifests, or RTSP URIs.
*/
@UnstableApi public static final int TYPE_OTHER = 4;
......
......@@ -442,22 +442,6 @@ public class ForwardingPlayer implements Player {
player.release();
}
/** Calls {@link Player#getCurrentTrackGroups()} on the delegate and returns the result. */
@SuppressWarnings("deprecation") // Forwarding to deprecated method
@Deprecated
@Override
public TrackGroupArray getCurrentTrackGroups() {
return player.getCurrentTrackGroups();
}
/** Calls {@link Player#getCurrentTrackSelections()} on the delegate and returns the result. */
@SuppressWarnings("deprecation") // Forwarding to deprecated method
@Deprecated
@Override
public TrackSelectionArray getCurrentTrackSelections() {
return player.getCurrentTrackSelections();
}
/** Calls {@link Player#getCurrentTracksInfo()} on the delegate and returns the result. */
@Override
public TracksInfo getCurrentTracksInfo() {
......@@ -847,12 +831,6 @@ public class ForwardingPlayer implements Player {
}
@Override
@SuppressWarnings("deprecation")
public void onTracksChanged(TrackGroupArray trackGroups, TrackSelectionArray trackSelections) {
listener.onTracksChanged(trackGroups, trackSelections);
}
@Override
public void onTracksInfoChanged(TracksInfo tracksInfo) {
listener.onTracksInfoChanged(tracksInfo);
}
......
......@@ -27,23 +27,27 @@ public final class MediaLibraryInfo {
/** A tag to use when logging library information. */
public static final String TAG = "AndroidXMedia3";
/** The version of the library expressed as a string, for example "1.2.3". */
/** The version of the library expressed as a string, for example "1.2.3" or "1.2.3-beta01". */
// Intentionally hardcoded. Do not derive from other constants (e.g. VERSION_INT) or vice versa.
public static final String VERSION = "1.0.0-alpha01";
public static final String VERSION = "1.0.0-alpha03";
/** The version of the library expressed as {@code TAG + "/" + VERSION}. */
// Intentionally hardcoded. Do not derive from other constants (e.g. VERSION) or vice versa.
public static final String VERSION_SLASHY = "AndroidXMedia3/1.0.0-alpha01";
public static final String VERSION_SLASHY = "AndroidXMedia3/1.0.0-alpha03";
/**
* The version of the library expressed as an integer, for example 1002003.
* The version of the library expressed as an integer, for example 1002003300.
*
* <p>Three digits are used for each component of {@link #VERSION}. For example "1.2.3" has the
* corresponding integer version 1002003 (001-002-003), and "123.45.6" has the corresponding
* integer version 123045006 (123-045-006).
* <p>Three digits are used for each of the first three components of {@link #VERSION}, then a
* single digit represents the cycle of this version: alpha (0), beta (1), rc (2) or stable (3).
* Finally two digits are used for the cycle number (always 00 for stable releases).
*
* <p>For example "1.2.3-alpha05" has the corresponding integer version 1002003005
* (001-002-003-0-05), and "123.45.6" has the corresponding integer version 123045006300
* (123-045-006-3-00).
*/
// Intentionally hardcoded. Do not derive from other constants (e.g. VERSION) or vice versa.
public static final int VERSION_INT = 1000000;
public static final int VERSION_INT = 1_000_000_0_03;
/** Whether the library was compiled with {@link Assertions} checks enabled. */
public static final boolean ASSERTIONS_ENABLED = true;
......
......@@ -108,11 +108,10 @@ public final class MimeTypes {
public static final String APPLICATION_MP4 = BASE_TYPE_APPLICATION + "/mp4";
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_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_ID3 = BASE_TYPE_APPLICATION + "/id3";
public static final String APPLICATION_CEA608 = BASE_TYPE_APPLICATION + "/cea-608";
......@@ -135,7 +134,7 @@ public final class MimeTypes {
@UnstableApi public static final String APPLICATION_EXIF = BASE_TYPE_APPLICATION + "/x-exif";
@UnstableApi public static final String APPLICATION_ICY = BASE_TYPE_APPLICATION + "/x-icy";
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
......
......@@ -676,24 +676,6 @@ public interface Player {
* <p>{@link #onEvents(Player, Events)} will also be called to report this event along with
* other events that happen in the same {@link Looper} message queue iteration.
*
* @param trackGroups The available tracks. Never null, but may be of length zero.
* @param trackSelections The selected tracks. Never null, but may contain null elements. A
* concrete implementation may include null elements if it has a fixed number of renderer
* components, wishes to report a TrackSelection for each of them, and has one or more
* renderer components that is not assigned any selected tracks.
* @deprecated Use {@link #onTracksInfoChanged(TracksInfo)} instead.
*/
@UnstableApi
@Deprecated
default void onTracksChanged(
TrackGroupArray trackGroups, TrackSelectionArray trackSelections) {}
/**
* Called when the available or selected tracks change.
*
* <p>{@link #onEvents(Player, Events)} will also be called to report this event along with
* other events that happen in the same {@link Looper} message queue iteration.
*
* @param tracksInfo The available tracks information. Never null, but may be of length zero.
*/
default void onTracksInfoChanged(TracksInfo tracksInfo) {}
......@@ -701,11 +683,12 @@ public interface Player {
/**
* Called when the combined {@link MediaMetadata} changes.
*
* <p>The provided {@link MediaMetadata} is a combination of the {@link MediaItem#mediaMetadata}
* and the static and dynamic metadata from the {@link TrackSelection#getFormat(int) track
* selections' formats} and {@link Listener#onMetadata(Metadata)}. If a field is populated in
* the {@link MediaItem#mediaMetadata}, it will be prioritised above the same field coming from
* static or dynamic metadata.
* <p>The provided {@link MediaMetadata} is a combination of the {@link MediaItem#mediaMetadata
* MediaItem metadata}, the static metadata in the media's {@link Format#metadata Format}, and
* any timed metadata that has been parsed from the media and output via {@link
* Listener#onMetadata(Metadata)}. If a field is populated in the {@link
* MediaItem#mediaMetadata}, it will be prioritised above the same field coming from static or
* timed metadata.
*
* <p>This method may be called multiple times in quick succession.
*
......@@ -2105,33 +2088,9 @@ public interface Player {
void release();
/**
* Returns the available track groups.
*
* @see Listener#onTracksChanged(TrackGroupArray, TrackSelectionArray)
* @deprecated Use {@link #getCurrentTracksInfo()}.
*/
@UnstableApi
@Deprecated
TrackGroupArray getCurrentTrackGroups();
/**
* Returns the current track selections.
*
* <p>A concrete implementation may include null elements if it has a fixed number of renderer
* components, wishes to report a TrackSelection for each of them, and has one or more renderer
* components that is not assigned any selected tracks.
*
* @see Listener#onTracksChanged(TrackGroupArray, TrackSelectionArray)
* @deprecated Use {@link #getCurrentTracksInfo()}.
*/
@UnstableApi
@Deprecated
TrackSelectionArray getCurrentTrackSelections();
/**
* Returns the available tracks, as well as the tracks' support, type, and selection status.
* Returns information about the current tracks.
*
* @see Listener#onTracksChanged(TrackGroupArray, TrackSelectionArray)
* @see Listener#onTracksInfoChanged(TracksInfo)
*/
TracksInfo getCurrentTracksInfo();
......@@ -2165,11 +2124,11 @@ public interface Player {
* Returns the current combined {@link MediaMetadata}, or {@link MediaMetadata#EMPTY} if not
* supported.
*
* <p>This {@link MediaMetadata} is a combination of the {@link MediaItem#mediaMetadata} and the
* static and dynamic metadata from the {@link TrackSelection#getFormat(int) track selections'
* formats} and {@link Listener#onMetadata(Metadata)}. If a field is populated in the {@link
* MediaItem#mediaMetadata}, it will be prioritised above the same field coming from static or
* dynamic metadata.
* <p>This {@link MediaMetadata} is a combination of the {@link MediaItem#mediaMetadata MediaItem
* metadata}, the static metadata in the media's {@link Format#metadata Format}, and any timed
* metadata that has been parsed from the media and output via {@link
* Listener#onMetadata(Metadata)}. If a field is populated in the {@link MediaItem#mediaMetadata},
* it will be prioritised above the same field coming from static or timed metadata.
*/
MediaMetadata getMediaMetadata();
......
......@@ -34,17 +34,31 @@ import java.lang.annotation.Target;
import java.util.Arrays;
import java.util.List;
/** Defines an immutable group of tracks identified by their format identity. */
/**
* An immutable group of tracks. 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.
*/
public final class TrackGroup implements Bundleable {
private static final String TAG = "TrackGroup";
/** The number of tracks in the group. */
public final int length;
@UnstableApi public final int length;
/** An identifier for the track group. */
public final String id;
@UnstableApi public final String id;
/** The type of tracks in the group. */
public final @C.TrackType int type;
@UnstableApi public final @C.TrackType int type;
private final Format[] formats;
......@@ -99,6 +113,7 @@ public final class TrackGroup implements Bundleable {
* @param index The index of the track.
* @return The track's format.
*/
@UnstableApi
public Format getFormat(int index) {
return formats[index];
}
......@@ -112,6 +127,7 @@ public final class TrackGroup implements Bundleable {
* @return The index of the track, or {@link C#INDEX_UNSET} if no such track exists.
*/
@SuppressWarnings("ReferenceEquality")
@UnstableApi
public int indexOf(Format format) {
for (int i = 0; i < formats.length; i++) {
if (format == formats[i]) {
......
......@@ -31,16 +31,21 @@ import java.lang.annotation.RetentionPolicy;
import java.util.List;
/**
* Forces the selection of {@link #trackIndices} for a {@link TrackGroup}.
* A track selection override, consisting of a {@link TrackGroup} and the indices of the tracks
* within the group that should be selected.
*
* <p>If multiple tracks in {@link #trackGroup} are overridden, as many as possible will be selected
* depending on the player capabilities.
* <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, no tracks from {@link #trackGroup} will be played. This is
* similar to {@link TrackSelectionParameters#disabledTrackTypes}, except it will only affect the
* playback of the associated {@link TrackGroup}. For example, if the only {@link
* C#TRACK_TYPE_VIDEO} {@link TrackGroup} is associated with no tracks, no video will play until the
* next video starts.
* <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 {
......@@ -60,14 +65,14 @@ public final class TrackSelectionOverride implements Bundleable {
private static final int FIELD_TRACK_GROUP = 0;
private static final int FIELD_TRACKS = 1;
/** Constructs an instance to force all tracks in {@code trackGroup} to be selected. */
public TrackSelectionOverride(TrackGroup trackGroup) {
this.trackGroup = trackGroup;
ImmutableList.Builder<Integer> builder = new ImmutableList.Builder<>();
for (int i = 0; i < trackGroup.length; i++) {
builder.add(i);
}
this.trackIndices = builder.build();
/**
* Constructs an instance to force {@code trackIndex} in {@code trackGroup} to be selected.
*
* @param trackGroup The {@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 trackGroup, int trackIndex) {
this(trackGroup, ImmutableList.of(trackIndex));
}
/**
......@@ -123,13 +128,9 @@ public final class TrackSelectionOverride implements Bundleable {
@UnstableApi
public static final Creator<TrackSelectionOverride> CREATOR =
bundle -> {
@Nullable Bundle trackGroupBundle = bundle.getBundle(keyForField(FIELD_TRACK_GROUP));
checkNotNull(trackGroupBundle); // Mandatory as there are no reasonable defaults.
Bundle trackGroupBundle = checkNotNull(bundle.getBundle(keyForField(FIELD_TRACK_GROUP)));
TrackGroup trackGroup = TrackGroup.CREATOR.fromBundle(trackGroupBundle);
@Nullable int[] tracks = bundle.getIntArray(keyForField(FIELD_TRACKS));
if (tracks == null) {
return new TrackSelectionOverride(trackGroup);
}
int[] tracks = checkNotNull(bundle.getIntArray(keyForField(FIELD_TRACKS)));
return new TrackSelectionOverride(trackGroup, Ints.asList(tracks));
};
......
......@@ -41,8 +41,8 @@ public final class TracksInfo implements Bundleable {
/**
* Information about a single group of tracks, including the underlying {@link TrackGroup}, the
* {@link C.TrackType type} of tracks it contains, and the level to which each track is supported
* by the player.
* level to which each track is supported by the player, and whether any of the tracks are
* selected.
*/
public static final class TrackGroupInfo implements Bundleable {
......@@ -55,26 +55,26 @@ public final class TracksInfo implements Bundleable {
private final boolean[] trackSelected;
/**
* Constructs a TrackGroupInfo.
* Constructs an instance.
*
* @param trackGroup The {@link TrackGroup} described.
* @param adaptiveSupported Whether adaptive selections containing more than one track in the
* {@code trackGroup} are supported.
* @param trackSupport The {@link C.FormatSupport} of each track in the {@code trackGroup}.
* @param tracksSelected Whether each track in the {@code trackGroup} is selected.
* @param trackGroup The underlying {@link TrackGroup}.
* @param adaptiveSupported Whether the player supports adaptive selections containing more than
* one track in the group.
* @param trackSupport The {@link C.FormatSupport} of each track in the group.
* @param trackSelected Whether each track in the {@code trackGroup} is selected.
*/
@UnstableApi
public TrackGroupInfo(
TrackGroup trackGroup,
boolean adaptiveSupported,
@C.FormatSupport int[] trackSupport,
boolean[] tracksSelected) {
boolean[] trackSelected) {
length = trackGroup.length;
checkArgument(length == trackSupport.length && length == tracksSelected.length);
checkArgument(length == trackSupport.length && length == trackSelected.length);
this.trackGroup = trackGroup;
this.adaptiveSupported = adaptiveSupported && length > 1;
this.trackSupport = trackSupport.clone();
this.trackSelected = tracksSelected.clone();
this.trackSelected = trackSelected.clone();
}
/** Returns the underlying {@link TrackGroup}. */
......@@ -266,11 +266,11 @@ public final class TracksInfo implements Bundleable {
}
}
private final ImmutableList<TrackGroupInfo> trackGroupInfos;
/** An {@code TrackInfo} that contains no tracks. */
@UnstableApi public static final TracksInfo EMPTY = new TracksInfo(ImmutableList.of());
private final ImmutableList<TrackGroupInfo> trackGroupInfos;
/**
* Constructs an instance.
*
......
......@@ -15,6 +15,8 @@
*/
package androidx.media3.common.util;
import static androidx.media3.common.util.Assertions.checkArgument;
import android.util.Pair;
import androidx.annotation.Nullable;
import androidx.media3.common.C;
......@@ -31,6 +33,12 @@ public final class CodecSpecificDataUtil {
private static final String[] HEVC_GENERAL_PROFILE_SPACE_STRINGS =
new String[] {"", "A", "B", "C"};
// MP4V-ES
private static final int VISUAL_OBJECT_LAYER = 1;
private static final int VISUAL_OBJECT_LAYER_START = 0x20;
private static final int EXTENDED_PAR = 0x0F;
private static final int RECTANGULAR = 0x00;
/**
* Parses an ALAC AudioSpecificConfig (i.e. an <a
* href="https://github.com/macosforge/alac/blob/master/ALACMagicCookieDescription.txt">ALACSpecificConfig</a>).
......@@ -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.
*
* @param profileIdc The encoding profile.
......
......@@ -147,8 +147,6 @@ public final class GlProgram {
* <p>Call this in the rendering loop to switch between different programs.
*/
public void use() {
// TODO(b/214975934): When multiple GL programs are supported by Transformer, make sure
// to call use() to switch between programs.
GLES20.glUseProgram(programId);
GlUtil.checkGlError();
}
......@@ -175,9 +173,16 @@ public final class GlProgram {
checkNotNull(attributeByName.get(name)).setBuffer(values, size);
}
/** Sets a texture sampler type uniform. */
public void setSamplerTexIdUniform(String name, int texId, int unit) {
checkNotNull(uniformByName.get(name)).setSamplerTexId(texId, unit);
/**
* Sets a texture sampler type uniform.
*
* @param name The uniform's name.
* @param texId The texture identifier.
* @param texUnitIndex The texture unit index. Use a different index (0, 1, 2, ...) for each
* texture sampler in the program.
*/
public void setSamplerTexIdUniform(String name, int texId, int texUnitIndex) {
checkNotNull(uniformByName.get(name)).setSamplerTexId(texId, texUnitIndex);
}
/** Sets a float type uniform. */
......@@ -322,7 +327,7 @@ public final class GlProgram {
private final float[] value;
private int texId;
private int unit;
private int texUnitIndex;
private Uniform(String name, int location, int type) {
this.name = name;
......@@ -335,11 +340,11 @@ public final class GlProgram {
* Configures {@link #bind()} to use the specified {@code texId} for this sampler uniform.
*
* @param texId The GL texture identifier from which to sample.
* @param unit The GL texture unit index.
* @param texUnitIndex The GL texture unit index.
*/
public void setSamplerTexId(int texId, int unit) {
public void setSamplerTexId(int texId, int texUnitIndex) {
this.texId = texId;
this.unit = unit;
this.texUnitIndex = texUnitIndex;
}
/** Configures {@link #bind()} to use the specified float {@code value} for this uniform. */
......@@ -382,7 +387,7 @@ public final class GlProgram {
if (texId == 0) {
throw new IllegalStateException("No call to setSamplerTexId() before bind.");
}
GLES20.glActiveTexture(GLES20.GL_TEXTURE0 + unit);
GLES20.glActiveTexture(GLES20.GL_TEXTURE0 + texUnitIndex);
if (type == GLES11Ext.GL_SAMPLER_EXTERNAL_OES || type == GL_SAMPLER_EXTERNAL_2D_Y2Y_EXT) {
GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, texId);
} else if (type == GLES20.GL_SAMPLER_2D) {
......@@ -390,7 +395,7 @@ public final class GlProgram {
} else {
throw new IllegalStateException("Unexpected uniform type: " + type);
}
GLES20.glUniform1i(location, unit);
GLES20.glUniform1i(location, texUnitIndex);
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR);
GLES20.glTexParameteri(
......
......@@ -119,7 +119,8 @@ public final class GlUtil {
/**
* Returns whether creating a GL context with {@value #EXTENSION_PROTECTED_CONTENT} is possible.
* If {@code true}, the device supports a protected output path for DRM content when using GL.
*
* <p>If {@code true}, the device supports a protected output path for DRM content when using GL.
*/
public static boolean isProtectedContentExtensionSupported(Context context) {
if (Util.SDK_INT < 24) {
......@@ -146,7 +147,11 @@ public final class GlUtil {
}
/**
* Returns whether creating a GL context with {@value #EXTENSION_SURFACELESS_CONTEXT} is possible.
* Returns whether the {@value #EXTENSION_SURFACELESS_CONTEXT} extension is supported.
*
* <p>This extension allows passing {@link EGL14#EGL_NO_SURFACE} for both the write and read
* surfaces in a call to {@link EGL14#eglMakeCurrent(EGLDisplay, EGLSurface, EGLSurface,
* EGLContext)}.
*/
public static boolean isSurfacelessContextExtensionSupported() {
if (Util.SDK_INT < 17) {
......@@ -207,6 +212,52 @@ public final class GlUtil {
}
/**
* Creates and focuses a new {@link EGLSurface} wrapping a 1x1 pixel buffer.
*
* @param eglContext The {@link EGLContext} to make current.
* @param eglDisplay The {@link EGLDisplay} to attach the surface to.
*/
@RequiresApi(17)
public static void focusPlaceholderEglSurface(EGLContext eglContext, EGLDisplay eglDisplay) {
int[] pbufferAttributes =
new int[] {
EGL14.EGL_WIDTH, /* width= */ 1,
EGL14.EGL_HEIGHT, /* height= */ 1,
EGL14.EGL_NONE
};
EGLSurface eglSurface =
Api17.createEglPbufferSurface(
eglDisplay, EGL_CONFIG_ATTRIBUTES_RGBA_8888, pbufferAttributes);
focusEglSurface(eglDisplay, eglContext, eglSurface, /* width= */ 1, /* height= */ 1);
}
/**
* Creates and focuses a new {@link EGLSurface} wrapping a 1x1 pixel buffer, for HDR rendering
* with Rec. 2020 color primaries and using the PQ transfer function.
*
* @param eglContext The {@link EGLContext} to make current.
* @param eglDisplay The {@link EGLDisplay} to attach the surface to.
*/
@RequiresApi(17)
public static void focusPlaceholderEglSurfaceBt2020Pq(
EGLContext eglContext, EGLDisplay eglDisplay) {
int[] pbufferAttributes =
new int[] {
EGL14.EGL_WIDTH,
/* width= */ 1,
EGL14.EGL_HEIGHT,
/* height= */ 1,
EGL_GL_COLORSPACE_KHR,
EGL_GL_COLORSPACE_BT2020_PQ_EXT,
EGL14.EGL_NONE
};
EGLSurface eglSurface =
Api17.createEglPbufferSurface(
eglDisplay, EGL_CONFIG_ATTRIBUTES_RGBA_1010102, pbufferAttributes);
focusEglSurface(eglDisplay, eglContext, eglSurface, /* width= */ 1, /* height= */ 1);
}
/**
* If there is an OpenGl error, logs the error and if {@link #glAssertionsEnabled} is true throws
* a {@link GlException}.
*/
......@@ -223,6 +274,30 @@ public final class GlUtil {
}
/**
* Asserts the texture size is valid.
*
* @param width The width for a texture.
* @param height The height for a texture.
* @throws GlException If the texture width or height is invalid.
*/
public static void assertValidTextureSize(int width, int height) {
// TODO(b/201293185): Consider handling adjustments for sizes > GL_MAX_TEXTURE_SIZE
// (ex. downscaling appropriately) in a FrameProcessor instead of asserting incorrect values.
// For valid GL sizes, see:
// https://www.khronos.org/registry/OpenGL-Refpages/es2.0/xhtml/glTexImage2D.xml
int[] maxTextureSizeBuffer = new int[1];
GLES20.glGetIntegerv(GLES20.GL_MAX_TEXTURE_SIZE, maxTextureSizeBuffer, 0);
int maxTextureSize = maxTextureSizeBuffer[0];
if (width < 0 || height < 0) {
throwGlException("width or height is less than 0");
}
if (width > maxTextureSize || height > maxTextureSize) {
throwGlException("width or height is greater than GL_MAX_TEXTURE_SIZE " + maxTextureSize);
}
}
/**
* Makes the specified {@code eglSurface} the render target, using a viewport of {@code width} by
* {@code height} pixels.
*/
......@@ -320,6 +395,7 @@ public final class GlUtil {
* @param height of the new texture in pixels
*/
public static int createTexture(int width, int height) {
assertValidTextureSize(width, height);
int texId = generateAndBindTexture(GLES20.GL_TEXTURE_2D);
ByteBuffer byteBuffer = ByteBuffer.allocateDirect(width * height * 4);
GLES20.glTexImage2D(
......@@ -345,6 +421,9 @@ public final class GlUtil {
* GLES11Ext#GL_TEXTURE_EXTERNAL_OES} for an external texture.
*/
private static int generateAndBindTexture(int textureTarget) {
checkEglException(
!Util.areEqual(EGL14.eglGetCurrentContext(), EGL14.EGL_NO_CONTEXT), "No current context");
int[] texId = new int[1];
GLES20.glGenTextures(/* n= */ 1, texId, /* offset= */ 0);
checkGlError();
......@@ -365,6 +444,9 @@ public final class GlUtil {
* @param texId The identifier of the texture to attach to the framebuffer.
*/
public static int createFboForTexture(int texId) {
checkEglException(
!Util.areEqual(EGL14.eglGetCurrentContext(), EGL14.EGL_NO_CONTEXT), "No current context");
int[] fboId = new int[1];
GLES20.glGenFramebuffers(/* n= */ 1, fboId, /* offset= */ 0);
checkGlError();
......@@ -377,9 +459,10 @@ public final class GlUtil {
}
/* package */ static void throwGlException(String errorMsg) {
Log.e(TAG, errorMsg);
if (glAssertionsEnabled) {
throw new GlException(errorMsg);
} else {
Log.e(TAG, errorMsg);
}
}
......@@ -389,6 +472,11 @@ public final class GlUtil {
}
}
private static void checkEglException(String errorMessage) {
int error = EGL14.eglGetError();
checkEglException(error == EGL14.EGL_SUCCESS, errorMessage + ", error code: " + error);
}
@RequiresApi(17)
private static final class Api17 {
private Api17() {}
......@@ -437,12 +525,28 @@ public final class GlUtil {
Object surface,
int[] configAttributes,
int[] windowSurfaceAttributes) {
return EGL14.eglCreateWindowSurface(
eglDisplay,
getEglConfig(eglDisplay, configAttributes),
surface,
windowSurfaceAttributes,
/* offset= */ 0);
EGLSurface eglSurface =
EGL14.eglCreateWindowSurface(
eglDisplay,
getEglConfig(eglDisplay, configAttributes),
surface,
windowSurfaceAttributes,
/* offset= */ 0);
checkEglException("Error creating surface");
return eglSurface;
}
@DoNotInline
public static EGLSurface createEglPbufferSurface(
EGLDisplay eglDisplay, int[] configAttributes, int[] pbufferAttributes) {
EGLSurface eglSurface =
EGL14.eglCreatePbufferSurface(
eglDisplay,
getEglConfig(eglDisplay, configAttributes),
pbufferAttributes,
/* offset= */ 0);
checkEglException("Error creating surface");
return eglSurface;
}
@DoNotInline
......@@ -458,8 +562,11 @@ public final class GlUtil {
if (boundFramebuffer[0] != framebuffer) {
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, framebuffer);
}
checkGlError();
EGL14.eglMakeCurrent(eglDisplay, eglSurface, eglSurface, eglContext);
checkEglException("Error making context current");
GLES20.glViewport(/* x= */ 0, /* y= */ 0, width, height);
checkGlError();
}
@DoNotInline
......@@ -470,19 +577,15 @@ public final class GlUtil {
}
EGL14.eglMakeCurrent(
eglDisplay, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_CONTEXT);
int error = EGL14.eglGetError();
checkEglException(error == EGL14.EGL_SUCCESS, "Error releasing context: " + error);
checkEglException("Error releasing context");
if (eglContext != null) {
EGL14.eglDestroyContext(eglDisplay, eglContext);
error = EGL14.eglGetError();
checkEglException(error == EGL14.EGL_SUCCESS, "Error destroying context: " + error);
checkEglException("Error destroying context");
}
EGL14.eglReleaseThread();
error = EGL14.eglGetError();
checkEglException(error == EGL14.EGL_SUCCESS, "Error releasing thread: " + error);
checkEglException("Error releasing thread");
EGL14.eglTerminate(eglDisplay);
error = EGL14.eglGetError();
checkEglException(error == EGL14.EGL_SUCCESS, "Error terminating display: " + error);
checkEglException("Error terminating display");
}
@DoNotInline
......
......@@ -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.
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;
/**
......@@ -63,6 +76,7 @@ public final class MediaFormatUtil {
public static MediaFormat createMediaFormatFromFormat(Format format) {
MediaFormat result = new MediaFormat();
maybeSetInteger(result, MediaFormat.KEY_BIT_RATE, format.bitrate);
maybeSetInteger(result, KEY_MAX_BIT_RATE, format.peakBitrate);
maybeSetInteger(result, MediaFormat.KEY_CHANNEL_COUNT, format.channelCount);
maybeSetColorInfo(result, format.colorInfo);
......
......@@ -47,8 +47,27 @@ import java.lang.annotation.Target;
* 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
* androidx.annotation.OptIn} annotation: {@code @androidx.annotation.OptIn(markerClass =
* androidx.media3.common.util.UnstableApi.class)}.
* androidx.annotation.OptIn} annotation.
*
* <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
* href="https://developer.android.com/studio/write/lint#pref">{@code lint.xml} file</a>:
......
......@@ -1122,6 +1122,25 @@ public final class Util {
}
/**
* Returns the maximum value in the given {@link SparseLongArray}.
*
* @param sparseLongArray The {@link SparseLongArray}.
* @return The maximum value.
* @throws NoSuchElementException If the array is empty.
*/
@RequiresApi(18)
public static long maxValue(SparseLongArray sparseLongArray) {
if (sparseLongArray.size() == 0) {
throw new NoSuchElementException();
}
long max = Long.MIN_VALUE;
for (int i = 0; i < sparseLongArray.size(); i++) {
max = max(max, sparseLongArray.valueAt(i));
}
return max;
}
/**
* Converts a time in microseconds to the corresponding time in milliseconds, preserving {@link
* C#TIME_UNSET} and {@link C#TIME_END_OF_SOURCE} values.
*
......@@ -1144,18 +1163,6 @@ public final class Util {
}
/**
* Converts a time in seconds to the corresponding time in microseconds.
*
* @param timeSec The time in seconds.
* @return The corresponding time in microseconds.
*/
public static long secToUs(double timeSec) {
return BigDecimal.valueOf(timeSec)
.multiply(BigDecimal.valueOf(C.MICROS_PER_SECOND))
.longValue();
}
/**
* Parses an xs:duration attribute value, returning the parsed duration in milliseconds.
*
* @param value The attribute value to decode.
......@@ -1897,10 +1904,10 @@ public final class Util {
/**
* Returns the MIME type corresponding to the given adaptive {@link ContentType}, or {@code null}
* if the content type is {@link C#TYPE_OTHER}.
* if the content type is not adaptive.
*/
@Nullable
public static String getAdaptiveMimeTypeForContentType(int contentType) {
public static String getAdaptiveMimeTypeForContentType(@ContentType int contentType) {
switch (contentType) {
case C.TYPE_DASH:
return MimeTypes.APPLICATION_MPD;
......@@ -1908,6 +1915,7 @@ public final class Util {
return MimeTypes.APPLICATION_M3U8;
case C.TYPE_SS:
return MimeTypes.APPLICATION_SS;
case C.TYPE_RTSP:
case C.TYPE_OTHER:
default:
return null;
......
......@@ -29,12 +29,12 @@ import org.junit.runner.RunWith;
public final class TrackSelectionOverrideTest {
@Test
public void newTrackSelectionOverride_withJustTrackGroup_selectsAllTracks() {
public void newTrackSelectionOverride_withOneTrack_selectsOneTrack() {
TrackSelectionOverride trackSelectionOverride =
new TrackSelectionOverride(newTrackGroupWithIds(1, 2));
new TrackSelectionOverride(newTrackGroupWithIds(1, 2), /* trackIndex= */ 1);
assertThat(trackSelectionOverride.trackGroup).isEqualTo(newTrackGroupWithIds(1, 2));
assertThat(trackSelectionOverride.trackIndices).containsExactly(0, 1).inOrder();
assertThat(trackSelectionOverride.trackIndices).containsExactly(1).inOrder();
}
@Test
......
......@@ -20,7 +20,6 @@ import static com.google.common.truth.Truth.assertThat;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import org.junit.Test;
import org.junit.runner.RunWith;
......@@ -57,6 +56,7 @@ public final class TrackSelectionParametersTest {
assertThat(parameters.preferredAudioMimeTypes).isEmpty();
assertThat(parameters.preferredTextLanguages).isEmpty();
assertThat(parameters.preferredTextRoleFlags).isEqualTo(0);
assertThat(parameters.ignoredTextSelectionFlags).isEqualTo(0);
assertThat(parameters.selectUndeterminedTextLanguage).isFalse();
// General
assertThat(parameters.forceLowestBitrate).isFalse();
......@@ -68,7 +68,8 @@ public final class TrackSelectionParametersTest {
@Test
public void parametersSet_fromDefault_isAsExpected() {
TrackSelectionOverride override1 =
new TrackSelectionOverride(new TrackGroup(new Format.Builder().build()));
new TrackSelectionOverride(
new TrackGroup(new Format.Builder().build()), /* trackIndex= */ 0);
TrackSelectionOverride override2 =
new TrackSelectionOverride(
new TrackGroup(
......@@ -98,18 +99,22 @@ public final class TrackSelectionParametersTest {
// Text
.setPreferredTextLanguages("de", "en")
.setPreferredTextRoleFlags(C.ROLE_FLAG_CAPTION)
.setIgnoredTextSelectionFlags(C.SELECTION_FLAG_AUTOSELECT)
.setSelectUndeterminedTextLanguage(true)
// General
.setForceLowestBitrate(false)
.setForceHighestSupportedBitrate(true)
.addOverride(new TrackSelectionOverride(new TrackGroup(new Format.Builder().build())))
.addOverride(
new TrackSelectionOverride(
new TrackGroup(new Format.Builder().build()), /* trackIndex= */ 0))
.addOverride(
new TrackSelectionOverride(
new TrackGroup(
new Format.Builder().setId(4).build(),
new Format.Builder().setId(5).build()),
/* trackIndices= */ ImmutableList.of(1)))
.setDisabledTrackTypes(ImmutableSet.of(C.TRACK_TYPE_AUDIO, C.TRACK_TYPE_TEXT))
.setTrackTypeDisabled(C.TRACK_TYPE_AUDIO, /* disabled= */ true)
.setTrackTypeDisabled(C.TRACK_TYPE_TEXT, /* disabled= */ true)
.build();
// Video
......@@ -138,6 +143,7 @@ public final class TrackSelectionParametersTest {
// Text
assertThat(parameters.preferredTextLanguages).containsExactly("de", "en").inOrder();
assertThat(parameters.preferredTextRoleFlags).isEqualTo(C.ROLE_FLAG_CAPTION);
assertThat(parameters.ignoredTextSelectionFlags).isEqualTo(C.SELECTION_FLAG_AUTOSELECT);
assertThat(parameters.selectUndeterminedTextLanguage).isTrue();
// General
assertThat(parameters.forceLowestBitrate).isFalse();
......@@ -202,8 +208,10 @@ public final class TrackSelectionParametersTest {
@Test
public void addOverride_onDifferentGroups_addsOverride() {
TrackSelectionOverride override1 = new TrackSelectionOverride(newTrackGroupWithIds(1));
TrackSelectionOverride override2 = new TrackSelectionOverride(newTrackGroupWithIds(2));
TrackSelectionOverride override1 =
new TrackSelectionOverride(newTrackGroupWithIds(1), /* trackIndex= */ 0);
TrackSelectionOverride override2 =
new TrackSelectionOverride(newTrackGroupWithIds(2), /* trackIndex= */ 0);
TrackSelectionParameters trackSelectionParameters =
new TrackSelectionParameters.Builder(getApplicationContext())
......@@ -234,8 +242,10 @@ public final class TrackSelectionParametersTest {
@Test
public void setOverrideForType_onSameType_replacesOverride() {
TrackSelectionOverride override1 = new TrackSelectionOverride(newTrackGroupWithIds(1));
TrackSelectionOverride override2 = new TrackSelectionOverride(newTrackGroupWithIds(2));
TrackSelectionOverride override1 =
new TrackSelectionOverride(newTrackGroupWithIds(1), /* trackIndex= */ 0);
TrackSelectionOverride override2 =
new TrackSelectionOverride(newTrackGroupWithIds(2), /* trackIndex= */ 0);
TrackSelectionParameters trackSelectionParameters =
new TrackSelectionParameters.Builder(getApplicationContext())
......@@ -248,8 +258,10 @@ public final class TrackSelectionParametersTest {
@Test
public void clearOverridesOfType_ofTypeAudio_removesAudioOverride() {
TrackSelectionOverride override1 = new TrackSelectionOverride(AAC_TRACK_GROUP);
TrackSelectionOverride override2 = new TrackSelectionOverride(newTrackGroupWithIds(1));
TrackSelectionOverride override1 =
new TrackSelectionOverride(AAC_TRACK_GROUP, /* trackIndex= */ 0);
TrackSelectionOverride override2 =
new TrackSelectionOverride(newTrackGroupWithIds(1), /* trackIndex= */ 0);
TrackSelectionParameters trackSelectionParameters =
new TrackSelectionParameters.Builder(getApplicationContext())
.addOverride(override1)
......@@ -262,8 +274,10 @@ public final class TrackSelectionParametersTest {
@Test
public void clearOverride_ofTypeGroup_removesOverride() {
TrackSelectionOverride override1 = new TrackSelectionOverride(AAC_TRACK_GROUP);
TrackSelectionOverride override2 = new TrackSelectionOverride(newTrackGroupWithIds(1));
TrackSelectionOverride override1 =
new TrackSelectionOverride(AAC_TRACK_GROUP, /* trackIndex= */ 0);
TrackSelectionOverride override2 =
new TrackSelectionOverride(newTrackGroupWithIds(1), /* trackIndex= */ 0);
TrackSelectionParameters trackSelectionParameters =
new TrackSelectionParameters.Builder(getApplicationContext())
.addOverride(override1)
......
......@@ -22,6 +22,7 @@ import static android.graphics.Color.argb;
import static android.graphics.Color.parseColor;
import static androidx.media3.common.util.ColorParser.parseTtmlColor;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertThrows;
import android.graphics.Color;
import androidx.test.ext.junit.runners.AndroidJUnit4;
......@@ -34,24 +35,26 @@ public final class ColorParserTest {
// Negative tests.
@Test(expected = IllegalArgumentException.class)
@Test
public void parseUnknownColor() {
ColorParser.parseTtmlColor("colorOfAnElectron");
assertThrows(
IllegalArgumentException.class, () -> ColorParser.parseTtmlColor("colorOfAnElectron"));
}
@Test(expected = IllegalArgumentException.class)
@Test
public void parseNull() {
ColorParser.parseTtmlColor(null);
assertThrows(IllegalArgumentException.class, () -> ColorParser.parseTtmlColor(null));
}
@Test(expected = IllegalArgumentException.class)
@Test
public void parseEmpty() {
ColorParser.parseTtmlColor("");
assertThrows(IllegalArgumentException.class, () -> ColorParser.parseTtmlColor(""));
}
@Test(expected = IllegalArgumentException.class)
@Test
public void rgbColorParsingRgbValuesNegative() {
ColorParser.parseTtmlColor("rgb(-4, 55, 209)");
assertThrows(
IllegalArgumentException.class, () -> ColorParser.parseTtmlColor("rgb(-4, 55, 209)"));
}
// Positive tests.
......
......@@ -21,6 +21,7 @@ import static androidx.media3.common.util.Util.escapeFileName;
import static androidx.media3.common.util.Util.getCodecsOfType;
import static androidx.media3.common.util.Util.getStringForTime;
import static androidx.media3.common.util.Util.gzip;
import static androidx.media3.common.util.Util.maxValue;
import static androidx.media3.common.util.Util.minValue;
import static androidx.media3.common.util.Util.parseXsDateTime;
import static androidx.media3.common.util.Util.parseXsDuration;
......@@ -748,6 +749,21 @@ public class UtilTest {
}
@Test
public void sparseLongArrayMaxValue_returnsMaxValue() {
SparseLongArray sparseLongArray = new SparseLongArray();
sparseLongArray.put(0, 2);
sparseLongArray.put(25, 10);
sparseLongArray.put(42, 1);
assertThat(maxValue(sparseLongArray)).isEqualTo(10);
}
@Test
public void sparseLongArrayMaxValue_emptyArray_throws() {
assertThrows(NoSuchElementException.class, () -> maxValue(new SparseLongArray()));
}
@Test
public void parseXsDuration_returnsParsedDurationInMillis() {
assertThat(parseXsDuration("PT150.279S")).isEqualTo(150279L);
assertThat(parseXsDuration("PT1.500S")).isEqualTo(1500L);
......
......@@ -48,6 +48,7 @@ public abstract class BaseDataSource implements DataSource {
this.listeners = new ArrayList<>(/* initialCapacity= */ 1);
}
@UnstableApi
@Override
public final void addTransferListener(TransferListener transferListener) {
checkNotNull(transferListener);
......
......@@ -26,13 +26,13 @@ import java.util.List;
import java.util.Map;
/** Reads data from URI-identified resources. */
@UnstableApi
public interface DataSource extends DataReader {
/** A factory for {@link DataSource} instances. */
interface Factory {
/** Creates a {@link DataSource} instance. */
@UnstableApi
DataSource createDataSource();
}
......@@ -41,6 +41,7 @@ public interface DataSource extends DataReader {
*
* @param transferListener A {@link TransferListener}.
*/
@UnstableApi
void addTransferListener(TransferListener transferListener);
/**
......@@ -72,6 +73,7 @@ public interface DataSource extends DataReader {
* unresolved. For all other requests, the value returned will be equal to the request's
* {@link DataSpec#length}.
*/
@UnstableApi
long open(DataSpec dataSpec) throws IOException;
/**
......@@ -82,6 +84,7 @@ public interface DataSource extends DataReader {
*
* @return The {@link Uri} from which data is being read, or null if the source is not open.
*/
@UnstableApi
@Nullable
Uri getUri();
......@@ -91,6 +94,7 @@ public interface DataSource extends DataReader {
*
* <p>Key look-up in the returned map is case-insensitive.
*/
@UnstableApi
default Map<String, List<String>> getResponseHeaders() {
return Collections.emptyMap();
}
......@@ -101,5 +105,6 @@ public interface DataSource extends DataReader {
*
* @throws IOException If an error occurs closing the source.
*/
@UnstableApi
void close() throws IOException;
}
......@@ -21,7 +21,6 @@ import androidx.media3.common.util.UnstableApi;
import java.io.IOException;
/** Used to specify reason of a DataSource error. */
@UnstableApi
public class DataSourceException extends IOException {
/**
......@@ -29,6 +28,7 @@ public class DataSourceException extends IOException {
* {@link #reason} is {@link PlaybackException#ERROR_CODE_IO_READ_POSITION_OUT_OF_RANGE} in its
* cause stack.
*/
@UnstableApi
public static boolean isCausedByPositionOutOfRange(IOException e) {
@Nullable Throwable cause = e;
while (cause != null) {
......@@ -49,7 +49,7 @@ public class DataSourceException extends IOException {
*
* @deprecated Use {@link PlaybackException#ERROR_CODE_IO_READ_POSITION_OUT_OF_RANGE}.
*/
@Deprecated
@UnstableApi @Deprecated
public static final int POSITION_OUT_OF_RANGE =
PlaybackException.ERROR_CODE_IO_READ_POSITION_OUT_OF_RANGE;
......@@ -65,6 +65,7 @@ public class DataSourceException extends IOException {
* @param reason Reason of the error, should be one of the {@code ERROR_CODE_IO_*} in {@link
* PlaybackException.ErrorCode}.
*/
@UnstableApi
public DataSourceException(@PlaybackException.ErrorCode int reason) {
this.reason = reason;
}
......@@ -76,6 +77,7 @@ public class DataSourceException extends IOException {
* @param reason Reason of the error, should be one of the {@code ERROR_CODE_IO_*} in {@link
* PlaybackException.ErrorCode}.
*/
@UnstableApi
public DataSourceException(@Nullable Throwable cause, @PlaybackException.ErrorCode int reason) {
super(cause);
this.reason = reason;
......@@ -88,6 +90,7 @@ public class DataSourceException extends IOException {
* @param reason Reason of the error, should be one of the {@code ERROR_CODE_IO_*} in {@link
* PlaybackException.ErrorCode}.
*/
@UnstableApi
public DataSourceException(@Nullable String message, @PlaybackException.ErrorCode int reason) {
super(message);
this.reason = reason;
......@@ -101,6 +104,7 @@ public class DataSourceException extends IOException {
* @param reason Reason of the error, should be one of the {@code ERROR_CODE_IO_*} in {@link
* PlaybackException.ErrorCode}.
*/
@UnstableApi
public DataSourceException(
@Nullable String message,
@Nullable Throwable cause,
......
......@@ -54,7 +54,6 @@ import java.util.Map;
* #DefaultDataSource(Context, DataSource)}.
* </ul>
*/
@UnstableApi
public final class DefaultDataSource implements DataSource {
/** {@link DataSource.Factory} for {@link DefaultDataSource} instances. */
......@@ -98,11 +97,13 @@ public final class DefaultDataSource implements DataSource {
* @param transferListener The listener that will be used.
* @return This factory.
*/
@UnstableApi
public Factory setTransferListener(@Nullable TransferListener transferListener) {
this.transferListener = transferListener;
return this;
}
@UnstableApi
@Override
public DefaultDataSource createDataSource() {
DefaultDataSource dataSource =
......@@ -144,6 +145,7 @@ public final class DefaultDataSource implements DataSource {
*
* @param context A context.
*/
@UnstableApi
public DefaultDataSource(Context context, boolean allowCrossProtocolRedirects) {
this(
context,
......@@ -162,6 +164,7 @@ public final class DefaultDataSource implements DataSource {
* @param allowCrossProtocolRedirects Whether cross-protocol redirects (i.e. redirects from HTTP
* to HTTPS and vice versa) are enabled when fetching remote data.
*/
@UnstableApi
public DefaultDataSource(
Context context, @Nullable String userAgent, boolean allowCrossProtocolRedirects) {
this(
......@@ -185,6 +188,7 @@ public final class DefaultDataSource implements DataSource {
* @param allowCrossProtocolRedirects Whether cross-protocol redirects (i.e. redirects from HTTP
* to HTTPS and vice versa) are enabled when fetching remote data.
*/
@UnstableApi
public DefaultDataSource(
Context context,
@Nullable String userAgent,
......@@ -209,12 +213,14 @@ public final class DefaultDataSource implements DataSource {
* @param baseDataSource A {@link DataSource} to use for URI schemes other than file, asset and
* content. This {@link DataSource} should normally support at least http(s).
*/
@UnstableApi
public DefaultDataSource(Context context, DataSource baseDataSource) {
this.context = context.getApplicationContext();
this.baseDataSource = Assertions.checkNotNull(baseDataSource);
transferListeners = new ArrayList<>();
}
@UnstableApi
@Override
public void addTransferListener(TransferListener transferListener) {
Assertions.checkNotNull(transferListener);
......@@ -229,6 +235,7 @@ public final class DefaultDataSource implements DataSource {
maybeAddListenerToDataSource(rawResourceDataSource, transferListener);
}
@UnstableApi
@Override
public long open(DataSpec dataSpec) throws IOException {
Assertions.checkState(dataSource == null);
......@@ -260,22 +267,26 @@ public final class DefaultDataSource implements DataSource {
return dataSource.open(dataSpec);
}
@UnstableApi
@Override
public int read(byte[] buffer, int offset, int length) throws IOException {
return Assertions.checkNotNull(dataSource).read(buffer, offset, length);
}
@UnstableApi
@Override
@Nullable
public Uri getUri() {
return dataSource == null ? null : dataSource.getUri();
}
@UnstableApi
@Override
public Map<String, List<String>> getResponseHeaders() {
return dataSource == null ? Collections.emptyMap() : dataSource.getResponseHeaders();
}
@UnstableApi
@Override
public void close() throws IOException {
if (dataSource != null) {
......
......@@ -60,7 +60,6 @@ import java.util.zip.GZIPInputStream;
* priority) the {@code dataSpec}, {@link #setRequestProperty} and the default properties that can
* be passed to {@link HttpDataSource.Factory#setDefaultRequestProperties(Map)}.
*/
@UnstableApi
public class DefaultHttpDataSource extends BaseDataSource implements HttpDataSource {
/** {@link DataSource.Factory} for {@link DefaultHttpDataSource} instances. */
......@@ -83,6 +82,7 @@ public class DefaultHttpDataSource extends BaseDataSource implements HttpDataSou
readTimeoutMs = DEFAULT_READ_TIMEOUT_MILLIS;
}
@UnstableApi
@Override
public final Factory setDefaultRequestProperties(Map<String, String> defaultRequestProperties) {
this.defaultRequestProperties.clearAndSet(defaultRequestProperties);
......@@ -99,6 +99,7 @@ public class DefaultHttpDataSource extends BaseDataSource implements HttpDataSou
* agent of the underlying platform.
* @return This factory.
*/
@UnstableApi
public Factory setUserAgent(@Nullable String userAgent) {
this.userAgent = userAgent;
return this;
......@@ -112,6 +113,7 @@ public class DefaultHttpDataSource extends BaseDataSource implements HttpDataSou
* @param connectTimeoutMs The connect timeout, in milliseconds, that will be used.
* @return This factory.
*/
@UnstableApi
public Factory setConnectTimeoutMs(int connectTimeoutMs) {
this.connectTimeoutMs = connectTimeoutMs;
return this;
......@@ -125,6 +127,7 @@ public class DefaultHttpDataSource extends BaseDataSource implements HttpDataSou
* @param readTimeoutMs The connect timeout, in milliseconds, that will be used.
* @return This factory.
*/
@UnstableApi
public Factory setReadTimeoutMs(int readTimeoutMs) {
this.readTimeoutMs = readTimeoutMs;
return this;
......@@ -138,6 +141,7 @@ public class DefaultHttpDataSource extends BaseDataSource implements HttpDataSou
* @param allowCrossProtocolRedirects Whether to allow cross protocol redirects.
* @return This factory.
*/
@UnstableApi
public Factory setAllowCrossProtocolRedirects(boolean allowCrossProtocolRedirects) {
this.allowCrossProtocolRedirects = allowCrossProtocolRedirects;
return this;
......@@ -154,6 +158,7 @@ public class DefaultHttpDataSource extends BaseDataSource implements HttpDataSou
* predicate that was previously set.
* @return This factory.
*/
@UnstableApi
public Factory setContentTypePredicate(@Nullable Predicate<String> contentTypePredicate) {
this.contentTypePredicate = contentTypePredicate;
return this;
......@@ -169,6 +174,7 @@ public class DefaultHttpDataSource extends BaseDataSource implements HttpDataSou
* @param transferListener The listener that will be used.
* @return This factory.
*/
@UnstableApi
public Factory setTransferListener(@Nullable TransferListener transferListener) {
this.transferListener = transferListener;
return this;
......@@ -178,11 +184,13 @@ public class DefaultHttpDataSource extends BaseDataSource implements HttpDataSou
* Sets whether we should keep the POST method and body when we have HTTP 302 redirects for a
* POST request.
*/
@UnstableApi
public Factory setKeepPostFor302Redirects(boolean keepPostFor302Redirects) {
this.keepPostFor302Redirects = keepPostFor302Redirects;
return this;
}
@UnstableApi
@Override
public DefaultHttpDataSource createDataSource() {
DefaultHttpDataSource dataSource =
......@@ -202,9 +210,9 @@ public class DefaultHttpDataSource extends BaseDataSource implements HttpDataSou
}
/** The default connection timeout, in milliseconds. */
public static final int DEFAULT_CONNECT_TIMEOUT_MILLIS = 8 * 1000;
@UnstableApi public static final int DEFAULT_CONNECT_TIMEOUT_MILLIS = 8 * 1000;
/** The default read timeout, in milliseconds. */
public static final int DEFAULT_READ_TIMEOUT_MILLIS = 8 * 1000;
@UnstableApi public static final int DEFAULT_READ_TIMEOUT_MILLIS = 8 * 1000;
private static final String TAG = "DefaultHttpDataSource";
private static final int MAX_REDIRECTS = 20; // Same limit as okhttp.
......@@ -232,6 +240,7 @@ public class DefaultHttpDataSource extends BaseDataSource implements HttpDataSou
/**
* @deprecated Use {@link DefaultHttpDataSource.Factory} instead.
*/
@UnstableApi
@SuppressWarnings("deprecation")
@Deprecated
public DefaultHttpDataSource() {
......@@ -241,6 +250,7 @@ public class DefaultHttpDataSource extends BaseDataSource implements HttpDataSou
/**
* @deprecated Use {@link DefaultHttpDataSource.Factory} instead.
*/
@UnstableApi
@SuppressWarnings("deprecation")
@Deprecated
public DefaultHttpDataSource(@Nullable String userAgent) {
......@@ -250,6 +260,7 @@ public class DefaultHttpDataSource extends BaseDataSource implements HttpDataSou
/**
* @deprecated Use {@link DefaultHttpDataSource.Factory} instead.
*/
@UnstableApi
@SuppressWarnings("deprecation")
@Deprecated
public DefaultHttpDataSource(
......@@ -265,6 +276,7 @@ public class DefaultHttpDataSource extends BaseDataSource implements HttpDataSou
/**
* @deprecated Use {@link DefaultHttpDataSource.Factory} instead.
*/
@UnstableApi
@Deprecated
public DefaultHttpDataSource(
@Nullable String userAgent,
......@@ -305,22 +317,26 @@ public class DefaultHttpDataSource extends BaseDataSource implements HttpDataSou
* @deprecated Use {@link DefaultHttpDataSource.Factory#setContentTypePredicate(Predicate)}
* instead.
*/
@UnstableApi
@Deprecated
public void setContentTypePredicate(@Nullable Predicate<String> contentTypePredicate) {
this.contentTypePredicate = contentTypePredicate;
}
@UnstableApi
@Override
@Nullable
public Uri getUri() {
return connection == null ? null : Uri.parse(connection.getURL().toString());
}
@UnstableApi
@Override
public int getResponseCode() {
return connection == null || responseCode <= 0 ? -1 : responseCode;
}
@UnstableApi
@Override
public Map<String, List<String>> getResponseHeaders() {
if (connection == null) {
......@@ -337,6 +353,7 @@ public class DefaultHttpDataSource extends BaseDataSource implements HttpDataSou
return new NullFilteringHeadersMap(connection.getHeaderFields());
}
@UnstableApi
@Override
public void setRequestProperty(String name, String value) {
checkNotNull(name);
......@@ -344,18 +361,21 @@ public class DefaultHttpDataSource extends BaseDataSource implements HttpDataSou
requestProperties.set(name, value);
}
@UnstableApi
@Override
public void clearRequestProperty(String name) {
checkNotNull(name);
requestProperties.remove(name);
}
@UnstableApi
@Override
public void clearAllRequestProperties() {
requestProperties.clear();
}
/** Opens the source to read the specified data. */
@UnstableApi
@Override
public long open(DataSpec dataSpec) throws HttpDataSourceException {
this.dataSpec = dataSpec;
......@@ -474,6 +494,7 @@ public class DefaultHttpDataSource extends BaseDataSource implements HttpDataSou
return bytesToRead;
}
@UnstableApi
@Override
public int read(byte[] buffer, int offset, int length) throws HttpDataSourceException {
try {
......@@ -484,6 +505,7 @@ public class DefaultHttpDataSource extends BaseDataSource implements HttpDataSou
}
}
@UnstableApi
@Override
public void close() throws HttpDataSourceException {
try {
......
......@@ -38,12 +38,12 @@ import java.util.List;
import java.util.Map;
/** An HTTP {@link DataSource}. */
@UnstableApi
public interface HttpDataSource extends DataSource {
/** A factory for {@link HttpDataSource} instances. */
interface Factory extends DataSource.Factory {
@UnstableApi
@Override
HttpDataSource createDataSource();
......@@ -59,6 +59,7 @@ public interface HttpDataSource extends DataSource {
* @param defaultRequestProperties The default request properties.
* @return This factory.
*/
@UnstableApi
Factory setDefaultRequestProperties(Map<String, String> defaultRequestProperties);
}
......@@ -67,6 +68,7 @@ public interface HttpDataSource extends DataSource {
* a thread safe way to avoid the potential of creating snapshots of an inconsistent or unintended
* state.
*/
@UnstableApi
final class RequestProperties {
private final Map<String, String> requestProperties;
......@@ -141,6 +143,7 @@ public interface HttpDataSource extends DataSource {
}
/** Base implementation of {@link Factory} that sets default request properties. */
@UnstableApi
abstract class BaseFactory implements Factory {
private final RequestProperties defaultRequestProperties;
......@@ -172,6 +175,7 @@ public interface HttpDataSource extends DataSource {
}
/** A {@link Predicate} that rejects content types often used for pay-walls. */
@UnstableApi
Predicate<String> REJECT_PAYWALL_TYPES =
contentType -> {
if (contentType == null) {
......@@ -208,6 +212,7 @@ public interface HttpDataSource extends DataSource {
* Returns a {@code HttpDataSourceException} whose error code is assigned according to the cause
* and type.
*/
@UnstableApi
public static HttpDataSourceException createForIOException(
IOException cause, DataSpec dataSpec, @Type int type) {
@PlaybackException.ErrorCode int errorCode;
......@@ -231,7 +236,7 @@ public interface HttpDataSource extends DataSource {
}
/** The {@link DataSpec} associated with the current connection. */
public final DataSpec dataSpec;
@UnstableApi public final DataSpec dataSpec;
public final @Type int type;
......@@ -239,6 +244,7 @@ public interface HttpDataSource extends DataSource {
* @deprecated Use {@link #HttpDataSourceException(DataSpec, int, int)
* HttpDataSourceException(DataSpec, PlaybackException.ERROR_CODE_IO_UNSPECIFIED, int)}.
*/
@UnstableApi
@Deprecated
public HttpDataSourceException(DataSpec dataSpec, @Type int type) {
this(dataSpec, PlaybackException.ERROR_CODE_IO_UNSPECIFIED, type);
......@@ -252,6 +258,7 @@ public interface HttpDataSource extends DataSource {
* PlaybackException.ErrorCode}.
* @param type See {@link Type}.
*/
@UnstableApi
public HttpDataSourceException(
DataSpec dataSpec, @PlaybackException.ErrorCode int errorCode, @Type int type) {
super(assignErrorCode(errorCode, type));
......@@ -264,6 +271,7 @@ public interface HttpDataSource extends DataSource {
* HttpDataSourceException(String, DataSpec, PlaybackException.ERROR_CODE_IO_UNSPECIFIED,
* int)}.
*/
@UnstableApi
@Deprecated
public HttpDataSourceException(String message, DataSpec dataSpec, @Type int type) {
this(message, dataSpec, PlaybackException.ERROR_CODE_IO_UNSPECIFIED, type);
......@@ -278,6 +286,7 @@ public interface HttpDataSource extends DataSource {
* PlaybackException.ErrorCode}.
* @param type See {@link Type}.
*/
@UnstableApi
public HttpDataSourceException(
String message,
DataSpec dataSpec,
......@@ -293,6 +302,7 @@ public interface HttpDataSource extends DataSource {
* HttpDataSourceException(IOException, DataSpec,
* PlaybackException.ERROR_CODE_IO_UNSPECIFIED, int)}.
*/
@UnstableApi
@Deprecated
public HttpDataSourceException(IOException cause, DataSpec dataSpec, @Type int type) {
this(cause, dataSpec, PlaybackException.ERROR_CODE_IO_UNSPECIFIED, type);
......@@ -307,6 +317,7 @@ public interface HttpDataSource extends DataSource {
* PlaybackException.ErrorCode}.
* @param type See {@link Type}.
*/
@UnstableApi
public HttpDataSourceException(
IOException cause,
DataSpec dataSpec,
......@@ -322,6 +333,7 @@ public interface HttpDataSource extends DataSource {
* HttpDataSourceException(String, IOException, DataSpec,
* PlaybackException.ERROR_CODE_IO_UNSPECIFIED, int)}.
*/
@UnstableApi
@Deprecated
public HttpDataSourceException(
String message, IOException cause, DataSpec dataSpec, @Type int type) {
......@@ -338,6 +350,7 @@ public interface HttpDataSource extends DataSource {
* PlaybackException.ErrorCode}.
* @param type See {@link Type}.
*/
@UnstableApi
public HttpDataSourceException(
String message,
@Nullable IOException cause,
......@@ -365,6 +378,7 @@ public interface HttpDataSource extends DataSource {
*/
final class CleartextNotPermittedException extends HttpDataSourceException {
@UnstableApi
public CleartextNotPermittedException(IOException cause, DataSpec dataSpec) {
super(
"Cleartext HTTP traffic not permitted. See"
......@@ -381,6 +395,7 @@ public interface HttpDataSource extends DataSource {
public final String contentType;
@UnstableApi
public InvalidContentTypeException(String contentType, DataSpec dataSpec) {
super(
"Invalid content type: " + contentType,
......@@ -412,6 +427,7 @@ public interface HttpDataSource extends DataSource {
* @deprecated Use {@link #InvalidResponseCodeException(int, String, IOException, Map, DataSpec,
* byte[])}.
*/
@UnstableApi
@Deprecated
public InvalidResponseCodeException(
int responseCode, Map<String, List<String>> headerFields, DataSpec dataSpec) {
......@@ -428,6 +444,7 @@ public interface HttpDataSource extends DataSource {
* @deprecated Use {@link #InvalidResponseCodeException(int, String, IOException, Map, DataSpec,
* byte[])}.
*/
@UnstableApi
@Deprecated
public InvalidResponseCodeException(
int responseCode,
......@@ -443,6 +460,7 @@ public interface HttpDataSource extends DataSource {
/* responseBody= */ Util.EMPTY_BYTE_ARRAY);
}
@UnstableApi
public InvalidResponseCodeException(
int responseCode,
@Nullable String responseMessage,
......@@ -470,12 +488,15 @@ public interface HttpDataSource extends DataSource {
* (in order of decreasing priority) the {@code dataSpec}, {@link #setRequestProperty} and the
* default parameters set in the {@link Factory}.
*/
@UnstableApi
@Override
long open(DataSpec dataSpec) throws HttpDataSourceException;
@UnstableApi
@Override
void close() throws HttpDataSourceException;
@UnstableApi
@Override
int read(byte[] buffer, int offset, int length) throws HttpDataSourceException;
......@@ -490,6 +511,7 @@ public interface HttpDataSource extends DataSource {
* @param name The name of the header field.
* @param value The value of the field.
*/
@UnstableApi
void setRequestProperty(String name, String value);
/**
......@@ -498,17 +520,21 @@ public interface HttpDataSource extends DataSource {
*
* @param name The name of the header field.
*/
@UnstableApi
void clearRequestProperty(String name);
/** Clears all request headers that were set by {@link #setRequestProperty(String, String)}. */
@UnstableApi
void clearAllRequestProperties();
/**
* When the source is open, returns the HTTP response status code associated with the last {@link
* #open} call. Otherwise, returns a negative value.
*/
@UnstableApi
int getResponseCode();
@UnstableApi
@Override
Map<String, List<String>> getResponseHeaders();
}
......@@ -22,14 +22,14 @@ import java.io.IOException;
/** A DataSource which provides no data. {@link #open(DataSpec)} throws {@link IOException}. */
@UnstableApi
public final class DummyDataSource implements DataSource {
public final class PlaceholderDataSource implements DataSource {
public static final DummyDataSource INSTANCE = new DummyDataSource();
public static final PlaceholderDataSource INSTANCE = new PlaceholderDataSource();
/** A factory that produces {@link DummyDataSource}. */
public static final Factory FACTORY = DummyDataSource::new;
/** A factory that produces {@link PlaceholderDataSource}. */
public static final Factory FACTORY = PlaceholderDataSource::new;
private DummyDataSource() {}
private PlaceholderDataSource() {}
@Override
public void addTransferListener(TransferListener transferListener) {
......@@ -38,7 +38,7 @@ public final class DummyDataSource implements DataSource {
@Override
public long open(DataSpec dataSpec) throws IOException {
throw new IOException("DummyDataSource cannot be opened");
throw new IOException("PlaceholderDataSource cannot be opened");
}
@Override
......
......@@ -36,8 +36,8 @@ import androidx.media3.datasource.DataSink;
import androidx.media3.datasource.DataSource;
import androidx.media3.datasource.DataSourceException;
import androidx.media3.datasource.DataSpec;
import androidx.media3.datasource.DummyDataSource;
import androidx.media3.datasource.FileDataSource;
import androidx.media3.datasource.PlaceholderDataSource;
import androidx.media3.datasource.PriorityDataSource;
import androidx.media3.datasource.TeeDataSource;
import androidx.media3.datasource.TransferListener;
......@@ -541,7 +541,7 @@ public final class CacheDataSource implements DataSource {
? new TeeDataSource(upstreamDataSource, cacheWriteDataSink)
: null;
} else {
this.upstreamDataSource = DummyDataSource.INSTANCE;
this.upstreamDataSource = PlaceholderDataSource.INSTANCE;
this.cacheWriteDataSource = null;
}
this.eventListener = eventListener;
......
......@@ -38,8 +38,9 @@ If your application only needs to play http(s) content, using the Cronet
extension is as simple as updating `DataSource.Factory` instantiations in your
application code to use `CronetDataSource.Factory`. If your application also
needs to play non-http(s) content such as local files, use:
```
new DefaultDataSourceFactory(
new DefaultDataSource.Factory(
...
/* baseDataSourceFactory= */ new CronetDataSource.Factory(...) );
```
......
......@@ -68,7 +68,6 @@ import org.chromium.net.UrlResponseInfo;
* priority) the {@code dataSpec}, {@link #setRequestProperty} and the default parameters used to
* construct the instance.
*/
@UnstableApi
public class CronetDataSource extends BaseDataSource implements HttpDataSource {
static {
......@@ -132,6 +131,7 @@ public class CronetDataSource extends BaseDataSource implements HttpDataSource {
* CronetEngine}, or {@link DefaultHttpDataSource} for cases where {@link
* CronetEngineWrapper#getCronetEngine()} would have returned {@code null}.
*/
@UnstableApi
@Deprecated
public Factory(CronetEngineWrapper cronetEngineWrapper, Executor executor) {
this.cronetEngine = cronetEngineWrapper.getCronetEngine();
......@@ -142,6 +142,7 @@ public class CronetDataSource extends BaseDataSource implements HttpDataSource {
readTimeoutMs = DEFAULT_READ_TIMEOUT_MILLIS;
}
@UnstableApi
@Override
public final Factory setDefaultRequestProperties(Map<String, String> defaultRequestProperties) {
this.defaultRequestProperties.clearAndSet(defaultRequestProperties);
......@@ -161,6 +162,7 @@ public class CronetDataSource extends BaseDataSource implements HttpDataSource {
* agent of the underlying {@link CronetEngine}.
* @return This factory.
*/
@UnstableApi
public Factory setUserAgent(@Nullable String userAgent) {
this.userAgent = userAgent;
if (internalFallbackFactory != null) {
......@@ -179,6 +181,7 @@ public class CronetDataSource extends BaseDataSource implements HttpDataSource {
* UrlRequest.Builder#REQUEST_PRIORITY_*} constants.
* @return This factory.
*/
@UnstableApi
public Factory setRequestPriority(int requestPriority) {
this.requestPriority = requestPriority;
return this;
......@@ -192,6 +195,7 @@ public class CronetDataSource extends BaseDataSource implements HttpDataSource {
* @param connectTimeoutMs The connect timeout, in milliseconds, that will be used.
* @return This factory.
*/
@UnstableApi
public Factory setConnectionTimeoutMs(int connectTimeoutMs) {
this.connectTimeoutMs = connectTimeoutMs;
if (internalFallbackFactory != null) {
......@@ -208,6 +212,7 @@ public class CronetDataSource extends BaseDataSource implements HttpDataSource {
* @param resetTimeoutOnRedirects Whether the connect timeout is reset when a redirect occurs.
* @return This factory.
*/
@UnstableApi
public Factory setResetTimeoutOnRedirects(boolean resetTimeoutOnRedirects) {
this.resetTimeoutOnRedirects = resetTimeoutOnRedirects;
return this;
......@@ -223,6 +228,7 @@ public class CronetDataSource extends BaseDataSource implements HttpDataSource {
* to the redirect url in the "Cookie" header.
* @return This factory.
*/
@UnstableApi
public Factory setHandleSetCookieRequests(boolean handleSetCookieRequests) {
this.handleSetCookieRequests = handleSetCookieRequests;
return this;
......@@ -236,6 +242,7 @@ public class CronetDataSource extends BaseDataSource implements HttpDataSource {
* @param readTimeoutMs The connect timeout, in milliseconds, that will be used.
* @return This factory.
*/
@UnstableApi
public Factory setReadTimeoutMs(int readTimeoutMs) {
this.readTimeoutMs = readTimeoutMs;
if (internalFallbackFactory != null) {
......@@ -254,6 +261,7 @@ public class CronetDataSource extends BaseDataSource implements HttpDataSource {
* predicate that was previously set.
* @return This factory.
*/
@UnstableApi
public Factory setContentTypePredicate(@Nullable Predicate<String> contentTypePredicate) {
this.contentTypePredicate = contentTypePredicate;
if (internalFallbackFactory != null) {
......@@ -266,6 +274,7 @@ public class CronetDataSource extends BaseDataSource implements HttpDataSource {
* Sets whether we should keep the POST method and body when we have HTTP 302 redirects for a
* POST request.
*/
@UnstableApi
public Factory setKeepPostFor302Redirects(boolean keepPostFor302Redirects) {
this.keepPostFor302Redirects = keepPostFor302Redirects;
if (internalFallbackFactory != null) {
......@@ -284,6 +293,7 @@ public class CronetDataSource extends BaseDataSource implements HttpDataSource {
* @param transferListener The listener that will be used.
* @return This factory.
*/
@UnstableApi
public Factory setTransferListener(@Nullable TransferListener transferListener) {
this.transferListener = transferListener;
if (internalFallbackFactory != null) {
......@@ -303,12 +313,14 @@ public class CronetDataSource extends BaseDataSource implements HttpDataSource {
* @deprecated Do not use {@link CronetDataSource} or its factory in cases where a suitable
* {@link CronetEngine} is not available. Use the fallback factory directly in such cases.
*/
@UnstableApi
@Deprecated
public Factory setFallbackFactory(@Nullable HttpDataSource.Factory fallbackFactory) {
this.fallbackFactory = fallbackFactory;
return this;
}
@UnstableApi
@Override
public HttpDataSource createDataSource() {
if (cronetEngine == null) {
......@@ -337,6 +349,7 @@ public class CronetDataSource extends BaseDataSource implements HttpDataSource {
}
/** Thrown when an error is encountered when trying to open a {@link CronetDataSource}. */
@UnstableApi
public static final class OpenException extends HttpDataSourceException {
/**
......@@ -389,9 +402,9 @@ public class CronetDataSource extends BaseDataSource implements HttpDataSource {
}
/** The default connection timeout, in milliseconds. */
public static final int DEFAULT_CONNECT_TIMEOUT_MILLIS = 8 * 1000;
@UnstableApi public static final int DEFAULT_CONNECT_TIMEOUT_MILLIS = 8 * 1000;
/** The default read timeout, in milliseconds. */
public static final int DEFAULT_READ_TIMEOUT_MILLIS = 8 * 1000;
@UnstableApi public static final int DEFAULT_READ_TIMEOUT_MILLIS = 8 * 1000;
/* package */ final UrlRequest.Callback urlRequestCallback;
......@@ -436,6 +449,7 @@ public class CronetDataSource extends BaseDataSource implements HttpDataSource {
private volatile long currentConnectTimeoutMs;
@UnstableApi
protected CronetDataSource(
CronetEngine cronetEngine,
Executor executor,
......@@ -473,6 +487,7 @@ public class CronetDataSource extends BaseDataSource implements HttpDataSource {
* @param contentTypePredicate The content type {@link Predicate}, or {@code null} to clear a
* predicate that was previously set.
*/
@UnstableApi
@Deprecated
public void setContentTypePredicate(@Nullable Predicate<String> contentTypePredicate) {
this.contentTypePredicate = contentTypePredicate;
......@@ -480,21 +495,25 @@ public class CronetDataSource extends BaseDataSource implements HttpDataSource {
// HttpDataSource implementation.
@UnstableApi
@Override
public void setRequestProperty(String name, String value) {
requestProperties.set(name, value);
}
@UnstableApi
@Override
public void clearRequestProperty(String name) {
requestProperties.remove(name);
}
@UnstableApi
@Override
public void clearAllRequestProperties() {
requestProperties.clear();
}
@UnstableApi
@Override
public int getResponseCode() {
return responseInfo == null || responseInfo.getHttpStatusCode() <= 0
......@@ -502,17 +521,20 @@ public class CronetDataSource extends BaseDataSource implements HttpDataSource {
: responseInfo.getHttpStatusCode();
}
@UnstableApi
@Override
public Map<String, List<String>> getResponseHeaders() {
return responseInfo == null ? Collections.emptyMap() : responseInfo.getAllHeaders();
}
@UnstableApi
@Override
@Nullable
public Uri getUri() {
return responseInfo == null ? null : Uri.parse(responseInfo.getUrl());
}
@UnstableApi
@Override
public long open(DataSpec dataSpec) throws HttpDataSourceException {
Assertions.checkNotNull(dataSpec);
......@@ -644,6 +666,7 @@ public class CronetDataSource extends BaseDataSource implements HttpDataSource {
return bytesRemaining;
}
@UnstableApi
@Override
public int read(byte[] buffer, int offset, int length) throws HttpDataSourceException {
Assertions.checkState(opened);
......@@ -715,6 +738,7 @@ public class CronetDataSource extends BaseDataSource implements HttpDataSource {
* @throws HttpDataSourceException If an error occurs reading from the source.
* @throws IllegalArgumentException If {@code buffer} is not a direct ByteBuffer.
*/
@UnstableApi
public int read(ByteBuffer buffer) throws HttpDataSourceException {
Assertions.checkState(opened);
......@@ -759,6 +783,7 @@ public class CronetDataSource extends BaseDataSource implements HttpDataSource {
return bytesRead;
}
@UnstableApi
@Override
public synchronized void close() {
if (currentUrlRequest != null) {
......@@ -779,17 +804,20 @@ public class CronetDataSource extends BaseDataSource implements HttpDataSource {
}
/** Returns current {@link UrlRequest}. May be null if the data source is not opened. */
@UnstableApi
@Nullable
protected UrlRequest getCurrentUrlRequest() {
return currentUrlRequest;
}
/** Returns current {@link UrlResponseInfo}. May be null if the data source is not opened. */
@UnstableApi
@Nullable
protected UrlResponseInfo getCurrentUrlResponseInfo() {
return responseInfo;
}
@UnstableApi
protected UrlRequest.Builder buildRequestBuilder(DataSpec dataSpec) throws IOException {
UrlRequest.Builder requestBuilder =
cronetEngine
......
......@@ -31,7 +31,6 @@ import org.chromium.net.CronetEngine;
import org.chromium.net.CronetProvider;
/** Cronet utility methods. */
@UnstableApi
public final class CronetUtil {
private static final String TAG = "CronetUtil";
......@@ -77,6 +76,7 @@ public final class CronetUtil {
* over Cronet Embedded, if both are available.
* @return The {@link CronetEngine}, or {@code null} if no suitable engine could be built.
*/
@UnstableApi
@Nullable
public static CronetEngine buildCronetEngine(
Context context, @Nullable String userAgent, boolean preferGooglePlayServices) {
......
......@@ -60,7 +60,6 @@ import okhttp3.ResponseBody;
* priority) the {@code dataSpec}, {@link #setRequestProperty} and the default parameters used to
* construct the instance.
*/
@UnstableApi
public class OkHttpDataSource extends BaseDataSource implements HttpDataSource {
static {
......@@ -89,6 +88,7 @@ public class OkHttpDataSource extends BaseDataSource implements HttpDataSource {
defaultRequestProperties = new RequestProperties();
}
@UnstableApi
@Override
public final Factory setDefaultRequestProperties(Map<String, String> defaultRequestProperties) {
this.defaultRequestProperties.clearAndSet(defaultRequestProperties);
......@@ -105,6 +105,7 @@ public class OkHttpDataSource extends BaseDataSource implements HttpDataSource {
* agent of the underlying {@link OkHttpClient}.
* @return This factory.
*/
@UnstableApi
public Factory setUserAgent(@Nullable String userAgent) {
this.userAgent = userAgent;
return this;
......@@ -118,6 +119,7 @@ public class OkHttpDataSource extends BaseDataSource implements HttpDataSource {
* @param cacheControl The cache control that will be used.
* @return This factory.
*/
@UnstableApi
public Factory setCacheControl(@Nullable CacheControl cacheControl) {
this.cacheControl = cacheControl;
return this;
......@@ -134,6 +136,7 @@ public class OkHttpDataSource extends BaseDataSource implements HttpDataSource {
* predicate that was previously set.
* @return This factory.
*/
@UnstableApi
public Factory setContentTypePredicate(@Nullable Predicate<String> contentTypePredicate) {
this.contentTypePredicate = contentTypePredicate;
return this;
......@@ -149,11 +152,13 @@ public class OkHttpDataSource extends BaseDataSource implements HttpDataSource {
* @param transferListener The listener that will be used.
* @return This factory.
*/
@UnstableApi
public Factory setTransferListener(@Nullable TransferListener transferListener) {
this.transferListener = transferListener;
return this;
}
@UnstableApi
@Override
public OkHttpDataSource createDataSource() {
OkHttpDataSource dataSource =
......@@ -185,6 +190,7 @@ public class OkHttpDataSource extends BaseDataSource implements HttpDataSource {
* @deprecated Use {@link OkHttpDataSource.Factory} instead.
*/
@SuppressWarnings("deprecation")
@UnstableApi
@Deprecated
public OkHttpDataSource(Call.Factory callFactory) {
this(callFactory, /* userAgent= */ null);
......@@ -194,6 +200,7 @@ public class OkHttpDataSource extends BaseDataSource implements HttpDataSource {
* @deprecated Use {@link OkHttpDataSource.Factory} instead.
*/
@SuppressWarnings("deprecation")
@UnstableApi
@Deprecated
public OkHttpDataSource(Call.Factory callFactory, @Nullable String userAgent) {
this(callFactory, userAgent, /* cacheControl= */ null, /* defaultRequestProperties= */ null);
......@@ -202,6 +209,7 @@ public class OkHttpDataSource extends BaseDataSource implements HttpDataSource {
/**
* @deprecated Use {@link OkHttpDataSource.Factory} instead.
*/
@UnstableApi
@Deprecated
public OkHttpDataSource(
Call.Factory callFactory,
......@@ -234,27 +242,32 @@ public class OkHttpDataSource extends BaseDataSource implements HttpDataSource {
/**
* @deprecated Use {@link OkHttpDataSource.Factory#setContentTypePredicate(Predicate)} instead.
*/
@UnstableApi
@Deprecated
public void setContentTypePredicate(@Nullable Predicate<String> contentTypePredicate) {
this.contentTypePredicate = contentTypePredicate;
}
@UnstableApi
@Override
@Nullable
public Uri getUri() {
return response == null ? null : Uri.parse(response.request().url().toString());
}
@UnstableApi
@Override
public int getResponseCode() {
return response == null ? -1 : response.code();
}
@UnstableApi
@Override
public Map<String, List<String>> getResponseHeaders() {
return response == null ? Collections.emptyMap() : response.headers().toMultimap();
}
@UnstableApi
@Override
public void setRequestProperty(String name, String value) {
Assertions.checkNotNull(name);
......@@ -262,17 +275,20 @@ public class OkHttpDataSource extends BaseDataSource implements HttpDataSource {
requestProperties.set(name, value);
}
@UnstableApi
@Override
public void clearRequestProperty(String name) {
Assertions.checkNotNull(name);
requestProperties.remove(name);
}
@UnstableApi
@Override
public void clearAllRequestProperties() {
requestProperties.clear();
}
@UnstableApi
@Override
public long open(DataSpec dataSpec) throws HttpDataSourceException {
this.dataSpec = dataSpec;
......@@ -358,6 +374,7 @@ public class OkHttpDataSource extends BaseDataSource implements HttpDataSource {
return bytesToRead;
}
@UnstableApi
@Override
public int read(byte[] buffer, int offset, int length) throws HttpDataSourceException {
try {
......@@ -368,6 +385,7 @@ public class OkHttpDataSource extends BaseDataSource implements HttpDataSource {
}
}
@UnstableApi
@Override
public void close() {
if (opened) {
......
......@@ -39,7 +39,7 @@ injected from application code.
`DefaultDataSource` will automatically use the RTMP extension whenever it's
available. Hence if your application is using `DefaultDataSource` or
`DefaultDataSourceFactory`, adding support for RTMP streams is as simple as
`DefaultDataSource.Factory`, adding support for RTMP streams is as simple as
adding a dependency to the RTMP extension as described above. No changes to your
application code are required. Alternatively, if you know that your application
doesn't need to handle any other protocols, you can update any
......
......@@ -17,7 +17,8 @@ apply from: "$gradle.ext.androidxMediaSettingsDir/common_library_config.gradle"
// failures if ffmpeg hasn't been built according to the README instructions.
if (project.file('src/main/jni/ffmpeg').exists()) {
android.externalNativeBuild.cmake.path = 'src/main/jni/CMakeLists.txt'
android.externalNativeBuild.cmake.version = '3.7.1+'
// Should match cmake_minimum_required.
android.externalNativeBuild.cmake.version = '3.21.0+'
}
dependencies {
......
......@@ -14,7 +14,7 @@
# limitations under the License.
#
cmake_minimum_required(VERSION 3.7.1 FATAL_ERROR)
cmake_minimum_required(VERSION 3.21.0 FATAL_ERROR)
# Enable C++11 features.
set(CMAKE_CXX_STANDARD 11)
......
......@@ -21,11 +21,11 @@ import static java.lang.Math.min;
import androidx.annotation.Nullable;
import androidx.media3.common.C;
import androidx.media3.common.TrackGroupArray;
import androidx.media3.common.util.Assertions;
import androidx.media3.common.util.Log;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.common.util.Util;
import androidx.media3.exoplayer.source.TrackGroupArray;
import androidx.media3.exoplayer.trackselection.ExoTrackSelection;
import androidx.media3.exoplayer.upstream.Allocator;
import androidx.media3.exoplayer.upstream.DefaultAllocator;
......
......@@ -172,25 +172,6 @@ public class DefaultRenderersFactory implements RenderersFactory {
}
/**
* Enable calling {@link MediaCodec#start} immediately after {@link MediaCodec#flush} on the
* playback thread, when operating the codec in asynchronous mode. If disabled, {@link
* MediaCodec#start} will be called by the callback thread after pending callbacks are handled.
*
* <p>By default, this feature is disabled.
*
* <p>This method is experimental, and will be renamed or removed in a future release.
*
* @param enabled Whether {@link MediaCodec#start} will be called on the playback thread
* immediately after {@link MediaCodec#flush}.
* @return This factory, for convenience.
*/
public DefaultRenderersFactory experimentalSetImmediateCodecStartAfterFlushEnabled(
boolean enabled) {
codecAdapterFactory.experimentalSetImmediateCodecStartAfterFlushEnabled(enabled);
return this;
}
/**
* Sets whether to enable fallback to lower-priority decoders if decoder initialization fails.
* This may result in using a decoder that is less efficient or slower than the primary decoder.
*
......
......@@ -38,6 +38,7 @@ import androidx.media3.common.MediaItem;
import androidx.media3.common.Player;
import androidx.media3.common.PriorityTaskManager;
import androidx.media3.common.Timeline;
import androidx.media3.common.TracksInfo;
import androidx.media3.common.VideoSize;
import androidx.media3.common.text.Cue;
import androidx.media3.common.util.Clock;
......@@ -54,8 +55,10 @@ import androidx.media3.exoplayer.metadata.MetadataRenderer;
import androidx.media3.exoplayer.source.DefaultMediaSourceFactory;
import androidx.media3.exoplayer.source.MediaSource;
import androidx.media3.exoplayer.source.ShuffleOrder;
import androidx.media3.exoplayer.source.TrackGroupArray;
import androidx.media3.exoplayer.text.TextRenderer;
import androidx.media3.exoplayer.trackselection.DefaultTrackSelector;
import androidx.media3.exoplayer.trackselection.TrackSelectionArray;
import androidx.media3.exoplayer.trackselection.TrackSelector;
import androidx.media3.exoplayer.upstream.BandwidthMeter;
import androidx.media3.exoplayer.upstream.DefaultBandwidthMeter;
......@@ -1216,6 +1219,27 @@ public interface ExoPlayer extends Player {
@Nullable
TrackSelector getTrackSelector();
/**
* Returns the available track groups.
*
* @see Listener#onTracksInfoChanged(TracksInfo)
* @deprecated Use {@link #getCurrentTracksInfo()}.
*/
@UnstableApi
@Deprecated
TrackGroupArray getCurrentTrackGroups();
/**
* Returns the current track selections for each renderer, which may include {@code null} elements
* if some renderers do not have any selected tracks.
*
* @see Listener#onTracksInfoChanged(TracksInfo)
* @deprecated Use {@link #getCurrentTracksInfo()}.
*/
@UnstableApi
@Deprecated
TrackSelectionArray getCurrentTrackSelections();
/** Returns the {@link Looper} associated with the playback thread. */
@UnstableApi
Looper getPlaybackLooper();
......
......@@ -69,8 +69,6 @@ import androidx.media3.common.Player;
import androidx.media3.common.PriorityTaskManager;
import androidx.media3.common.Timeline;
import androidx.media3.common.TrackGroup;
import androidx.media3.common.TrackGroupArray;
import androidx.media3.common.TrackSelectionArray;
import androidx.media3.common.TrackSelectionParameters;
import androidx.media3.common.TracksInfo;
import androidx.media3.common.VideoSize;
......@@ -93,8 +91,10 @@ import androidx.media3.exoplayer.metadata.MetadataOutput;
import androidx.media3.exoplayer.source.MediaSource;
import androidx.media3.exoplayer.source.MediaSource.MediaPeriodId;
import androidx.media3.exoplayer.source.ShuffleOrder;
import androidx.media3.exoplayer.source.TrackGroupArray;
import androidx.media3.exoplayer.text.TextOutput;
import androidx.media3.exoplayer.trackselection.ExoTrackSelection;
import androidx.media3.exoplayer.trackselection.TrackSelectionArray;
import androidx.media3.exoplayer.trackselection.TrackSelector;
import androidx.media3.exoplayer.trackselection.TrackSelectorResult;
import androidx.media3.exoplayer.upstream.BandwidthMeter;
......@@ -1896,11 +1896,6 @@ import java.util.concurrent.TimeoutException;
}
if (previousPlaybackInfo.trackSelectorResult != newPlaybackInfo.trackSelectorResult) {
trackSelector.onSelectionActivated(newPlaybackInfo.trackSelectorResult.info);
TrackSelectionArray newSelection =
new TrackSelectionArray(newPlaybackInfo.trackSelectorResult.selections);
listeners.queueEvent(
Player.EVENT_TRACKS_CHANGED,
listener -> listener.onTracksChanged(newPlaybackInfo.trackGroups, newSelection));
listeners.queueEvent(
Player.EVENT_TRACKS_CHANGED,
listener -> listener.onTracksInfoChanged(newPlaybackInfo.trackSelectorResult.tracksInfo));
......
......@@ -44,7 +44,6 @@ import androidx.media3.common.Player.PlayWhenReadyChangeReason;
import androidx.media3.common.Player.PlaybackSuppressionReason;
import androidx.media3.common.Player.RepeatMode;
import androidx.media3.common.Timeline;
import androidx.media3.common.TrackGroupArray;
import androidx.media3.common.util.Assertions;
import androidx.media3.common.util.Clock;
import androidx.media3.common.util.HandlerWrapper;
......@@ -56,11 +55,13 @@ import androidx.media3.exoplayer.DefaultMediaClock.PlaybackParametersListener;
import androidx.media3.exoplayer.analytics.AnalyticsCollector;
import androidx.media3.exoplayer.analytics.PlayerId;
import androidx.media3.exoplayer.drm.DrmSession;
import androidx.media3.exoplayer.metadata.MetadataRenderer;
import androidx.media3.exoplayer.source.BehindLiveWindowException;
import androidx.media3.exoplayer.source.MediaPeriod;
import androidx.media3.exoplayer.source.MediaSource.MediaPeriodId;
import androidx.media3.exoplayer.source.SampleStream;
import androidx.media3.exoplayer.source.ShuffleOrder;
import androidx.media3.exoplayer.source.TrackGroupArray;
import androidx.media3.exoplayer.text.TextRenderer;
import androidx.media3.exoplayer.trackselection.ExoTrackSelection;
import androidx.media3.exoplayer.trackselection.TrackSelector;
......@@ -2228,6 +2229,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
return reading.info.isFollowedByTransitionToSameStream
&& nextPeriod.prepared
&& (renderer instanceof TextRenderer // [internal: b/181312195]
|| renderer instanceof MetadataRenderer
|| renderer.getReadingPositionUs() >= nextPeriod.getStartPositionRendererTime());
}
......
......@@ -18,8 +18,8 @@ package androidx.media3.exoplayer;
import androidx.media3.common.C;
import androidx.media3.common.Timeline;
import androidx.media3.common.TrackGroup;
import androidx.media3.common.TrackGroupArray;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.exoplayer.source.TrackGroupArray;
import androidx.media3.exoplayer.trackselection.ExoTrackSelection;
import androidx.media3.exoplayer.upstream.Allocator;
......
......@@ -21,7 +21,6 @@ import androidx.annotation.Nullable;
import androidx.media3.common.C;
import androidx.media3.common.Format;
import androidx.media3.common.Timeline;
import androidx.media3.common.TrackGroupArray;
import androidx.media3.common.util.Assertions;
import androidx.media3.common.util.Log;
import androidx.media3.exoplayer.source.ClippingMediaPeriod;
......@@ -29,6 +28,7 @@ import androidx.media3.exoplayer.source.EmptySampleStream;
import androidx.media3.exoplayer.source.MediaPeriod;
import androidx.media3.exoplayer.source.MediaSource.MediaPeriodId;
import androidx.media3.exoplayer.source.SampleStream;
import androidx.media3.exoplayer.source.TrackGroupArray;
import androidx.media3.exoplayer.trackselection.ExoTrackSelection;
import androidx.media3.exoplayer.trackselection.TrackSelector;
import androidx.media3.exoplayer.trackselection.TrackSelectorResult;
......
......@@ -925,7 +925,8 @@ import com.google.common.collect.ImmutableList;
: endPositionUs;
if (durationUs != C.TIME_UNSET && startPositionUs >= durationUs) {
// Ensure start position doesn't exceed duration.
startPositionUs = max(0, durationUs - 1);
boolean endAtLastFrame = isLastInTimeline || !clipPeriodAtContentDuration;
startPositionUs = max(0, durationUs - (endAtLastFrame ? 1 : 0));
}
return new MediaPeriodInfo(
id,
......
......@@ -26,7 +26,6 @@ import androidx.annotation.VisibleForTesting;
import androidx.media3.common.C;
import androidx.media3.common.MediaItem;
import androidx.media3.common.Timeline;
import androidx.media3.common.TrackGroupArray;
import androidx.media3.common.util.Clock;
import androidx.media3.common.util.HandlerWrapper;
import androidx.media3.common.util.UnstableApi;
......@@ -34,6 +33,7 @@ import androidx.media3.exoplayer.analytics.PlayerId;
import androidx.media3.exoplayer.source.DefaultMediaSourceFactory;
import androidx.media3.exoplayer.source.MediaPeriod;
import androidx.media3.exoplayer.source.MediaSource;
import androidx.media3.exoplayer.source.TrackGroupArray;
import androidx.media3.exoplayer.upstream.Allocator;
import androidx.media3.exoplayer.upstream.DefaultAllocator;
import androidx.media3.extractor.DefaultExtractorsFactory;
......@@ -157,7 +157,7 @@ public final class MetadataRetriever {
mediaPeriod.maybeThrowPrepareError();
}
mediaSourceHandler.sendEmptyMessageDelayed(
MESSAGE_CHECK_FOR_FAILURE, /* delayMillis= */ ERROR_POLL_INTERVAL_MS);
MESSAGE_CHECK_FOR_FAILURE, /* delayMs= */ ERROR_POLL_INTERVAL_MS);
} catch (Exception e) {
trackGroupsFuture.setException(e);
mediaSourceHandler.obtainMessage(MESSAGE_RELEASE).sendToTarget();
......
......@@ -23,8 +23,8 @@ import androidx.media3.common.PlaybackParameters;
import androidx.media3.common.Player;
import androidx.media3.common.Player.PlaybackSuppressionReason;
import androidx.media3.common.Timeline;
import androidx.media3.common.TrackGroupArray;
import androidx.media3.exoplayer.source.MediaSource.MediaPeriodId;
import androidx.media3.exoplayer.source.TrackGroupArray;
import androidx.media3.exoplayer.trackselection.TrackSelectorResult;
import com.google.common.collect.ImmutableList;
import java.util.List;
......
......@@ -35,8 +35,6 @@ import androidx.media3.common.MediaMetadata;
import androidx.media3.common.PlaybackParameters;
import androidx.media3.common.PriorityTaskManager;
import androidx.media3.common.Timeline;
import androidx.media3.common.TrackGroupArray;
import androidx.media3.common.TrackSelectionArray;
import androidx.media3.common.TrackSelectionParameters;
import androidx.media3.common.TracksInfo;
import androidx.media3.common.VideoSize;
......@@ -49,6 +47,8 @@ import androidx.media3.exoplayer.analytics.AnalyticsListener;
import androidx.media3.exoplayer.source.DefaultMediaSourceFactory;
import androidx.media3.exoplayer.source.MediaSource;
import androidx.media3.exoplayer.source.ShuffleOrder;
import androidx.media3.exoplayer.source.TrackGroupArray;
import androidx.media3.exoplayer.trackselection.TrackSelectionArray;
import androidx.media3.exoplayer.trackselection.TrackSelector;
import androidx.media3.exoplayer.upstream.BandwidthMeter;
import androidx.media3.exoplayer.video.VideoFrameMetadataListener;
......
......@@ -45,7 +45,6 @@ import androidx.media3.common.Player.DiscontinuityReason;
import androidx.media3.common.Player.PlaybackSuppressionReason;
import androidx.media3.common.Player.TimelineChangeReason;
import androidx.media3.common.Timeline;
import androidx.media3.common.TrackSelection;
import androidx.media3.common.TrackSelectionParameters;
import androidx.media3.common.TracksInfo;
import androidx.media3.common.VideoSize;
......@@ -60,6 +59,7 @@ import androidx.media3.exoplayer.metadata.MetadataOutput;
import androidx.media3.exoplayer.source.LoadEventInfo;
import androidx.media3.exoplayer.source.MediaLoadData;
import androidx.media3.exoplayer.source.MediaSource.MediaPeriodId;
import androidx.media3.exoplayer.trackselection.TrackSelection;
import androidx.media3.exoplayer.video.VideoDecoderOutputBufferRenderer;
import com.google.common.base.Objects;
import java.io.IOException;
......
......@@ -38,8 +38,6 @@ import androidx.media3.common.Player.PlaybackSuppressionReason;
import androidx.media3.common.Timeline;
import androidx.media3.common.Timeline.Period;
import androidx.media3.common.Timeline.Window;
import androidx.media3.common.TrackGroupArray;
import androidx.media3.common.TrackSelectionArray;
import androidx.media3.common.TrackSelectionParameters;
import androidx.media3.common.TracksInfo;
import androidx.media3.common.VideoSize;
......@@ -484,13 +482,6 @@ public class DefaultAnalyticsCollector implements AnalyticsCollector {
listener -> listener.onMediaItemTransition(eventTime, mediaItem, reason));
}
@SuppressWarnings("deprecation") // Implementing deprecated method.
@Override
public final void onTracksChanged(
TrackGroupArray trackGroups, TrackSelectionArray trackSelections) {
// Do nothing. Handled by non-deprecated onTracksInfoChanged.
}
@Override
public void onTracksInfoChanged(TracksInfo tracksInfo) {
EventTime eventTime = generateCurrentPlayerMediaPeriodEventTime();
......
......@@ -1745,8 +1745,11 @@ public final class DefaultAudioSink implements AudioSink {
// the channel count for this encoding, but before then there is no way to query it so we
// assume 6 channel audio is supported.
if (Util.SDK_INT >= 29) {
// Default to 48 kHz if the format doesn't have a sample rate (for example, for chunkless
// HLS preparation). See [Internal: b/222127949].
int sampleRate = format.sampleRate != Format.NO_VALUE ? format.sampleRate : 48000;
channelCount =
getMaxSupportedChannelCountForPassthroughV29(C.ENCODING_E_AC3_JOC, format.sampleRate);
getMaxSupportedChannelCountForPassthroughV29(C.ENCODING_E_AC3_JOC, sampleRate);
if (channelCount == 0) {
Log.w(TAG, "E-AC3 JOC encoding supported but no channel count supported");
return null;
......
......@@ -24,8 +24,8 @@ import androidx.annotation.RequiresApi;
import androidx.media3.common.MediaItem;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.common.util.Util;
import androidx.media3.datasource.DataSource;
import androidx.media3.datasource.DefaultHttpDataSource;
import androidx.media3.datasource.HttpDataSource;
import com.google.common.primitives.Ints;
import java.util.Map;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
......@@ -42,7 +42,7 @@ public final class DefaultDrmSessionManagerProvider implements DrmSessionManager
@GuardedBy("lock")
private @MonotonicNonNull DrmSessionManager manager;
@Nullable private HttpDataSource.Factory drmHttpDataSourceFactory;
@Nullable private DataSource.Factory drmHttpDataSourceFactory;
@Nullable private String userAgent;
public DefaultDrmSessionManagerProvider() {
......@@ -50,26 +50,22 @@ public final class DefaultDrmSessionManagerProvider implements DrmSessionManager
}
/**
* Sets the {@link HttpDataSource.Factory} to be used for creating {@link HttpMediaDrmCallback
* HttpMediaDrmCallbacks} which executes key and provisioning requests over HTTP. If {@code null}
* is passed the {@link DefaultHttpDataSource.Factory} is used.
* Sets the {@link DataSource.Factory} which is used to create {@link HttpMediaDrmCallback}
* instances. If {@code null} is passed a {@link DefaultHttpDataSource.Factory} is used.
*
* @param drmHttpDataSourceFactory The HTTP data source factory or {@code null} to use {@link
* @param drmDataSourceFactory The data source factory or {@code null} to use {@link
* DefaultHttpDataSource.Factory}.
*/
public void setDrmHttpDataSourceFactory(
@Nullable HttpDataSource.Factory drmHttpDataSourceFactory) {
this.drmHttpDataSourceFactory = drmHttpDataSourceFactory;
public void setDrmHttpDataSourceFactory(@Nullable DataSource.Factory drmDataSourceFactory) {
this.drmHttpDataSourceFactory = drmDataSourceFactory;
}
/**
* Sets the optional user agent to be used for DRM requests.
*
* <p>In case a factory has been set by {@link
* #setDrmHttpDataSourceFactory(HttpDataSource.Factory)}, this user agent is ignored.
*
* @param userAgent The user agent to be used for DRM requests.
* @deprecated Pass a custom {@link DataSource.Factory} to {@link
* #setDrmHttpDataSourceFactory(DataSource.Factory)} which sets the desired user agent on
* outgoing requests.
*/
@Deprecated
public void setDrmUserAgent(@Nullable String userAgent) {
this.userAgent = userAgent;
}
......@@ -94,7 +90,7 @@ public final class DefaultDrmSessionManagerProvider implements DrmSessionManager
@RequiresApi(18)
private DrmSessionManager createManager(MediaItem.DrmConfiguration drmConfiguration) {
HttpDataSource.Factory dataSourceFactory =
DataSource.Factory dataSourceFactory =
drmHttpDataSourceFactory != null
? drmHttpDataSourceFactory
: new DefaultHttpDataSource.Factory().setUserAgent(userAgent);
......
......@@ -192,7 +192,11 @@ public final class FrameworkMediaDrm implements ExoMediaDrm {
@Override
public void setPlayerIdForSession(byte[] sessionId, PlayerId playerId) {
if (Util.SDK_INT >= 31) {
Api31.setLogSessionIdOnMediaDrmSession(mediaDrm, sessionId, playerId);
try {
Api31.setLogSessionIdOnMediaDrmSession(mediaDrm, sessionId, playerId);
} catch (UnsupportedOperationException e) {
Log.w(TAG, "setLogSessionId failed.");
}
}
}
......
......@@ -22,9 +22,9 @@ import androidx.media3.common.C;
import androidx.media3.common.util.Assertions;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.common.util.Util;
import androidx.media3.datasource.DataSource;
import androidx.media3.datasource.DataSourceInputStream;
import androidx.media3.datasource.DataSpec;
import androidx.media3.datasource.HttpDataSource;
import androidx.media3.datasource.HttpDataSource.InvalidResponseCodeException;
import androidx.media3.datasource.StatsDataSource;
import androidx.media3.exoplayer.drm.ExoMediaDrm.KeyRequest;
......@@ -36,41 +36,47 @@ import java.util.List;
import java.util.Map;
import java.util.UUID;
/** A {@link MediaDrmCallback} that makes requests using {@link HttpDataSource} instances. */
/** A {@link MediaDrmCallback} that makes requests using {@link DataSource} instances. */
@UnstableApi
public final class HttpMediaDrmCallback implements MediaDrmCallback {
private static final int MAX_MANUAL_REDIRECTS = 5;
private final HttpDataSource.Factory dataSourceFactory;
private final DataSource.Factory dataSourceFactory;
@Nullable private final String defaultLicenseUrl;
private final boolean forceDefaultLicenseUrl;
private final Map<String, String> keyRequestProperties;
/**
* Constructs an instance.
*
* @param defaultLicenseUrl The default license URL. Used for key requests that do not specify
* their own license URL. May be {@code null} if it's known that all key requests will specify
* their own URLs.
* @param dataSourceFactory A factory from which to obtain {@link HttpDataSource} instances.
* @param dataSourceFactory A factory from which to obtain {@link DataSource} instances. This will
* usually be an HTTP-based {@link DataSource}.
*/
public HttpMediaDrmCallback(
@Nullable String defaultLicenseUrl, HttpDataSource.Factory dataSourceFactory) {
@Nullable String defaultLicenseUrl, DataSource.Factory dataSourceFactory) {
this(defaultLicenseUrl, /* forceDefaultLicenseUrl= */ false, dataSourceFactory);
}
/**
* Constructs an instance.
*
* @param defaultLicenseUrl The default license URL. Used for key requests that do not specify
* their own license URL, or for all key requests if {@code forceDefaultLicenseUrl} is set to
* true. May be {@code null} if {@code forceDefaultLicenseUrl} is {@code false} and if it's
* known that all key requests will specify their own URLs.
* @param forceDefaultLicenseUrl Whether to force use of {@code defaultLicenseUrl} for key
* requests that include their own license URL.
* @param dataSourceFactory A factory from which to obtain {@link HttpDataSource} instances.
* @param dataSourceFactory A factory from which to obtain {@link DataSource} instances. This will
* * usually be an HTTP-based {@link DataSource}.
*/
public HttpMediaDrmCallback(
@Nullable String defaultLicenseUrl,
boolean forceDefaultLicenseUrl,
HttpDataSource.Factory dataSourceFactory) {
DataSource.Factory dataSourceFactory) {
Assertions.checkArgument(!(forceDefaultLicenseUrl && TextUtils.isEmpty(defaultLicenseUrl)));
this.dataSourceFactory = dataSourceFactory;
this.defaultLicenseUrl = defaultLicenseUrl;
......@@ -156,7 +162,7 @@ public final class HttpMediaDrmCallback implements MediaDrmCallback {
}
private static byte[] executePost(
HttpDataSource.Factory dataSourceFactory,
DataSource.Factory dataSourceFactory,
String url,
@Nullable byte[] httpBody,
Map<String, String> requestProperties)
......
......@@ -26,7 +26,7 @@ import androidx.media3.common.DrmInitData;
import androidx.media3.common.Format;
import androidx.media3.common.util.Assertions;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.datasource.HttpDataSource;
import androidx.media3.datasource.DataSource;
import androidx.media3.exoplayer.analytics.PlayerId;
import androidx.media3.exoplayer.drm.DefaultDrmSessionManager.Mode;
import androidx.media3.exoplayer.drm.DrmSession.DrmSessionException;
......@@ -53,20 +53,17 @@ public final class OfflineLicenseHelper {
*
* @param defaultLicenseUrl The default license URL. Used for key requests that do not specify
* their own license URL.
* @param httpDataSourceFactory A factory from which to obtain {@link HttpDataSource} instances.
* @param dataSourceFactory A factory from which to obtain {@link DataSource} instances.
* @param eventDispatcher A {@link DrmSessionEventListener.EventDispatcher} used to distribute
* DRM-related events.
* @return A new instance which uses Widevine CDM.
*/
public static OfflineLicenseHelper newWidevineInstance(
String defaultLicenseUrl,
HttpDataSource.Factory httpDataSourceFactory,
DataSource.Factory dataSourceFactory,
DrmSessionEventListener.EventDispatcher eventDispatcher) {
return newWidevineInstance(
defaultLicenseUrl,
/* forceDefaultLicenseUrl= */ false,
httpDataSourceFactory,
eventDispatcher);
defaultLicenseUrl, /* forceDefaultLicenseUrl= */ false, dataSourceFactory, eventDispatcher);
}
/**
......@@ -77,7 +74,7 @@ public final class OfflineLicenseHelper {
* their own license URL.
* @param forceDefaultLicenseUrl Whether to use {@code defaultLicenseUrl} for key requests that
* include their own license URL.
* @param httpDataSourceFactory A factory from which to obtain {@link HttpDataSource} instances.
* @param dataSourceFactory A factory from which to obtain {@link DataSource} instances.
* @param eventDispatcher A {@link DrmSessionEventListener.EventDispatcher} used to distribute
* DRM-related events.
* @return A new instance which uses Widevine CDM.
......@@ -85,12 +82,12 @@ public final class OfflineLicenseHelper {
public static OfflineLicenseHelper newWidevineInstance(
String defaultLicenseUrl,
boolean forceDefaultLicenseUrl,
HttpDataSource.Factory httpDataSourceFactory,
DataSource.Factory dataSourceFactory,
DrmSessionEventListener.EventDispatcher eventDispatcher) {
return newWidevineInstance(
defaultLicenseUrl,
forceDefaultLicenseUrl,
httpDataSourceFactory,
dataSourceFactory,
/* optionalKeyRequestParameters= */ null,
eventDispatcher);
}
......@@ -113,7 +110,7 @@ public final class OfflineLicenseHelper {
public static OfflineLicenseHelper newWidevineInstance(
String defaultLicenseUrl,
boolean forceDefaultLicenseUrl,
HttpDataSource.Factory httpDataSourceFactory,
DataSource.Factory dataSourceFactory,
@Nullable Map<String, String> optionalKeyRequestParameters,
DrmSessionEventListener.EventDispatcher eventDispatcher) {
return new OfflineLicenseHelper(
......@@ -121,7 +118,7 @@ public final class OfflineLicenseHelper {
.setKeyRequestParameters(optionalKeyRequestParameters)
.build(
new HttpMediaDrmCallback(
defaultLicenseUrl, forceDefaultLicenseUrl, httpDataSourceFactory)),
defaultLicenseUrl, forceDefaultLicenseUrl, dataSourceFactory)),
eventDispatcher);
}
......
......@@ -24,6 +24,8 @@ import static java.lang.annotation.ElementType.TYPE_USE;
import androidx.annotation.IntDef;
import androidx.media3.common.util.UnstableApi;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/** Thrown when the requested DRM scheme is not supported. */
......@@ -35,8 +37,9 @@ public final class UnsupportedDrmException extends Exception {
* #REASON_INSTANTIATION_ERROR}.
*/
// @Target list includes both 'default' targets and TYPE_USE, to ensure backwards compatibility
// with Kotlin usages from before TYPE_USE was added. @Retention(RetentionPolicy.SOURCE)
// with Kotlin usages from before TYPE_USE was added.
@Documented
@Retention(RetentionPolicy.SOURCE)
@Target({FIELD, METHOD, PARAMETER, LOCAL_VARIABLE, TYPE_USE})
@IntDef({REASON_UNSUPPORTED_SCHEME, REASON_INSTANTIATION_ERROR})
public @interface Reason {}
......
......@@ -54,7 +54,6 @@ import java.nio.ByteBuffer;
private final Supplier<HandlerThread> callbackThreadSupplier;
private final Supplier<HandlerThread> queueingThreadSupplier;
private final boolean synchronizeCodecInteractionsWithQueueing;
private final boolean enableImmediateCodecStartAfterFlush;
/**
* Creates an factory for {@link AsynchronousMediaCodecAdapter} instances.
......@@ -66,29 +65,23 @@ import java.nio.ByteBuffer;
* interactions will wait until all input buffers pending queueing wil be submitted to the
* {@link MediaCodec}.
*/
public Factory(
@C.TrackType int trackType,
boolean synchronizeCodecInteractionsWithQueueing,
boolean enableImmediateCodecStartAfterFlush) {
public Factory(@C.TrackType int trackType, boolean synchronizeCodecInteractionsWithQueueing) {
this(
/* callbackThreadSupplier= */ () ->
new HandlerThread(createCallbackThreadLabel(trackType)),
/* queueingThreadSupplier= */ () ->
new HandlerThread(createQueueingThreadLabel(trackType)),
synchronizeCodecInteractionsWithQueueing,
enableImmediateCodecStartAfterFlush);
synchronizeCodecInteractionsWithQueueing);
}
@VisibleForTesting
/* package */ Factory(
Supplier<HandlerThread> callbackThreadSupplier,
Supplier<HandlerThread> queueingThreadSupplier,
boolean synchronizeCodecInteractionsWithQueueing,
boolean enableImmediateCodecStartAfterFlush) {
boolean synchronizeCodecInteractionsWithQueueing) {
this.callbackThreadSupplier = callbackThreadSupplier;
this.queueingThreadSupplier = queueingThreadSupplier;
this.synchronizeCodecInteractionsWithQueueing = synchronizeCodecInteractionsWithQueueing;
this.enableImmediateCodecStartAfterFlush = enableImmediateCodecStartAfterFlush;
}
@Override
......@@ -105,8 +98,7 @@ import java.nio.ByteBuffer;
codec,
callbackThreadSupplier.get(),
queueingThreadSupplier.get(),
synchronizeCodecInteractionsWithQueueing,
enableImmediateCodecStartAfterFlush);
synchronizeCodecInteractionsWithQueueing);
TraceUtil.endSection();
codecAdapter.initialize(
configuration.mediaFormat,
......@@ -139,7 +131,6 @@ import java.nio.ByteBuffer;
private final AsynchronousMediaCodecCallback asynchronousMediaCodecCallback;
private final AsynchronousMediaCodecBufferEnqueuer bufferEnqueuer;
private final boolean synchronizeCodecInteractionsWithQueueing;
private final boolean enableImmediateCodecStartAfterFlush;
private boolean codecReleased;
private @State int state;
......@@ -147,13 +138,11 @@ import java.nio.ByteBuffer;
MediaCodec codec,
HandlerThread callbackThread,
HandlerThread enqueueingThread,
boolean synchronizeCodecInteractionsWithQueueing,
boolean enableImmediateCodecStartAfterFlush) {
boolean synchronizeCodecInteractionsWithQueueing) {
this.codec = codec;
this.asynchronousMediaCodecCallback = new AsynchronousMediaCodecCallback(callbackThread);
this.bufferEnqueuer = new AsynchronousMediaCodecBufferEnqueuer(codec, enqueueingThread);
this.synchronizeCodecInteractionsWithQueueing = synchronizeCodecInteractionsWithQueueing;
this.enableImmediateCodecStartAfterFlush = enableImmediateCodecStartAfterFlush;
this.state = STATE_CREATED;
}
......@@ -232,18 +221,13 @@ import java.nio.ByteBuffer;
// The order of calls is important:
// 1. Flush the bufferEnqueuer to stop queueing input buffers.
// 2. Flush the codec to stop producing available input/output buffers.
// 3. Flush the callback after flushing the codec so that in-flight callbacks are discarded.
// 3. Flush the callback so that in-flight callbacks are discarded.
// 4. Start the codec. The asynchronous callback will drop pending callbacks and we can start
// the codec now.
bufferEnqueuer.flush();
codec.flush();
if (enableImmediateCodecStartAfterFlush) {
// The asynchronous callback will drop pending callbacks but we can start the codec now.
asynchronousMediaCodecCallback.flush(/* codec= */ null);
codec.start();
} else {
// Let the asynchronous callback start the codec in the callback thread after pending
// callbacks are handled.
asynchronousMediaCodecCallback.flush(codec);
}
asynchronousMediaCodecCallback.flush();
codec.start();
}
@Override
......
......@@ -191,14 +191,11 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
/**
* Initiates a flush asynchronously, which will be completed on the callback thread. When the
* flush is complete, it will trigger {@code onFlushCompleted} from the callback thread.
*
* @param codec A {@link MediaCodec} to {@link MediaCodec#start start} after all pending callbacks
* are handled, or {@code null} if starting the {@link MediaCodec} is performed elsewhere.
*/
public void flush(@Nullable MediaCodec codec) {
public void flush() {
synchronized (lock) {
++pendingFlushCount;
Util.castNonNull(handler).post(() -> this.onFlushCompleted(codec));
Util.castNonNull(handler).post(this::onFlushCompleted);
}
}
......@@ -238,7 +235,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
}
}
private void onFlushCompleted(@Nullable MediaCodec codec) {
private void onFlushCompleted() {
synchronized (lock) {
if (shutDown) {
return;
......@@ -254,15 +251,6 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
return;
}
flushInternal();
if (codec != null) {
try {
codec.start();
} catch (IllegalStateException e) {
setInternalException(e);
} catch (Exception e) {
setInternalException(new IllegalStateException(e));
}
}
}
}
......
......@@ -17,7 +17,6 @@ package androidx.media3.exoplayer.mediacodec;
import static java.lang.annotation.ElementType.TYPE_USE;
import android.media.MediaCodec;
import androidx.annotation.IntDef;
import androidx.media3.common.MimeTypes;
import androidx.media3.common.util.Log;
......@@ -55,11 +54,9 @@ public final class DefaultMediaCodecAdapterFactory implements MediaCodecAdapter.
private @Mode int asynchronousMode;
private boolean enableSynchronizeCodecInteractionsWithQueueing;
private boolean enableImmediateCodecStartAfterFlush;
public DefaultMediaCodecAdapterFactory() {
asynchronousMode = MODE_DEFAULT;
enableImmediateCodecStartAfterFlush = true;
}
/**
......@@ -96,27 +93,12 @@ public final class DefaultMediaCodecAdapterFactory implements MediaCodecAdapter.
enableSynchronizeCodecInteractionsWithQueueing = enabled;
}
/**
* Enable calling {@link MediaCodec#start} immediately after {@link MediaCodec#flush} on the
* playback thread, when operating the codec in asynchronous mode. If disabled, {@link
* MediaCodec#start} will be called by the callback thread after pending callbacks are handled.
*
* <p>By default, this feature is enabled.
*
* <p>This method is experimental, and will be renamed or removed in a future release.
*
* @param enabled Whether {@link MediaCodec#start()} will be called on the playback thread
* immediately after {@link MediaCodec#flush}.
*/
public void experimentalSetImmediateCodecStartAfterFlushEnabled(boolean enabled) {
enableImmediateCodecStartAfterFlush = enabled;
}
@Override
public MediaCodecAdapter createAdapter(MediaCodecAdapter.Configuration configuration)
throws IOException {
if ((asynchronousMode == MODE_ENABLED && Util.SDK_INT >= 23)
|| (asynchronousMode == MODE_DEFAULT && Util.SDK_INT >= 31)) {
if (Util.SDK_INT >= 23
&& (asynchronousMode == MODE_ENABLED
|| (asynchronousMode == MODE_DEFAULT && Util.SDK_INT >= 31))) {
int trackType = MimeTypes.getTrackType(configuration.format.sampleMimeType);
Log.i(
TAG,
......@@ -124,9 +106,7 @@ public final class DefaultMediaCodecAdapterFactory implements MediaCodecAdapter.
+ Util.getTrackTypeString(trackType));
AsynchronousMediaCodecAdapter.Factory factory =
new AsynchronousMediaCodecAdapter.Factory(
trackType,
enableSynchronizeCodecInteractionsWithQueueing,
enableImmediateCodecStartAfterFlush);
trackType, enableSynchronizeCodecInteractionsWithQueueing);
return factory.createAdapter(configuration);
}
return new SynchronousMediaCodecAdapter.Factory().createAdapter(configuration);
......
......@@ -443,6 +443,8 @@ public final class MediaCodecUtil {
return "audio/x-lg-alac";
} else if (mimeType.equals(MimeTypes.AUDIO_FLAC) && "OMX.lge.flac.decoder".equals(name)) {
return "audio/x-lg-flac";
} else if (mimeType.equals(MimeTypes.AUDIO_AC3) && "OMX.lge.ac3.decoder".equals(name)) {
return "audio/lg-ac3";
}
return null;
......
......@@ -19,7 +19,6 @@ import androidx.annotation.Nullable;
import androidx.media3.common.C;
import androidx.media3.common.Format;
import androidx.media3.common.MimeTypes;
import androidx.media3.common.TrackGroupArray;
import androidx.media3.common.util.Assertions;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.common.util.Util;
......
......@@ -147,7 +147,6 @@ public final class DefaultMediaSourceFactory implements MediaSourceFactory {
* @param dataSourceFactory A {@link DataSource.Factory} to create {@link DataSource} instances
* for requesting media data.
*/
@UnstableApi
public DefaultMediaSourceFactory(DataSource.Factory dataSourceFactory) {
this(dataSourceFactory, new DefaultExtractorsFactory());
}
......
......@@ -21,7 +21,6 @@ import static androidx.media3.common.util.Util.castNonNull;
import androidx.annotation.Nullable;
import androidx.media3.common.C;
import androidx.media3.common.TrackGroupArray;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.exoplayer.SeekParameters;
import androidx.media3.exoplayer.source.MediaSource.MediaPeriodId;
......
......@@ -19,7 +19,6 @@ import androidx.media3.common.C;
import androidx.media3.common.StreamKey;
import androidx.media3.common.Timeline;
import androidx.media3.common.TrackGroup;
import androidx.media3.common.TrackGroupArray;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.exoplayer.ExoPlayer;
import androidx.media3.exoplayer.SeekParameters;
......
......@@ -23,7 +23,6 @@ import androidx.media3.common.C;
import androidx.media3.common.Format;
import androidx.media3.common.StreamKey;
import androidx.media3.common.TrackGroup;
import androidx.media3.common.TrackGroupArray;
import androidx.media3.common.util.Assertions;
import androidx.media3.decoder.DecoderInputBuffer;
import androidx.media3.exoplayer.FormatHolder;
......
......@@ -28,7 +28,6 @@ import androidx.media3.common.Metadata;
import androidx.media3.common.MimeTypes;
import androidx.media3.common.ParserException;
import androidx.media3.common.TrackGroup;
import androidx.media3.common.TrackGroupArray;
import androidx.media3.common.util.Assertions;
import androidx.media3.common.util.ConditionVariable;
import androidx.media3.common.util.ParsableByteArray;
......
......@@ -26,7 +26,6 @@ import androidx.media3.common.MediaItem;
import androidx.media3.common.MimeTypes;
import androidx.media3.common.Timeline;
import androidx.media3.common.TrackGroup;
import androidx.media3.common.TrackGroupArray;
import androidx.media3.common.util.Assertions;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.common.util.Util;
......
......@@ -20,7 +20,6 @@ import androidx.media3.common.C;
import androidx.media3.common.Format;
import androidx.media3.common.MimeTypes;
import androidx.media3.common.TrackGroup;
import androidx.media3.common.TrackGroupArray;
import androidx.media3.common.util.Assertions;
import androidx.media3.common.util.Log;
import androidx.media3.common.util.Util;
......
......@@ -74,11 +74,12 @@ public final class SingleSampleMediaSource extends BaseMediaSource {
}
/**
* Sets an optional track id to be used.
*
* @param trackId An optional track id.
* @return This factory, for convenience.
* @deprecated Use {@link MediaItem.SubtitleConfiguration.Builder#setId(String)} instead (on the
* {@link MediaItem.SubtitleConfiguration} passed to {@link
* #createMediaSource(MediaItem.SubtitleConfiguration, long)}). {@code trackId} will only be
* used if {@link MediaItem.SubtitleConfiguration#id} is {@code null}.
*/
@Deprecated
public Factory setTrackId(@Nullable String trackId) {
this.trackId = trackId;
return this;
......@@ -157,29 +158,28 @@ public final class SingleSampleMediaSource extends BaseMediaSource {
this.durationUs = durationUs;
this.loadErrorHandlingPolicy = loadErrorHandlingPolicy;
this.treatLoadErrorsAsEndOfStream = treatLoadErrorsAsEndOfStream;
mediaItem =
this.mediaItem =
new MediaItem.Builder()
.setUri(Uri.EMPTY)
.setMediaId(subtitleConfiguration.uri.toString())
.setSubtitleConfigurations(ImmutableList.of(subtitleConfiguration))
.setTag(tag)
.build();
format =
this.format =
new Format.Builder()
.setId(trackId)
.setSampleMimeType(firstNonNull(subtitleConfiguration.mimeType, MimeTypes.TEXT_UNKNOWN))
.setLanguage(subtitleConfiguration.language)
.setSelectionFlags(subtitleConfiguration.selectionFlags)
.setRoleFlags(subtitleConfiguration.roleFlags)
.setLabel(subtitleConfiguration.label)
.setId(subtitleConfiguration.id)
.setId(subtitleConfiguration.id != null ? subtitleConfiguration.id : trackId)
.build();
dataSpec =
this.dataSpec =
new DataSpec.Builder()
.setUri(subtitleConfiguration.uri)
.setFlags(DataSpec.FLAG_ALLOW_GZIP)
.build();
timeline =
this.timeline =
new SinglePeriodTimeline(
durationUs,
/* isSeekable= */ true,
......
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