mirror of
https://github.com/androidx/media.git
synced 2025-04-30 06:46:50 +08:00
Merge branch 'main' of github.com:ittiam-systems/media into rtp_wav
This commit is contained in:
commit
bfc1fb9aa7
@ -1,5 +1,61 @@
|
||||
# Release notes
|
||||
|
||||
### Unreleased changes
|
||||
|
||||
* Core library:
|
||||
* Enable support for Android platform diagnostics via
|
||||
`MediaMetricsManager`. ExoPlayer will forward playback events and
|
||||
performance data to the platform, which helps to provide system
|
||||
performance and debugging information on the device. This data may also
|
||||
be collected by Google
|
||||
[if sharing usage and diagnostics data is enabled](https://support.google.com/accounts/answer/6078260)
|
||||
by the user of the device. Apps can opt-out of contributing to platform
|
||||
diagnostics for ExoPlayer with
|
||||
`ExoPlayer.Builder.setUsePlatformDiagnostics(false)`.
|
||||
* Track selection:
|
||||
* Flatten `TrackSelectionOverrides` class into `TrackSelectionParameters`,
|
||||
and promote `TrackSelectionOverride` to a top level class.
|
||||
* Audio:
|
||||
* Use LG AC3 audio decoder advertising non-standard MIME type.
|
||||
* Extractors:
|
||||
* Matroska: Parse `DiscardPadding` for Opus tracks.
|
||||
* UI:
|
||||
* Fix delivery of events to `OnClickListener`s set on `PlayerView` and
|
||||
`LegacyPlayerView`, in the case that `useController=false`
|
||||
([#9605](https://github.com/google/ExoPlayer/issues/9605)). Also fix
|
||||
delivery of events to `OnLongClickListener` for all view configurations.
|
||||
* Fix incorrectly treating a sequence of touch events that exit the bounds
|
||||
of `PlayerView` and `LegacyPlayerView` before `ACTION_UP` as a click
|
||||
([#9861](https://github.com/google/ExoPlayer/issues/9861)).
|
||||
* Fix `PlayerView` accessibility issue where it was not possible to
|
||||
tapping would toggle playback rather than hiding the controls
|
||||
([#8627](https://github.com/google/ExoPlayer/issues/8627)).
|
||||
* Rewrite `TrackSelectionView` and `TrackSelectionDialogBuilder` to work
|
||||
with the `Player` interface rather than `ExoPlayer`. This allows the
|
||||
views to be used with other `Player` implementations, and removes the
|
||||
dependency from the UI module to the ExoPlayer module. This is a
|
||||
breaking change.
|
||||
* RTSP:
|
||||
* Add RTP reader for HEVC
|
||||
([#36](https://github.com/androidx/media/pull/36)).
|
||||
* Remove deprecated symbols:
|
||||
* Remove `Player.Listener.onTracksChanged`. Use
|
||||
`Player.Listener.onTracksInfoChanged` instead.
|
||||
* Remove `Player.getCurrentTrackGroups` and
|
||||
`Player.getCurrentTrackSelections`. Use `Player.getCurrentTracksInfo`
|
||||
instead. You can also continue to use `ExoPlayer.getCurrentTrackGroups`
|
||||
and `ExoPlayer.getCurrentTrackSelections`, although these methods remain
|
||||
deprecated.
|
||||
* Remove `DownloadHelper`
|
||||
`DEFAULT_TRACK_SELECTOR_PARAMETERS_WITHOUT_VIEWPORT` and
|
||||
`DEFAULT_TRACK_SELECTOR_PARAMETERS` constants. Use
|
||||
`getDefaultTrackSelectorParameters(Context)` instead when possible, and
|
||||
`DEFAULT_TRACK_SELECTOR_PARAMETERS_WITHOUT_CONTEXT` otherwise.
|
||||
* FFmpeg extension:
|
||||
* Update CMake version to `3.21.0+` to avoid a CMake bug causing
|
||||
AndroidStudio's gradle sync to fail
|
||||
([#9933](https://github.com/google/ExoPlayer/issues/9933)).
|
||||
|
||||
### 1.0.0-alpha03 (2022-03-14)
|
||||
|
||||
This release corresponds to the
|
||||
|
@ -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'
|
||||
|
@ -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 =
|
||||
|
@ -21,7 +21,6 @@ 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 +58,7 @@ public final class DemoUtil {
|
||||
private static final String DOWNLOAD_CONTENT_DIRECTORY = "downloads";
|
||||
|
||||
private static DataSource.@MonotonicNonNull Factory dataSourceFactory;
|
||||
private static HttpDataSource.@MonotonicNonNull Factory httpDataSourceFactory;
|
||||
private static DataSource.@MonotonicNonNull Factory httpDataSourceFactory;
|
||||
private static @MonotonicNonNull DatabaseProvider databaseProvider;
|
||||
private static @MonotonicNonNull File downloadDirectory;
|
||||
private static @MonotonicNonNull Cache downloadCache;
|
||||
@ -85,7 +84,7 @@ public final class DemoUtil {
|
||||
.setExtensionRendererMode(extensionRendererMode);
|
||||
}
|
||||
|
||||
public static synchronized HttpDataSource.Factory getHttpDataSourceFactory(Context context) {
|
||||
public static synchronized DataSource.Factory getHttpDataSourceFactory(Context context) {
|
||||
if (httpDataSourceFactory == null) {
|
||||
if (USE_CRONET_FOR_NETWORKING) {
|
||||
context = context.getApplicationContext();
|
||||
|
@ -15,8 +15,7 @@
|
||||
*/
|
||||
package 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;
|
||||
@ -30,12 +29,11 @@ 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;
|
||||
@ -48,6 +46,7 @@ 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.source.TrackGroupArray;
|
||||
import androidx.media3.exoplayer.trackselection.MappingTrackSelector.MappedTrackInfo;
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
@ -66,7 +65,7 @@ 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;
|
||||
@ -74,11 +73,9 @@ public class DownloadTracker {
|
||||
@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();
|
||||
@ -87,8 +84,7 @@ public class DownloadTracker {
|
||||
}
|
||||
|
||||
public void addListener(Listener listener) {
|
||||
checkNotNull(listener);
|
||||
listeners.add(listener);
|
||||
listeners.add(checkNotNull(listener));
|
||||
}
|
||||
|
||||
public void removeListener(Listener listener) {
|
||||
@ -119,8 +115,7 @@ public class DownloadTracker {
|
||||
startDownloadDialogHelper =
|
||||
new StartDownloadDialogHelper(
|
||||
fragmentManager,
|
||||
DownloadHelper.forMediaItem(
|
||||
context, mediaItem, renderersFactory, httpDataSourceFactory),
|
||||
DownloadHelper.forMediaItem(context, mediaItem, renderersFactory, dataSourceFactory),
|
||||
mediaItem);
|
||||
}
|
||||
}
|
||||
@ -218,7 +213,7 @@ public class DownloadTracker {
|
||||
new WidevineOfflineLicenseFetchTask(
|
||||
format,
|
||||
mediaItem.localConfiguration.drmConfiguration,
|
||||
httpDataSourceFactory,
|
||||
dataSourceFactory,
|
||||
/* dialogHelper= */ this,
|
||||
helper);
|
||||
widevineOfflineLicenseFetchTask.execute();
|
||||
@ -361,7 +356,7 @@ public class DownloadTracker {
|
||||
|
||||
private final Format format;
|
||||
private final MediaItem.DrmConfiguration drmConfiguration;
|
||||
private final HttpDataSource.Factory httpDataSourceFactory;
|
||||
private final DataSource.Factory dataSourceFactory;
|
||||
private final StartDownloadDialogHelper dialogHelper;
|
||||
private final DownloadHelper downloadHelper;
|
||||
|
||||
@ -371,12 +366,12 @@ public class DownloadTracker {
|
||||
public WidevineOfflineLicenseFetchTask(
|
||||
Format format,
|
||||
MediaItem.DrmConfiguration drmConfiguration,
|
||||
HttpDataSource.Factory httpDataSourceFactory,
|
||||
DataSource.Factory dataSourceFactory,
|
||||
StartDownloadDialogHelper dialogHelper,
|
||||
DownloadHelper downloadHelper) {
|
||||
this.format = format;
|
||||
this.drmConfiguration = drmConfiguration;
|
||||
this.httpDataSourceFactory = httpDataSourceFactory;
|
||||
this.dataSourceFactory = dataSourceFactory;
|
||||
this.dialogHelper = dialogHelper;
|
||||
this.downloadHelper = downloadHelper;
|
||||
}
|
||||
@ -387,7 +382,7 @@ public class DownloadTracker {
|
||||
OfflineLicenseHelper.newWidevineInstance(
|
||||
drmConfiguration.licenseUri.toString(),
|
||||
drmConfiguration.forceDefaultLicenseUri,
|
||||
httpDataSourceFactory,
|
||||
dataSourceFactory,
|
||||
drmConfiguration.licenseRequestHeaders,
|
||||
new DrmSessionEventListener.EventDispatcher());
|
||||
try {
|
||||
@ -405,7 +400,7 @@ public class DownloadTracker {
|
||||
if (drmSessionException != null) {
|
||||
dialogHelper.onOfflineLicenseFetchedError(drmSessionException);
|
||||
} else {
|
||||
dialogHelper.onOfflineLicenseFetched(downloadHelper, checkStateNotNull(keySetId));
|
||||
dialogHelper.onOfflineLicenseFetched(downloadHelper, checkNotNull(keySetId));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -15,8 +15,9 @@
|
||||
*/
|
||||
package 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));
|
||||
|
@ -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;
|
||||
@ -116,8 +116,11 @@ public class SampleChooserActivity extends AppCompatActivity
|
||||
useExtensionRenderers = DemoUtil.useExtensionRenderers();
|
||||
downloadTracker = DemoUtil.getDownloadTracker(/* context= */ this);
|
||||
loadSample();
|
||||
startDownloadService();
|
||||
}
|
||||
|
||||
// Start the download service if it should be running but it's not currently.
|
||||
/** Start the download service if it should be running but it's not currently. */
|
||||
private void startDownloadService() {
|
||||
// Starting the service in the foreground causes notification flicker if there is no scheduled
|
||||
// action. Starting it in the background throws an exception if the app is in the background too
|
||||
// (e.g. if device screen is locked).
|
||||
|
@ -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,8 +50,6 @@ 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";
|
||||
@ -81,7 +79,6 @@ 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;
|
||||
@ -136,14 +133,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);
|
||||
@ -185,7 +174,6 @@ public final class ConfigurationActivity extends AppCompatActivity {
|
||||
"audioMimeSpinner",
|
||||
"videoMimeSpinner",
|
||||
"resolutionHeightSpinner",
|
||||
"translateSpinner",
|
||||
"scaleSpinner",
|
||||
"rotateSpinner",
|
||||
"enableFallbackCheckBox",
|
||||
@ -209,13 +197,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(", "));
|
||||
@ -258,7 +239,6 @@ public final class ConfigurationActivity extends AppCompatActivity {
|
||||
"audioMimeSpinner",
|
||||
"videoMimeSpinner",
|
||||
"resolutionHeightSpinner",
|
||||
"translateSpinner",
|
||||
"scaleSpinner",
|
||||
"rotateSpinner",
|
||||
"enableHdrEditingCheckBox"
|
||||
@ -277,7 +257,6 @@ public final class ConfigurationActivity extends AppCompatActivity {
|
||||
"audioMimeSpinner",
|
||||
"videoMimeSpinner",
|
||||
"resolutionHeightSpinner",
|
||||
"translateSpinner",
|
||||
"scaleSpinner",
|
||||
"rotateSpinner",
|
||||
"enableHdrEditingCheckBox"
|
||||
@ -295,7 +274,6 @@ public final class ConfigurationActivity extends AppCompatActivity {
|
||||
"audioMimeSpinner",
|
||||
"videoMimeSpinner",
|
||||
"resolutionHeightSpinner",
|
||||
"translateSpinner",
|
||||
"scaleSpinner",
|
||||
"rotateSpinner",
|
||||
"enableHdrEditingCheckBox"
|
||||
@ -304,7 +282,6 @@ public final class ConfigurationActivity extends AppCompatActivity {
|
||||
audioMimeSpinner.setEnabled(isAudioEnabled);
|
||||
videoMimeSpinner.setEnabled(isVideoEnabled);
|
||||
resolutionHeightSpinner.setEnabled(isVideoEnabled);
|
||||
translateSpinner.setEnabled(isVideoEnabled);
|
||||
scaleSpinner.setEnabled(isVideoEnabled);
|
||||
rotateSpinner.setEnabled(isVideoEnabled);
|
||||
enableHdrEditingCheckBox.setEnabled(isVideoEnabled);
|
||||
@ -312,7 +289,6 @@ public final class ConfigurationActivity extends AppCompatActivity {
|
||||
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.hdr_editing).setEnabled(isVideoEnabled);
|
||||
|
@ -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,15 @@ 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.experimental_setEnableHdrEditing(
|
||||
bundle.getBoolean(ConfigurationActivity.ENABLE_HDR_EDITING));
|
||||
transformerBuilder
|
||||
@ -251,27 +255,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",
|
||||
|
@ -137,17 +137,6 @@
|
||||
android:layout_gravity="right|center_vertical"
|
||||
android:gravity="right" />
|
||||
</TableRow>
|
||||
<TableRow
|
||||
android:layout_weight="1"
|
||||
android:gravity="center_vertical" >
|
||||
<TextView
|
||||
android:id="@+id/translate"
|
||||
android:text="@string/translate"/>
|
||||
<Spinner
|
||||
android:id="@+id/translate_spinner"
|
||||
android:layout_gravity="right|center_vertical"
|
||||
android:gravity="right" />
|
||||
</TableRow>
|
||||
<TableRow
|
||||
android:layout_weight="1"
|
||||
android:gravity="center_vertical" >
|
||||
|
@ -24,7 +24,6 @@
|
||||
<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>
|
||||
|
@ -111,9 +111,6 @@ public final class CastPlayer extends BasePlayer {
|
||||
|
||||
private static final String TAG = "CastPlayer";
|
||||
|
||||
private static final int RENDERER_INDEX_VIDEO = 0;
|
||||
private static final int RENDERER_INDEX_AUDIO = 1;
|
||||
private static final int RENDERER_INDEX_TEXT = 2;
|
||||
private static final long PROGRESS_REPORT_PERIOD_MS = 1000;
|
||||
private static final long[] EMPTY_TRACK_ID_ARRAY = new long[0];
|
||||
|
||||
|
@ -683,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.
|
||||
*
|
||||
@ -2123,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,7 +34,21 @@ 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";
|
||||
|
@ -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 {
|
||||
|
||||
|
@ -47,10 +47,11 @@ import org.checkerframework.checker.initialization.qual.UnknownInitialization;
|
||||
import org.checkerframework.checker.nullness.qual.EnsuresNonNull;
|
||||
|
||||
/**
|
||||
* Constraint parameters for track selection.
|
||||
* Parameters for controlling track selection.
|
||||
*
|
||||
* <p>For example the following code modifies the parameters to restrict video track selections to
|
||||
* SD, and to select a German audio track if there is one:
|
||||
* <p>Parameters can be queried and set on a {@link Player}. For example the following code modifies
|
||||
* the parameters to restrict video track selections to SD, and to select a German audio track if
|
||||
* there is one:
|
||||
*
|
||||
* <pre>{@code
|
||||
* // Build on the current parameters.
|
||||
@ -656,28 +657,26 @@ public class TrackSelectionParameters implements Bundleable {
|
||||
return this;
|
||||
}
|
||||
|
||||
/** Adds an override for the provided {@link TrackGroup}. */
|
||||
/** Adds an override, replacing any override for the same {@link TrackGroup}. */
|
||||
public Builder addOverride(TrackSelectionOverride override) {
|
||||
overrides.put(override.trackGroup, override);
|
||||
return this;
|
||||
}
|
||||
|
||||
/** Removes the override associated with the provided {@link TrackGroup} if present. */
|
||||
public Builder clearOverride(TrackGroup trackGroup) {
|
||||
overrides.remove(trackGroup);
|
||||
return this;
|
||||
}
|
||||
|
||||
/** Set the override for the type of the provided {@link TrackGroup}. */
|
||||
/** Sets an override, replacing all existing overrides with the same track type. */
|
||||
public Builder setOverrideForType(TrackSelectionOverride override) {
|
||||
clearOverridesOfType(override.getTrackType());
|
||||
overrides.put(override.trackGroup, override);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove any override associated with {@link TrackGroup TrackGroups} of type {@code trackType}.
|
||||
*/
|
||||
/** Removes the override for the provided {@link TrackGroup}, if there is one. */
|
||||
public Builder clearOverride(TrackGroup trackGroup) {
|
||||
overrides.remove(trackGroup);
|
||||
return this;
|
||||
}
|
||||
|
||||
/** Removes all overrides of the provided track type. */
|
||||
public Builder clearOverridesOfType(@C.TrackType int trackType) {
|
||||
Iterator<TrackSelectionOverride> it = overrides.values().iterator();
|
||||
while (it.hasNext()) {
|
||||
@ -689,7 +688,7 @@ public class TrackSelectionParameters implements Bundleable {
|
||||
return this;
|
||||
}
|
||||
|
||||
/** Removes all track overrides. */
|
||||
/** Removes all overrides. */
|
||||
public Builder clearOverrides() {
|
||||
overrides.clear();
|
||||
return this;
|
||||
|
@ -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.
|
||||
*
|
||||
|
@ -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) {
|
||||
@ -222,6 +223,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 +345,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(
|
||||
@ -390,6 +416,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() {}
|
||||
@ -438,12 +469,15 @@ 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
|
||||
@ -459,8 +493,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
|
||||
@ -471,19 +508,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
|
||||
|
@ -1143,18 +1143,6 @@ public final class Util {
|
||||
return (timeMs == C.TIME_UNSET || timeMs == C.TIME_END_OF_SOURCE) ? timeMs : (timeMs * 1000);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
|
@ -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.
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -172,6 +172,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) {
|
||||
|
@ -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;
|
||||
|
@ -38,8 +38,6 @@ import androidx.media3.common.MediaItem;
|
||||
import androidx.media3.common.Player;
|
||||
import androidx.media3.common.PriorityTaskManager;
|
||||
import androidx.media3.common.Timeline;
|
||||
import androidx.media3.common.TrackGroupArray;
|
||||
import androidx.media3.common.TrackSelectionArray;
|
||||
import androidx.media3.common.TracksInfo;
|
||||
import androidx.media3.common.VideoSize;
|
||||
import androidx.media3.common.text.Cue;
|
||||
@ -57,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;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
@ -62,6 +61,7 @@ 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;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -15,6 +15,8 @@
|
||||
*/
|
||||
package androidx.media3.exoplayer.drm;
|
||||
|
||||
import static androidx.media3.common.util.Assertions.checkNotNull;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.media.DeniedByServerException;
|
||||
import android.media.MediaCrypto;
|
||||
@ -23,6 +25,7 @@ import android.media.MediaDrm;
|
||||
import android.media.MediaDrmException;
|
||||
import android.media.NotProvisionedException;
|
||||
import android.media.UnsupportedSchemeException;
|
||||
import android.media.metrics.LogSessionId;
|
||||
import android.os.PersistableBundle;
|
||||
import android.text.TextUtils;
|
||||
import androidx.annotation.DoNotInline;
|
||||
@ -188,7 +191,13 @@ public final class FrameworkMediaDrm implements ExoMediaDrm {
|
||||
|
||||
@Override
|
||||
public void setPlayerIdForSession(byte[] sessionId, PlayerId playerId) {
|
||||
// TODO(b/221032172): Implement this when CDM compatibility issues are resolved.
|
||||
if (Util.SDK_INT >= 31) {
|
||||
try {
|
||||
Api31.setLogSessionIdOnMediaDrmSession(mediaDrm, sessionId, playerId);
|
||||
} catch (UnsupportedOperationException e) {
|
||||
Log.w(TAG, "setLogSessionId failed.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Return values of MediaDrm.KeyRequest.getRequestType are equal to KeyRequest.RequestType.
|
||||
@ -518,5 +527,16 @@ public final class FrameworkMediaDrm implements ExoMediaDrm {
|
||||
public static boolean requiresSecureDecoder(MediaDrm mediaDrm, String mimeType) {
|
||||
return mediaDrm.requiresSecureDecoder(mimeType);
|
||||
}
|
||||
|
||||
@DoNotInline
|
||||
public static void setLogSessionIdOnMediaDrmSession(
|
||||
MediaDrm mediaDrm, byte[] drmSessionId, PlayerId playerId) {
|
||||
LogSessionId logSessionId = playerId.getLogSessionId();
|
||||
if (!logSessionId.equals(LogSessionId.LOG_SESSION_ID_NONE)) {
|
||||
MediaDrm.PlaybackComponent playbackComponent =
|
||||
checkNotNull(mediaDrm.getPlaybackComponent(drmSessionId));
|
||||
playbackComponent.setLogSessionId(logSessionId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -96,8 +96,9 @@ public final class DefaultMediaCodecAdapterFactory implements MediaCodecAdapter.
|
||||
@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,
|
||||
|
@ -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;
|
||||
|
@ -31,7 +31,6 @@ import androidx.media3.common.MimeTypes;
|
||||
import androidx.media3.common.StreamKey;
|
||||
import androidx.media3.common.Timeline;
|
||||
import androidx.media3.common.TrackGroup;
|
||||
import androidx.media3.common.TrackGroupArray;
|
||||
import androidx.media3.common.TrackSelectionOverride;
|
||||
import androidx.media3.common.TrackSelectionParameters;
|
||||
import androidx.media3.common.TracksInfo;
|
||||
@ -52,6 +51,7 @@ import androidx.media3.exoplayer.source.MediaPeriod;
|
||||
import androidx.media3.exoplayer.source.MediaSource;
|
||||
import androidx.media3.exoplayer.source.MediaSource.MediaPeriodId;
|
||||
import androidx.media3.exoplayer.source.MediaSource.MediaSourceCaller;
|
||||
import androidx.media3.exoplayer.source.TrackGroupArray;
|
||||
import androidx.media3.exoplayer.source.chunk.MediaChunk;
|
||||
import androidx.media3.exoplayer.source.chunk.MediaChunkIterator;
|
||||
import androidx.media3.exoplayer.trackselection.BaseTrackSelection;
|
||||
|
@ -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;
|
||||
|
@ -13,13 +13,16 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package androidx.media3.common;
|
||||
package androidx.media3.exoplayer.source;
|
||||
|
||||
import static java.lang.annotation.ElementType.TYPE_USE;
|
||||
|
||||
import android.os.Bundle;
|
||||
import androidx.annotation.IntDef;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.media3.common.Bundleable;
|
||||
import androidx.media3.common.C;
|
||||
import androidx.media3.common.TrackGroup;
|
||||
import androidx.media3.common.util.BundleableUtil;
|
||||
import androidx.media3.common.util.Log;
|
||||
import androidx.media3.common.util.UnstableApi;
|
||||
@ -30,7 +33,16 @@ import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
import java.util.List;
|
||||
|
||||
/** An immutable array of {@link TrackGroup}s. */
|
||||
/**
|
||||
* An immutable array of {@link TrackGroup}s.
|
||||
*
|
||||
* <p>This class is typically used to represent all of the tracks available in a piece of media.
|
||||
* Tracks that are known to present the same content are grouped together (e.g., the same video feed
|
||||
* provided at different resolutions in an adaptive stream). Tracks that are known to present
|
||||
* different content are in separate track groups (e.g., an audio track will not be in the same
|
||||
* group as a video track, and an audio track in one language will be in a different group to an
|
||||
* audio track in another language).
|
||||
*/
|
||||
@UnstableApi
|
||||
public final class TrackGroupArray implements Bundleable {
|
||||
|
@ -36,7 +36,6 @@ import androidx.media3.common.MediaItem;
|
||||
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.common.util.Util;
|
||||
import androidx.media3.datasource.TransferListener;
|
||||
@ -54,6 +53,7 @@ import androidx.media3.exoplayer.source.MediaPeriod;
|
||||
import androidx.media3.exoplayer.source.MediaSource;
|
||||
import androidx.media3.exoplayer.source.MediaSourceEventListener;
|
||||
import androidx.media3.exoplayer.source.SampleStream;
|
||||
import androidx.media3.exoplayer.source.TrackGroupArray;
|
||||
import androidx.media3.exoplayer.trackselection.ExoTrackSelection;
|
||||
import androidx.media3.exoplayer.upstream.Allocator;
|
||||
import com.google.common.collect.ArrayListMultimap;
|
||||
|
@ -24,7 +24,6 @@ import androidx.media3.common.C;
|
||||
import androidx.media3.common.Format;
|
||||
import androidx.media3.common.Timeline;
|
||||
import androidx.media3.common.TrackGroup;
|
||||
import androidx.media3.common.TrackSelection;
|
||||
import androidx.media3.common.util.Clock;
|
||||
import androidx.media3.common.util.Log;
|
||||
import androidx.media3.common.util.UnstableApi;
|
||||
@ -781,7 +780,8 @@ public class AdaptiveTrackSelection extends BaseTrackSelection {
|
||||
}
|
||||
double[] logBitrates = new double[trackBitrates[i].length];
|
||||
for (int j = 0; j < trackBitrates[i].length; j++) {
|
||||
logBitrates[j] = trackBitrates[i][j] == Format.NO_VALUE ? 0 : Math.log(trackBitrates[i][j]);
|
||||
logBitrates[j] =
|
||||
trackBitrates[i][j] == Format.NO_VALUE ? 0 : Math.log((double) trackBitrates[i][j]);
|
||||
}
|
||||
double totalBitrateDiff = logBitrates[logBitrates.length - 1] - logBitrates[0];
|
||||
for (int j = 0; j < logBitrates.length - 1; j++) {
|
||||
|
@ -22,7 +22,6 @@ 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;
|
||||
import androidx.media3.common.util.UnstableApi;
|
||||
import androidx.media3.common.util.Util;
|
||||
|
@ -36,8 +36,6 @@ import androidx.media3.common.Format;
|
||||
import androidx.media3.common.MimeTypes;
|
||||
import androidx.media3.common.Timeline;
|
||||
import androidx.media3.common.TrackGroup;
|
||||
import androidx.media3.common.TrackGroupArray;
|
||||
import androidx.media3.common.TrackSelection;
|
||||
import androidx.media3.common.TrackSelectionOverride;
|
||||
import androidx.media3.common.TrackSelectionParameters;
|
||||
import androidx.media3.common.util.Assertions;
|
||||
@ -51,6 +49,7 @@ import androidx.media3.exoplayer.RendererCapabilities.AdaptiveSupport;
|
||||
import androidx.media3.exoplayer.RendererCapabilities.Capabilities;
|
||||
import androidx.media3.exoplayer.RendererConfiguration;
|
||||
import androidx.media3.exoplayer.source.MediaSource.MediaPeriodId;
|
||||
import androidx.media3.exoplayer.source.TrackGroupArray;
|
||||
import com.google.common.collect.ComparisonChain;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.Ordering;
|
||||
@ -86,11 +85,10 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
|
||||
* .setMaxVideoSizeSd()
|
||||
* .setPreferredAudioLanguage("de")
|
||||
* .build());
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* Some specialized parameters are only available in the extended {@link Parameters} class, which
|
||||
* can be retrieved and modified in a similar way in this track selector:
|
||||
* can be retrieved and modified in a similar way by calling methods directly on this class:
|
||||
*
|
||||
* <pre>{@code
|
||||
* defaultTrackSelector.setParameters(
|
||||
@ -98,7 +96,6 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
|
||||
* .buildUpon()
|
||||
* .setTunnelingEnabled(true)
|
||||
* .build());
|
||||
*
|
||||
* }</pre>
|
||||
*/
|
||||
@UnstableApi
|
||||
|
@ -20,7 +20,6 @@ import androidx.media3.common.C;
|
||||
import androidx.media3.common.Format;
|
||||
import androidx.media3.common.Timeline;
|
||||
import androidx.media3.common.TrackGroup;
|
||||
import androidx.media3.common.TrackSelection;
|
||||
import androidx.media3.common.util.Log;
|
||||
import androidx.media3.common.util.UnstableApi;
|
||||
import androidx.media3.exoplayer.source.MediaSource.MediaPeriodId;
|
||||
|
@ -18,7 +18,6 @@ package androidx.media3.exoplayer.trackselection;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.media3.common.C;
|
||||
import androidx.media3.common.TrackGroup;
|
||||
import androidx.media3.common.TrackSelection;
|
||||
import androidx.media3.common.util.UnstableApi;
|
||||
import androidx.media3.exoplayer.source.chunk.MediaChunk;
|
||||
import androidx.media3.exoplayer.source.chunk.MediaChunkIterator;
|
||||
|
@ -31,7 +31,6 @@ import androidx.media3.common.C;
|
||||
import androidx.media3.common.C.FormatSupport;
|
||||
import androidx.media3.common.Timeline;
|
||||
import androidx.media3.common.TrackGroup;
|
||||
import androidx.media3.common.TrackGroupArray;
|
||||
import androidx.media3.common.TracksInfo;
|
||||
import androidx.media3.common.util.UnstableApi;
|
||||
import androidx.media3.common.util.Util;
|
||||
@ -42,6 +41,7 @@ import androidx.media3.exoplayer.RendererCapabilities.AdaptiveSupport;
|
||||
import androidx.media3.exoplayer.RendererCapabilities.Capabilities;
|
||||
import androidx.media3.exoplayer.RendererConfiguration;
|
||||
import androidx.media3.exoplayer.source.MediaSource.MediaPeriodId;
|
||||
import androidx.media3.exoplayer.source.TrackGroupArray;
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
|
@ -13,11 +13,14 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package androidx.media3.common;
|
||||
package androidx.media3.exoplayer.trackselection;
|
||||
|
||||
import static java.lang.annotation.ElementType.TYPE_USE;
|
||||
|
||||
import androidx.annotation.IntDef;
|
||||
import androidx.media3.common.C;
|
||||
import androidx.media3.common.Format;
|
||||
import androidx.media3.common.TrackGroup;
|
||||
import androidx.media3.common.util.UnstableApi;
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.Retention;
|
@ -13,7 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package androidx.media3.common;
|
||||
package androidx.media3.exoplayer.trackselection;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.media3.common.util.UnstableApi;
|
@ -19,12 +19,11 @@ import android.os.SystemClock;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.media3.common.C;
|
||||
import androidx.media3.common.TrackGroup;
|
||||
import androidx.media3.common.TrackGroupArray;
|
||||
import androidx.media3.common.TrackSelection;
|
||||
import androidx.media3.common.TracksInfo;
|
||||
import androidx.media3.common.TracksInfo.TrackGroupInfo;
|
||||
import androidx.media3.common.util.UnstableApi;
|
||||
import androidx.media3.exoplayer.RendererCapabilities;
|
||||
import androidx.media3.exoplayer.source.TrackGroupArray;
|
||||
import androidx.media3.exoplayer.trackselection.DefaultTrackSelector.SelectionOverride;
|
||||
import androidx.media3.exoplayer.trackselection.ExoTrackSelection.Definition;
|
||||
import androidx.media3.exoplayer.upstream.LoadErrorHandlingPolicy;
|
||||
|
@ -17,8 +17,6 @@ package androidx.media3.exoplayer.trackselection;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.media3.common.Timeline;
|
||||
import androidx.media3.common.TrackGroupArray;
|
||||
import androidx.media3.common.TrackSelection;
|
||||
import androidx.media3.common.TrackSelectionParameters;
|
||||
import androidx.media3.common.util.Assertions;
|
||||
import androidx.media3.common.util.UnstableApi;
|
||||
@ -28,6 +26,7 @@ import androidx.media3.exoplayer.Renderer;
|
||||
import androidx.media3.exoplayer.RendererCapabilities;
|
||||
import androidx.media3.exoplayer.RendererConfiguration;
|
||||
import androidx.media3.exoplayer.source.MediaSource.MediaPeriodId;
|
||||
import androidx.media3.exoplayer.source.TrackGroupArray;
|
||||
import androidx.media3.exoplayer.upstream.BandwidthMeter;
|
||||
|
||||
/**
|
||||
|
@ -286,19 +286,19 @@ public class EventLogger implements AnalyticsListener {
|
||||
}
|
||||
// TODO: Replace this with an override of onMediaMetadataChanged.
|
||||
// Log metadata for at most one of the selected tracks.
|
||||
for (int groupIndex = 0; groupIndex < trackGroupInfos.size(); groupIndex++) {
|
||||
boolean loggedMetadata = false;
|
||||
for (int groupIndex = 0; !loggedMetadata && groupIndex < trackGroupInfos.size(); groupIndex++) {
|
||||
TracksInfo.TrackGroupInfo trackGroupInfo = trackGroupInfos.get(groupIndex);
|
||||
TrackGroup trackGroup = trackGroupInfo.getTrackGroup();
|
||||
for (int trackIndex = 0; trackIndex < trackGroup.length; trackIndex++) {
|
||||
if (!trackGroupInfo.isTrackSelected(trackIndex)) {
|
||||
continue;
|
||||
}
|
||||
@Nullable Metadata metadata = trackGroup.getFormat(trackIndex).metadata;
|
||||
if (metadata != null) {
|
||||
logd(" Metadata [");
|
||||
printMetadata(metadata, " ");
|
||||
logd(" ]");
|
||||
break;
|
||||
for (int trackIndex = 0; !loggedMetadata && trackIndex < trackGroup.length; trackIndex++) {
|
||||
if (trackGroupInfo.isTrackSelected(trackIndex)) {
|
||||
@Nullable Metadata metadata = trackGroup.getFormat(trackIndex).metadata;
|
||||
if (metadata != null && metadata.length() > 0) {
|
||||
logd(" Metadata [");
|
||||
printMetadata(metadata, " ");
|
||||
logd(" ]");
|
||||
loggedMetadata = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -18,9 +18,9 @@ package androidx.media3.exoplayer;
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import androidx.media3.common.C;
|
||||
import androidx.media3.common.TrackGroupArray;
|
||||
import androidx.media3.common.util.Util;
|
||||
import androidx.media3.exoplayer.DefaultLoadControl.Builder;
|
||||
import androidx.media3.exoplayer.source.TrackGroupArray;
|
||||
import androidx.media3.exoplayer.trackselection.ExoTrackSelection;
|
||||
import androidx.media3.exoplayer.upstream.DefaultAllocator;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
|
@ -108,7 +108,6 @@ import androidx.media3.common.Player.PositionInfo;
|
||||
import androidx.media3.common.Timeline;
|
||||
import androidx.media3.common.Timeline.Window;
|
||||
import androidx.media3.common.TrackGroup;
|
||||
import androidx.media3.common.TrackGroupArray;
|
||||
import androidx.media3.common.TracksInfo;
|
||||
import androidx.media3.common.TracksInfo.TrackGroupInfo;
|
||||
import androidx.media3.common.util.Assertions;
|
||||
@ -127,6 +126,7 @@ import androidx.media3.exoplayer.source.MediaSource;
|
||||
import androidx.media3.exoplayer.source.MediaSource.MediaPeriodId;
|
||||
import androidx.media3.exoplayer.source.MediaSourceEventListener;
|
||||
import androidx.media3.exoplayer.source.SinglePeriodTimeline;
|
||||
import androidx.media3.exoplayer.source.TrackGroupArray;
|
||||
import androidx.media3.exoplayer.source.ads.ServerSideAdInsertionMediaSource;
|
||||
import androidx.media3.exoplayer.trackselection.DefaultTrackSelector;
|
||||
import androidx.media3.exoplayer.upstream.Allocation;
|
||||
|
@ -26,7 +26,7 @@ import android.net.Uri;
|
||||
import androidx.media3.common.C;
|
||||
import androidx.media3.common.MediaItem;
|
||||
import androidx.media3.common.MimeTypes;
|
||||
import androidx.media3.common.TrackGroupArray;
|
||||
import androidx.media3.exoplayer.source.TrackGroupArray;
|
||||
import androidx.media3.extractor.metadata.mp4.MdtaMetadataEntry;
|
||||
import androidx.media3.extractor.metadata.mp4.MotionPhotoMetadata;
|
||||
import androidx.media3.extractor.metadata.mp4.SlowMotionData;
|
||||
|
@ -27,7 +27,6 @@ import androidx.media3.common.MimeTypes;
|
||||
import androidx.media3.common.StreamKey;
|
||||
import androidx.media3.common.Timeline;
|
||||
import androidx.media3.common.TrackGroup;
|
||||
import androidx.media3.common.TrackGroupArray;
|
||||
import androidx.media3.common.TrackSelectionOverride;
|
||||
import androidx.media3.common.TrackSelectionParameters;
|
||||
import androidx.media3.exoplayer.Renderer;
|
||||
@ -35,6 +34,7 @@ import androidx.media3.exoplayer.RenderersFactory;
|
||||
import androidx.media3.exoplayer.offline.DownloadHelper.Callback;
|
||||
import androidx.media3.exoplayer.source.MediaPeriod;
|
||||
import androidx.media3.exoplayer.source.MediaSourceEventListener.EventDispatcher;
|
||||
import androidx.media3.exoplayer.source.TrackGroupArray;
|
||||
import androidx.media3.exoplayer.trackselection.DefaultTrackSelector;
|
||||
import androidx.media3.exoplayer.trackselection.ExoTrackSelection;
|
||||
import androidx.media3.exoplayer.trackselection.MappingTrackSelector.MappedTrackInfo;
|
||||
|
@ -23,7 +23,6 @@ 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.media3.common.TrackGroupArray;
|
||||
import androidx.media3.decoder.DecoderInputBuffer;
|
||||
import androidx.media3.exoplayer.FormatHolder;
|
||||
import androidx.media3.exoplayer.drm.DrmSessionEventListener;
|
||||
|
@ -13,10 +13,13 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package androidx.media3.common;
|
||||
package androidx.media3.exoplayer.source;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import androidx.media3.common.Format;
|
||||
import androidx.media3.common.MimeTypes;
|
||||
import androidx.media3.common.TrackGroup;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
@ -24,7 +24,6 @@ import androidx.media3.common.Format;
|
||||
import androidx.media3.common.MimeTypes;
|
||||
import androidx.media3.common.Timeline;
|
||||
import androidx.media3.common.TrackGroup;
|
||||
import androidx.media3.common.TrackSelection;
|
||||
import androidx.media3.datasource.DataSpec;
|
||||
import androidx.media3.exoplayer.source.MediaSource;
|
||||
import androidx.media3.exoplayer.source.chunk.BaseMediaChunkIterator;
|
||||
|
@ -40,8 +40,6 @@ import androidx.media3.common.Format;
|
||||
import androidx.media3.common.MimeTypes;
|
||||
import androidx.media3.common.Timeline;
|
||||
import androidx.media3.common.TrackGroup;
|
||||
import androidx.media3.common.TrackGroupArray;
|
||||
import androidx.media3.common.TrackSelection;
|
||||
import androidx.media3.common.TrackSelectionOverride;
|
||||
import androidx.media3.common.TracksInfo;
|
||||
import androidx.media3.common.util.Util;
|
||||
@ -50,6 +48,7 @@ import androidx.media3.exoplayer.RendererCapabilities;
|
||||
import androidx.media3.exoplayer.RendererCapabilities.Capabilities;
|
||||
import androidx.media3.exoplayer.RendererConfiguration;
|
||||
import androidx.media3.exoplayer.source.MediaSource.MediaPeriodId;
|
||||
import androidx.media3.exoplayer.source.TrackGroupArray;
|
||||
import androidx.media3.exoplayer.trackselection.DefaultTrackSelector.Parameters;
|
||||
import androidx.media3.exoplayer.trackselection.DefaultTrackSelector.ParametersBuilder;
|
||||
import androidx.media3.exoplayer.trackselection.DefaultTrackSelector.SelectionOverride;
|
||||
|
@ -24,7 +24,6 @@ import androidx.media3.common.Format;
|
||||
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.Util;
|
||||
import androidx.media3.exoplayer.ExoPlaybackException;
|
||||
import androidx.media3.exoplayer.RendererCapabilities;
|
||||
@ -32,6 +31,7 @@ import androidx.media3.exoplayer.RendererCapabilities.AdaptiveSupport;
|
||||
import androidx.media3.exoplayer.RendererCapabilities.Capabilities;
|
||||
import androidx.media3.exoplayer.RendererConfiguration;
|
||||
import androidx.media3.exoplayer.source.MediaSource.MediaPeriodId;
|
||||
import androidx.media3.exoplayer.source.TrackGroupArray;
|
||||
import androidx.media3.test.utils.FakeTimeline;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import org.junit.BeforeClass;
|
||||
|
@ -32,10 +32,9 @@ import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import androidx.media3.common.Format;
|
||||
import androidx.media3.common.TrackGroup;
|
||||
import androidx.media3.common.TrackGroupArray;
|
||||
import androidx.media3.common.TrackSelection;
|
||||
import androidx.media3.common.TracksInfo;
|
||||
import androidx.media3.common.TracksInfo.TrackGroupInfo;
|
||||
import androidx.media3.exoplayer.source.TrackGroupArray;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import java.util.List;
|
||||
|
@ -20,10 +20,10 @@ import static org.junit.Assert.fail;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.media3.common.Timeline;
|
||||
import androidx.media3.common.TrackGroupArray;
|
||||
import androidx.media3.exoplayer.ExoPlaybackException;
|
||||
import androidx.media3.exoplayer.RendererCapabilities;
|
||||
import androidx.media3.exoplayer.source.MediaSource.MediaPeriodId;
|
||||
import androidx.media3.exoplayer.source.TrackGroupArray;
|
||||
import androidx.media3.exoplayer.trackselection.TrackSelector.InvalidationListener;
|
||||
import androidx.media3.exoplayer.upstream.BandwidthMeter;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
|
@ -28,7 +28,6 @@ import androidx.media3.common.Format;
|
||||
import androidx.media3.common.MimeTypes;
|
||||
import androidx.media3.common.StreamKey;
|
||||
import androidx.media3.common.TrackGroup;
|
||||
import androidx.media3.common.TrackGroupArray;
|
||||
import androidx.media3.common.util.Util;
|
||||
import androidx.media3.datasource.TransferListener;
|
||||
import androidx.media3.exoplayer.SeekParameters;
|
||||
@ -50,6 +49,7 @@ import androidx.media3.exoplayer.source.MediaSourceEventListener;
|
||||
import androidx.media3.exoplayer.source.MediaSourceEventListener.EventDispatcher;
|
||||
import androidx.media3.exoplayer.source.SampleStream;
|
||||
import androidx.media3.exoplayer.source.SequenceableLoader;
|
||||
import androidx.media3.exoplayer.source.TrackGroupArray;
|
||||
import androidx.media3.exoplayer.source.chunk.ChunkSampleStream;
|
||||
import androidx.media3.exoplayer.source.chunk.ChunkSampleStream.EmbeddedSampleStream;
|
||||
import androidx.media3.exoplayer.trackselection.ExoTrackSelection;
|
||||
|
@ -24,7 +24,6 @@ import androidx.media3.common.util.Assertions;
|
||||
import androidx.media3.common.util.UnstableApi;
|
||||
import androidx.media3.datasource.DataSource;
|
||||
import androidx.media3.datasource.DataSpec;
|
||||
import androidx.media3.datasource.HttpDataSource;
|
||||
import androidx.media3.exoplayer.dash.manifest.DashManifest;
|
||||
import androidx.media3.exoplayer.dash.manifest.DashManifestParser;
|
||||
import androidx.media3.exoplayer.dash.manifest.Period;
|
||||
@ -85,7 +84,7 @@ public final class DashUtil {
|
||||
/**
|
||||
* Loads a DASH manifest.
|
||||
*
|
||||
* @param dataSource The {@link HttpDataSource} from which the manifest should be read.
|
||||
* @param dataSource The {@link DataSource} from which the manifest should be read.
|
||||
* @param uri The {@link Uri} of the manifest to be read.
|
||||
* @return An instance of {@link DashManifest}.
|
||||
* @throws IOException Thrown when there is an error while loading.
|
||||
@ -97,7 +96,7 @@ public final class DashUtil {
|
||||
/**
|
||||
* Loads a {@link Format} for acquiring keys for a given period in a DASH manifest.
|
||||
*
|
||||
* @param dataSource The {@link HttpDataSource} from which data should be loaded.
|
||||
* @param dataSource The {@link DataSource} from which data should be loaded.
|
||||
* @param period The {@link Period}.
|
||||
* @return The loaded {@link Format}, or null if none is defined.
|
||||
* @throws IOException Thrown when there is an error while loading.
|
||||
|
@ -1189,41 +1189,41 @@ public class DashManifestParser extends DefaultHandler
|
||||
xpp.nextToken();
|
||||
while (!XmlPullParserUtil.isEndTag(xpp, "Event")) {
|
||||
switch (xpp.getEventType()) {
|
||||
case (XmlPullParser.START_DOCUMENT):
|
||||
case XmlPullParser.START_DOCUMENT:
|
||||
xmlSerializer.startDocument(null, false);
|
||||
break;
|
||||
case (XmlPullParser.END_DOCUMENT):
|
||||
case XmlPullParser.END_DOCUMENT:
|
||||
xmlSerializer.endDocument();
|
||||
break;
|
||||
case (XmlPullParser.START_TAG):
|
||||
case XmlPullParser.START_TAG:
|
||||
xmlSerializer.startTag(xpp.getNamespace(), xpp.getName());
|
||||
for (int i = 0; i < xpp.getAttributeCount(); i++) {
|
||||
xmlSerializer.attribute(
|
||||
xpp.getAttributeNamespace(i), xpp.getAttributeName(i), xpp.getAttributeValue(i));
|
||||
}
|
||||
break;
|
||||
case (XmlPullParser.END_TAG):
|
||||
case XmlPullParser.END_TAG:
|
||||
xmlSerializer.endTag(xpp.getNamespace(), xpp.getName());
|
||||
break;
|
||||
case (XmlPullParser.TEXT):
|
||||
case XmlPullParser.TEXT:
|
||||
xmlSerializer.text(xpp.getText());
|
||||
break;
|
||||
case (XmlPullParser.CDSECT):
|
||||
case XmlPullParser.CDSECT:
|
||||
xmlSerializer.cdsect(xpp.getText());
|
||||
break;
|
||||
case (XmlPullParser.ENTITY_REF):
|
||||
case XmlPullParser.ENTITY_REF:
|
||||
xmlSerializer.entityRef(xpp.getText());
|
||||
break;
|
||||
case (XmlPullParser.IGNORABLE_WHITESPACE):
|
||||
case XmlPullParser.IGNORABLE_WHITESPACE:
|
||||
xmlSerializer.ignorableWhitespace(xpp.getText());
|
||||
break;
|
||||
case (XmlPullParser.PROCESSING_INSTRUCTION):
|
||||
case XmlPullParser.PROCESSING_INSTRUCTION:
|
||||
xmlSerializer.processingInstruction(xpp.getText());
|
||||
break;
|
||||
case (XmlPullParser.COMMENT):
|
||||
case XmlPullParser.COMMENT:
|
||||
xmlSerializer.comment(xpp.getText());
|
||||
break;
|
||||
case (XmlPullParser.DOCDECL):
|
||||
case XmlPullParser.DOCDECL:
|
||||
xmlSerializer.docdecl(xpp.getText());
|
||||
break;
|
||||
default: // fall out
|
||||
|
@ -21,7 +21,6 @@ import android.net.Uri;
|
||||
import androidx.media3.common.Format;
|
||||
import androidx.media3.common.MimeTypes;
|
||||
import androidx.media3.common.TrackGroup;
|
||||
import androidx.media3.common.TrackGroupArray;
|
||||
import androidx.media3.datasource.TransferListener;
|
||||
import androidx.media3.exoplayer.analytics.PlayerId;
|
||||
import androidx.media3.exoplayer.dash.PlayerEmsgHandler.PlayerEmsgCallback;
|
||||
@ -33,6 +32,7 @@ import androidx.media3.exoplayer.drm.DrmSessionManager;
|
||||
import androidx.media3.exoplayer.source.CompositeSequenceableLoaderFactory;
|
||||
import androidx.media3.exoplayer.source.MediaSource.MediaPeriodId;
|
||||
import androidx.media3.exoplayer.source.MediaSourceEventListener;
|
||||
import androidx.media3.exoplayer.source.TrackGroupArray;
|
||||
import androidx.media3.exoplayer.upstream.Allocator;
|
||||
import androidx.media3.exoplayer.upstream.LoadErrorHandlingPolicy;
|
||||
import androidx.media3.exoplayer.upstream.LoaderErrorThrower;
|
||||
|
@ -25,7 +25,6 @@ import androidx.media3.common.Metadata;
|
||||
import androidx.media3.common.MimeTypes;
|
||||
import androidx.media3.common.StreamKey;
|
||||
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;
|
||||
@ -45,6 +44,7 @@ import androidx.media3.exoplayer.source.MediaPeriod;
|
||||
import androidx.media3.exoplayer.source.MediaSourceEventListener.EventDispatcher;
|
||||
import androidx.media3.exoplayer.source.SampleStream;
|
||||
import androidx.media3.exoplayer.source.SequenceableLoader;
|
||||
import androidx.media3.exoplayer.source.TrackGroupArray;
|
||||
import androidx.media3.exoplayer.trackselection.ExoTrackSelection;
|
||||
import androidx.media3.exoplayer.upstream.Allocator;
|
||||
import androidx.media3.exoplayer.upstream.LoadErrorHandlingPolicy;
|
||||
|
@ -33,7 +33,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.Log;
|
||||
import androidx.media3.common.util.ParsableByteArray;
|
||||
@ -53,6 +52,7 @@ import androidx.media3.exoplayer.source.SampleQueue.UpstreamFormatChangedListene
|
||||
import androidx.media3.exoplayer.source.SampleStream;
|
||||
import androidx.media3.exoplayer.source.SampleStream.ReadFlags;
|
||||
import androidx.media3.exoplayer.source.SequenceableLoader;
|
||||
import androidx.media3.exoplayer.source.TrackGroupArray;
|
||||
import androidx.media3.exoplayer.source.chunk.Chunk;
|
||||
import androidx.media3.exoplayer.source.chunk.MediaChunkIterator;
|
||||
import androidx.media3.exoplayer.trackselection.ExoTrackSelection;
|
||||
|
@ -18,11 +18,12 @@ package androidx.media3.exoplayer.ima;
|
||||
import static androidx.media3.common.util.Assertions.checkNotNull;
|
||||
import static androidx.media3.common.util.Assertions.checkState;
|
||||
import static androidx.media3.common.util.Util.msToUs;
|
||||
import static androidx.media3.common.util.Util.secToUs;
|
||||
import static androidx.media3.common.util.Util.sum;
|
||||
import static androidx.media3.common.util.Util.usToMs;
|
||||
import static androidx.media3.exoplayer.ima.ImaUtil.expandAdGroupPlaceholder;
|
||||
import static androidx.media3.exoplayer.ima.ImaUtil.getAdGroupAndIndexInMultiPeriodWindow;
|
||||
import static androidx.media3.exoplayer.ima.ImaUtil.secToMsRounded;
|
||||
import static androidx.media3.exoplayer.ima.ImaUtil.secToUsRounded;
|
||||
import static androidx.media3.exoplayer.ima.ImaUtil.splitAdPlaybackStateForPeriods;
|
||||
import static androidx.media3.exoplayer.ima.ImaUtil.updateAdDurationAndPropagate;
|
||||
import static androidx.media3.exoplayer.ima.ImaUtil.updateAdDurationInAdGroup;
|
||||
@ -661,19 +662,29 @@ public final class ImaServerSideAdInsertionMediaSource extends CompositeMediaSou
|
||||
|
||||
private static AdPlaybackState setVodAdGroupPlaceholders(
|
||||
List<CuePoint> cuePoints, AdPlaybackState adPlaybackState) {
|
||||
// TODO(b/192231683) Use getEndTimeMs()/getStartTimeMs() after jar target was removed
|
||||
for (int i = 0; i < cuePoints.size(); i++) {
|
||||
CuePoint cuePoint = cuePoints.get(i);
|
||||
long fromPositionUs = msToUs(secToMsRounded(cuePoint.getStartTime()));
|
||||
adPlaybackState =
|
||||
addAdGroupToAdPlaybackState(
|
||||
adPlaybackState,
|
||||
/* fromPositionUs= */ secToUs(cuePoint.getStartTime()),
|
||||
/* fromPositionUs= */ fromPositionUs,
|
||||
/* contentResumeOffsetUs= */ 0,
|
||||
// TODO(b/192231683) Use getEndTimeMs()/getStartTimeMs() after jar target was removed
|
||||
/* adDurationsUs...= */ secToUs(cuePoint.getEndTime() - cuePoint.getStartTime()));
|
||||
/* adDurationsUs...= */ getAdDuration(
|
||||
/* startTimeSeconds= */ cuePoint.getStartTime(),
|
||||
/* endTimeSeconds= */ cuePoint.getEndTime()));
|
||||
}
|
||||
return adPlaybackState;
|
||||
}
|
||||
|
||||
private static long getAdDuration(double startTimeSeconds, double endTimeSeconds) {
|
||||
// startTimeSeconds and endTimeSeconds that are coming from the SDK, only have a precision of
|
||||
// milliseconds so everything that is below a millisecond can be safely considered as coming
|
||||
// from rounding issues.
|
||||
return msToUs(secToMsRounded(endTimeSeconds - startTimeSeconds));
|
||||
}
|
||||
|
||||
private static AdPlaybackState setVodAdInPlaceholder(Ad ad, AdPlaybackState adPlaybackState) {
|
||||
AdPodInfo adPodInfo = ad.getAdPodInfo();
|
||||
// Handle post rolls that have a podIndex of -1.
|
||||
@ -685,9 +696,9 @@ public final class ImaServerSideAdInsertionMediaSource extends CompositeMediaSou
|
||||
adPlaybackState =
|
||||
expandAdGroupPlaceholder(
|
||||
adGroupIndex,
|
||||
/* adGroupDurationUs= */ secToUs(adPodInfo.getMaxDuration()),
|
||||
/* adGroupDurationUs= */ msToUs(secToMsRounded(adPodInfo.getMaxDuration())),
|
||||
adIndexInAdGroup,
|
||||
/* adDurationUs= */ secToUs(ad.getDuration()),
|
||||
/* adDurationUs= */ msToUs(secToMsRounded(ad.getDuration())),
|
||||
/* adsInAdGroupCount= */ adPodInfo.getTotalAds(),
|
||||
adPlaybackState);
|
||||
} else if (adIndexInAdGroup < adGroup.count - 1) {
|
||||
@ -695,7 +706,7 @@ public final class ImaServerSideAdInsertionMediaSource extends CompositeMediaSou
|
||||
updateAdDurationInAdGroup(
|
||||
adGroupIndex,
|
||||
adIndexInAdGroup,
|
||||
/* adDurationUs= */ secToUs(ad.getDuration()),
|
||||
/* adDurationUs= */ msToUs(secToMsRounded(ad.getDuration())),
|
||||
adPlaybackState);
|
||||
}
|
||||
return adPlaybackState;
|
||||
@ -704,7 +715,7 @@ public final class ImaServerSideAdInsertionMediaSource extends CompositeMediaSou
|
||||
private AdPlaybackState addLiveAdBreak(
|
||||
Ad ad, long currentPeriodPositionUs, AdPlaybackState adPlaybackState) {
|
||||
AdPodInfo adPodInfo = ad.getAdPodInfo();
|
||||
long adDurationUs = secToUs(ad.getDuration());
|
||||
long adDurationUs = secToUsRounded(ad.getDuration());
|
||||
int adIndexInAdGroup = adPodInfo.getAdPosition() - 1;
|
||||
// TODO(b/208398934) Support seeking backwards.
|
||||
if (adIndexInAdGroup == 0 || adPlaybackState.adGroupCount == 1) {
|
||||
@ -718,7 +729,7 @@ public final class ImaServerSideAdInsertionMediaSource extends CompositeMediaSou
|
||||
new long[adCount],
|
||||
adIndexInAdGroup,
|
||||
adDurationUs,
|
||||
secToUs(adPodInfo.getMaxDuration()));
|
||||
msToUs(secToMsRounded(adPodInfo.getMaxDuration())));
|
||||
adPlaybackState =
|
||||
addAdGroupToAdPlaybackState(
|
||||
adPlaybackState,
|
||||
|
@ -54,7 +54,10 @@ import com.google.ads.interactivemedia.v3.api.player.VideoAdPlayer;
|
||||
import com.google.ads.interactivemedia.v3.api.player.VideoProgressUpdate;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.math.DoubleMath;
|
||||
import java.io.IOException;
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
@ -398,17 +401,13 @@ import java.util.Set;
|
||||
long elapsedAdGroupAdDurationUs = 0;
|
||||
for (int j = periodIndex; j < contentTimeline.getPeriodCount(); j++) {
|
||||
contentTimeline.getPeriod(j, period, /* setIds= */ true);
|
||||
// TODO(b/192231683) Remove subtracted US from ad group time when we can upgrade the SDK.
|
||||
// Subtract one microsecond to work around rounding errors with adGroup.timeUs.
|
||||
if (totalElapsedContentDurationUs < adGroup.timeUs - 1) {
|
||||
if (totalElapsedContentDurationUs < adGroup.timeUs) {
|
||||
// Period starts before the ad group, so it is a content period.
|
||||
adPlaybackStates.put(checkNotNull(period.uid), contentOnlyAdPlaybackState);
|
||||
totalElapsedContentDurationUs += period.durationUs;
|
||||
} else {
|
||||
long periodStartUs = totalElapsedContentDurationUs + elapsedAdGroupAdDurationUs;
|
||||
// TODO(b/192231683) Remove additional US when we can upgrade the SDK.
|
||||
// Add one microsecond to work around rounding errors with adGroup.timeUs.
|
||||
if (periodStartUs + period.durationUs <= adGroup.timeUs + adGroupDurationUs + 1) {
|
||||
if (periodStartUs + period.durationUs <= adGroup.timeUs + adGroupDurationUs) {
|
||||
// The period ends before the end of the ad group, so it is an ad period (Note: A VOD ad
|
||||
// reported by the IMA SDK spans multiple periods before the LOADED event arrives).
|
||||
adPlaybackStates.put(
|
||||
@ -490,16 +489,12 @@ import java.util.Set;
|
||||
long elapsedAdGroupAdDurationUs = 0;
|
||||
for (int j = periodIndex; j < contentTimeline.getPeriodCount(); j++) {
|
||||
contentTimeline.getPeriod(j, period, /* setIds= */ true);
|
||||
// TODO(b/192231683) Remove subtracted US from ad group time when we can upgrade the SDK.
|
||||
// Subtract one microsecond to work around rounding errors with adGroup.timeUs.
|
||||
if (totalElapsedContentDurationUs < adGroup.timeUs - 1) {
|
||||
if (totalElapsedContentDurationUs < adGroup.timeUs) {
|
||||
// Period starts before the ad group, so it is a content period.
|
||||
totalElapsedContentDurationUs += period.durationUs;
|
||||
} else {
|
||||
long periodStartUs = totalElapsedContentDurationUs + elapsedAdGroupAdDurationUs;
|
||||
// TODO(b/192231683) Remove additional US when we can upgrade the SDK.
|
||||
// Add one microsecond to work around rounding errors with adGroup.timeUs.
|
||||
if (periodStartUs + period.durationUs <= adGroup.timeUs + adGroupDurationUs + 1) {
|
||||
if (periodStartUs + period.durationUs <= adGroup.timeUs + adGroupDurationUs) {
|
||||
// The period ends before the end of the ad group, so it is an ad period.
|
||||
if (j == adPeriodIndex) {
|
||||
return new Pair<>(/* adGroupIndex= */ i, adIndexInAdGroup);
|
||||
@ -518,5 +513,31 @@ import java.util.Set;
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a time in seconds to the corresponding time in microseconds.
|
||||
*
|
||||
* <p>Fractional values are rounded to the nearest microsecond using {@link RoundingMode#HALF_UP}.
|
||||
*
|
||||
* @param timeSec The time in seconds.
|
||||
* @return The corresponding time in microseconds.
|
||||
*/
|
||||
public static long secToUsRounded(double timeSec) {
|
||||
return DoubleMath.roundToLong(
|
||||
BigDecimal.valueOf(timeSec).scaleByPowerOfTen(6).doubleValue(), RoundingMode.HALF_UP);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a time in seconds to the corresponding time in milliseconds.
|
||||
*
|
||||
* <p>Fractional values are rounded to the nearest millisecond using {@link RoundingMode#HALF_UP}.
|
||||
*
|
||||
* @param timeSec The time in seconds.
|
||||
* @return The corresponding time in milliseconds.
|
||||
*/
|
||||
public static long secToMsRounded(double timeSec) {
|
||||
return DoubleMath.roundToLong(
|
||||
BigDecimal.valueOf(timeSec).scaleByPowerOfTen(3).doubleValue(), RoundingMode.HALF_UP);
|
||||
}
|
||||
|
||||
private ImaUtil() {}
|
||||
}
|
||||
|
@ -23,13 +23,13 @@ import androidx.media3.common.MediaItem;
|
||||
import androidx.media3.common.PlaybackException;
|
||||
import androidx.media3.common.Player;
|
||||
import androidx.media3.common.Timeline;
|
||||
import androidx.media3.common.TrackSelectionArray;
|
||||
import androidx.media3.common.TrackSelectionParameters;
|
||||
import androidx.media3.common.TracksInfo;
|
||||
import androidx.media3.common.util.Clock;
|
||||
import androidx.media3.common.util.ListenerSet;
|
||||
import androidx.media3.common.util.Util;
|
||||
import androidx.media3.exoplayer.ExoPlayer;
|
||||
import androidx.media3.exoplayer.trackselection.TrackSelectionArray;
|
||||
import androidx.media3.test.utils.StubExoPlayer;
|
||||
|
||||
/** A fake {@link ExoPlayer} for testing content/ad playback. */
|
||||
@ -79,7 +79,7 @@ import androidx.media3.test.utils.StubExoPlayer;
|
||||
PositionInfo oldPosition =
|
||||
new PositionInfo(
|
||||
windowUid,
|
||||
/* windowIndex= */ 0,
|
||||
/* mediaItemIndex= */ 0,
|
||||
mediaItem,
|
||||
periodUid,
|
||||
/* periodIndex= */ 0,
|
||||
@ -97,7 +97,7 @@ import androidx.media3.test.utils.StubExoPlayer;
|
||||
PositionInfo newPosition =
|
||||
new PositionInfo(
|
||||
windowUid,
|
||||
/* windowIndex= */ 0,
|
||||
/* mediaItemIndex= */ 0,
|
||||
mediaItem,
|
||||
periodUid,
|
||||
/* periodIndex= */ 0,
|
||||
@ -128,7 +128,7 @@ import androidx.media3.test.utils.StubExoPlayer;
|
||||
PositionInfo oldPosition =
|
||||
new PositionInfo(
|
||||
windowUid,
|
||||
/* windowIndex= */ 0,
|
||||
/* mediaItemIndex= */ 0,
|
||||
mediaItem,
|
||||
periodUid,
|
||||
/* periodIndex= */ 0,
|
||||
@ -146,7 +146,7 @@ import androidx.media3.test.utils.StubExoPlayer;
|
||||
PositionInfo newPosition =
|
||||
new PositionInfo(
|
||||
windowUid,
|
||||
/* windowIndex= */ 0,
|
||||
/* mediaItemIndex= */ 0,
|
||||
mediaItem,
|
||||
periodUid,
|
||||
/* periodIndex= */ 0,
|
||||
|
@ -28,8 +28,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.TrackSelection;
|
||||
import androidx.media3.common.util.UnstableApi;
|
||||
import androidx.media3.common.util.Util;
|
||||
import androidx.media3.decoder.DecoderInputBuffer;
|
||||
@ -44,7 +42,9 @@ import androidx.media3.exoplayer.source.SampleQueue.UpstreamFormatChangedListene
|
||||
import androidx.media3.exoplayer.source.SampleStream;
|
||||
import androidx.media3.exoplayer.source.SampleStream.ReadDataResult;
|
||||
import androidx.media3.exoplayer.source.SampleStream.ReadFlags;
|
||||
import androidx.media3.exoplayer.source.TrackGroupArray;
|
||||
import androidx.media3.exoplayer.trackselection.ExoTrackSelection;
|
||||
import androidx.media3.exoplayer.trackselection.TrackSelection;
|
||||
import androidx.media3.exoplayer.upstream.Allocator;
|
||||
import androidx.media3.exoplayer.upstream.Loader;
|
||||
import androidx.media3.exoplayer.upstream.Loader.Loadable;
|
||||
|
@ -20,7 +20,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.datasource.TransferListener;
|
||||
import androidx.media3.exoplayer.SeekParameters;
|
||||
import androidx.media3.exoplayer.drm.DrmSessionEventListener;
|
||||
@ -31,6 +30,7 @@ import androidx.media3.exoplayer.source.MediaPeriod;
|
||||
import androidx.media3.exoplayer.source.MediaSourceEventListener;
|
||||
import androidx.media3.exoplayer.source.SampleStream;
|
||||
import androidx.media3.exoplayer.source.SequenceableLoader;
|
||||
import androidx.media3.exoplayer.source.TrackGroupArray;
|
||||
import androidx.media3.exoplayer.source.chunk.ChunkSampleStream;
|
||||
import androidx.media3.exoplayer.trackselection.ExoTrackSelection;
|
||||
import androidx.media3.exoplayer.upstream.Allocator;
|
||||
|
@ -31,6 +31,21 @@ public final class NalUnitUtil {
|
||||
|
||||
private static final String TAG = "NalUnitUtil";
|
||||
|
||||
/** Coded slice of a non-IDR picture. */
|
||||
public static final int NAL_UNIT_TYPE_NON_IDR = 1;
|
||||
/** Coded slice data partition A. */
|
||||
public static final int NAL_UNIT_TYPE_PARTITION_A = 2;
|
||||
/** Coded slice of an IDR picture. */
|
||||
public static final int NAL_UNIT_TYPE_IDR = 5;
|
||||
/** Supplemental enhancement information. */
|
||||
public static final int NAL_UNIT_TYPE_SEI = 6;
|
||||
/** Sequence parameter set. */
|
||||
public static final int NAL_UNIT_TYPE_SPS = 7;
|
||||
/** Picture parameter set. */
|
||||
public static final int NAL_UNIT_TYPE_PPS = 8;
|
||||
/** Access unit delimiter. */
|
||||
public static final int NAL_UNIT_TYPE_AUD = 9;
|
||||
|
||||
/** Holds data parsed from a H.264 sequence parameter set NAL unit. */
|
||||
public static final class SpsData {
|
||||
|
||||
@ -38,6 +53,7 @@ public final class NalUnitUtil {
|
||||
public final int constraintsFlagsAndReservedZero2Bits;
|
||||
public final int levelIdc;
|
||||
public final int seqParameterSetId;
|
||||
public final int maxNumRefFrames;
|
||||
public final int width;
|
||||
public final int height;
|
||||
public final float pixelWidthHeightRatio;
|
||||
@ -53,6 +69,7 @@ public final class NalUnitUtil {
|
||||
int constraintsFlagsAndReservedZero2Bits,
|
||||
int levelIdc,
|
||||
int seqParameterSetId,
|
||||
int maxNumRefFrames,
|
||||
int width,
|
||||
int height,
|
||||
float pixelWidthHeightRatio,
|
||||
@ -66,6 +83,7 @@ public final class NalUnitUtil {
|
||||
this.constraintsFlagsAndReservedZero2Bits = constraintsFlagsAndReservedZero2Bits;
|
||||
this.levelIdc = levelIdc;
|
||||
this.seqParameterSetId = seqParameterSetId;
|
||||
this.maxNumRefFrames = maxNumRefFrames;
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
this.pixelWidthHeightRatio = pixelWidthHeightRatio;
|
||||
@ -372,7 +390,7 @@ public final class NalUnitUtil {
|
||||
data.readUnsignedExpGolombCodedInt(); // offset_for_ref_frame[i]
|
||||
}
|
||||
}
|
||||
data.readUnsignedExpGolombCodedInt(); // max_num_ref_frames
|
||||
int maxNumRefFrames = data.readUnsignedExpGolombCodedInt(); // max_num_ref_frames
|
||||
data.skipBit(); // gaps_in_frame_num_value_allowed_flag
|
||||
|
||||
int picWidthInMbs = data.readUnsignedExpGolombCodedInt() + 1;
|
||||
@ -432,6 +450,7 @@ public final class NalUnitUtil {
|
||||
constraintsFlagsAndReservedZero2Bits,
|
||||
levelIdc,
|
||||
seqParameterSetId,
|
||||
maxNumRefFrames,
|
||||
frameWidth,
|
||||
frameHeight,
|
||||
pixelWidthHeightRatio,
|
||||
|
@ -15,7 +15,6 @@
|
||||
*/
|
||||
package androidx.media3.extractor.mp4;
|
||||
|
||||
import static androidx.media3.common.util.Assertions.checkNotNull;
|
||||
import static androidx.media3.common.util.Util.castNonNull;
|
||||
import static androidx.media3.extractor.mp4.AtomParsers.parseTraks;
|
||||
import static androidx.media3.extractor.mp4.Sniffer.BRAND_HEIC;
|
||||
@ -165,8 +164,8 @@ public final class Mp4Extractor implements Extractor, SeekMap {
|
||||
private int sampleCurrentNalBytesRemaining;
|
||||
|
||||
// Extractor outputs.
|
||||
private @MonotonicNonNull ExtractorOutput extractorOutput;
|
||||
private Mp4Track @MonotonicNonNull [] tracks;
|
||||
private ExtractorOutput extractorOutput;
|
||||
private Mp4Track[] tracks;
|
||||
|
||||
private long @MonotonicNonNull [][] accumulatedSampleSizes;
|
||||
private int firstVideoTrackIndex;
|
||||
@ -197,6 +196,8 @@ public final class Mp4Extractor implements Extractor, SeekMap {
|
||||
nalLength = new ParsableByteArray(4);
|
||||
scratch = new ParsableByteArray();
|
||||
sampleTrackIndex = C.INDEX_UNSET;
|
||||
extractorOutput = ExtractorOutput.PLACEHOLDER;
|
||||
tracks = new Mp4Track[0];
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -227,7 +228,7 @@ public final class Mp4Extractor implements Extractor, SeekMap {
|
||||
sefReader.reset();
|
||||
slowMotionMetadataEntries.clear();
|
||||
}
|
||||
} else if (tracks != null) {
|
||||
} else {
|
||||
for (Mp4Track track : tracks) {
|
||||
updateSampleIndex(track, timeUs);
|
||||
if (track.trueHdSampleRechunker != null) {
|
||||
@ -280,7 +281,7 @@ public final class Mp4Extractor implements Extractor, SeekMap {
|
||||
|
||||
@Override
|
||||
public SeekPoints getSeekPoints(long timeUs) {
|
||||
if (checkNotNull(tracks).length == 0) {
|
||||
if (tracks.length == 0) {
|
||||
return new SeekPoints(SeekPoint.START);
|
||||
}
|
||||
|
||||
@ -502,7 +503,6 @@ public final class Mp4Extractor implements Extractor, SeekMap {
|
||||
isQuickTime,
|
||||
/* modifyTrackFunction= */ track -> track);
|
||||
|
||||
ExtractorOutput extractorOutput = checkNotNull(this.extractorOutput);
|
||||
int trackCount = trackSampleTables.size();
|
||||
for (int i = 0; i < trackCount; i++) {
|
||||
TrackSampleTable trackSampleTable = trackSampleTables.get(i);
|
||||
@ -582,7 +582,7 @@ public final class Mp4Extractor implements Extractor, SeekMap {
|
||||
return RESULT_END_OF_INPUT;
|
||||
}
|
||||
}
|
||||
Mp4Track track = castNonNull(tracks)[sampleTrackIndex];
|
||||
Mp4Track track = tracks[sampleTrackIndex];
|
||||
TrackOutput trackOutput = track.trackOutput;
|
||||
int sampleIndex = track.sampleIndex;
|
||||
long position = track.sampleTable.offsets[sampleIndex];
|
||||
@ -699,7 +699,7 @@ public final class Mp4Extractor implements Extractor, SeekMap {
|
||||
long minAccumulatedBytes = Long.MAX_VALUE;
|
||||
boolean minAccumulatedBytesRequiresReload = true;
|
||||
int minAccumulatedBytesTrackIndex = C.INDEX_UNSET;
|
||||
for (int trackIndex = 0; trackIndex < castNonNull(tracks).length; trackIndex++) {
|
||||
for (int trackIndex = 0; trackIndex < tracks.length; trackIndex++) {
|
||||
Mp4Track track = tracks[trackIndex];
|
||||
int sampleIndex = track.sampleIndex;
|
||||
if (sampleIndex == track.sampleTable.sampleCount) {
|
||||
@ -744,7 +744,6 @@ public final class Mp4Extractor implements Extractor, SeekMap {
|
||||
private void processEndOfStreamReadingAtomHeader() {
|
||||
if (fileType == FILE_TYPE_HEIC && (flags & FLAG_READ_MOTION_PHOTO_METADATA) != 0) {
|
||||
// Add image track and prepare media.
|
||||
ExtractorOutput extractorOutput = checkNotNull(this.extractorOutput);
|
||||
TrackOutput trackOutput = extractorOutput.track(/* id= */ 0, C.TRACK_TYPE_IMAGE);
|
||||
@Nullable
|
||||
Metadata metadata = motionPhotoMetadata == null ? null : new Metadata(motionPhotoMetadata);
|
||||
|
@ -44,10 +44,6 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
||||
@UnstableApi
|
||||
public final class H264Reader implements ElementaryStreamReader {
|
||||
|
||||
private static final int NAL_UNIT_TYPE_SEI = 6; // Supplemental enhancement information
|
||||
private static final int NAL_UNIT_TYPE_SPS = 7; // Sequence parameter set
|
||||
private static final int NAL_UNIT_TYPE_PPS = 8; // Picture parameter set
|
||||
|
||||
private final SeiReader seiReader;
|
||||
private final boolean allowNonIdrKeyframes;
|
||||
private final boolean detectAccessUnits;
|
||||
@ -85,9 +81,9 @@ public final class H264Reader implements ElementaryStreamReader {
|
||||
this.allowNonIdrKeyframes = allowNonIdrKeyframes;
|
||||
this.detectAccessUnits = detectAccessUnits;
|
||||
prefixFlags = new boolean[3];
|
||||
sps = new NalUnitTargetBuffer(NAL_UNIT_TYPE_SPS, 128);
|
||||
pps = new NalUnitTargetBuffer(NAL_UNIT_TYPE_PPS, 128);
|
||||
sei = new NalUnitTargetBuffer(NAL_UNIT_TYPE_SEI, 128);
|
||||
sps = new NalUnitTargetBuffer(NalUnitUtil.NAL_UNIT_TYPE_SPS, 128);
|
||||
pps = new NalUnitTargetBuffer(NalUnitUtil.NAL_UNIT_TYPE_PPS, 128);
|
||||
sei = new NalUnitTargetBuffer(NalUnitUtil.NAL_UNIT_TYPE_SEI, 128);
|
||||
pesTimeUs = C.TIME_UNSET;
|
||||
seiWrapper = new ParsableByteArray();
|
||||
}
|
||||
@ -266,11 +262,6 @@ public final class H264Reader implements ElementaryStreamReader {
|
||||
|
||||
private static final int DEFAULT_BUFFER_SIZE = 128;
|
||||
|
||||
private static final int NAL_UNIT_TYPE_NON_IDR = 1; // Coded slice of a non-IDR picture
|
||||
private static final int NAL_UNIT_TYPE_PARTITION_A = 2; // Coded slice data partition A
|
||||
private static final int NAL_UNIT_TYPE_IDR = 5; // Coded slice of an IDR picture
|
||||
private static final int NAL_UNIT_TYPE_AUD = 9; // Access unit delimiter
|
||||
|
||||
private final TrackOutput output;
|
||||
private final boolean allowNonIdrKeyframes;
|
||||
private final boolean detectAccessUnits;
|
||||
@ -331,11 +322,11 @@ public final class H264Reader implements ElementaryStreamReader {
|
||||
nalUnitType = type;
|
||||
nalUnitTimeUs = pesTimeUs;
|
||||
nalUnitStartPosition = position;
|
||||
if ((allowNonIdrKeyframes && nalUnitType == NAL_UNIT_TYPE_NON_IDR)
|
||||
if ((allowNonIdrKeyframes && nalUnitType == NalUnitUtil.NAL_UNIT_TYPE_NON_IDR)
|
||||
|| (detectAccessUnits
|
||||
&& (nalUnitType == NAL_UNIT_TYPE_IDR
|
||||
|| nalUnitType == NAL_UNIT_TYPE_NON_IDR
|
||||
|| nalUnitType == NAL_UNIT_TYPE_PARTITION_A))) {
|
||||
&& (nalUnitType == NalUnitUtil.NAL_UNIT_TYPE_IDR
|
||||
|| nalUnitType == NalUnitUtil.NAL_UNIT_TYPE_NON_IDR
|
||||
|| nalUnitType == NalUnitUtil.NAL_UNIT_TYPE_PARTITION_A))) {
|
||||
// Store the previous header and prepare to populate the new one.
|
||||
SliceHeaderData newSliceHeader = previousSliceHeader;
|
||||
previousSliceHeader = sliceHeader;
|
||||
@ -425,7 +416,7 @@ public final class H264Reader implements ElementaryStreamReader {
|
||||
bottomFieldFlagPresent = true;
|
||||
}
|
||||
}
|
||||
boolean idrPicFlag = nalUnitType == NAL_UNIT_TYPE_IDR;
|
||||
boolean idrPicFlag = nalUnitType == NalUnitUtil.NAL_UNIT_TYPE_IDR;
|
||||
int idrPicId = 0;
|
||||
if (idrPicFlag) {
|
||||
if (!bitArray.canReadExpGolombCodedNum()) {
|
||||
@ -480,7 +471,7 @@ public final class H264Reader implements ElementaryStreamReader {
|
||||
|
||||
public boolean endNalUnit(
|
||||
long position, int offset, boolean hasOutputFormat, boolean randomAccessIndicator) {
|
||||
if (nalUnitType == NAL_UNIT_TYPE_AUD
|
||||
if (nalUnitType == NalUnitUtil.NAL_UNIT_TYPE_AUD
|
||||
|| (detectAccessUnits && sliceHeader.isFirstVclNalUnitOfPicture(previousSliceHeader))) {
|
||||
// If the NAL unit ending is the start of a new sample, output the previous one.
|
||||
if (hasOutputFormat && readingSample) {
|
||||
@ -495,8 +486,8 @@ public final class H264Reader implements ElementaryStreamReader {
|
||||
boolean treatIFrameAsKeyframe =
|
||||
allowNonIdrKeyframes ? sliceHeader.isISlice() : randomAccessIndicator;
|
||||
sampleIsKeyframe |=
|
||||
nalUnitType == NAL_UNIT_TYPE_IDR
|
||||
|| (treatIFrameAsKeyframe && nalUnitType == NAL_UNIT_TYPE_NON_IDR);
|
||||
nalUnitType == NalUnitUtil.NAL_UNIT_TYPE_IDR
|
||||
|| (treatIFrameAsKeyframe && nalUnitType == NalUnitUtil.NAL_UNIT_TYPE_NON_IDR);
|
||||
return sampleIsKeyframe;
|
||||
}
|
||||
|
||||
|
@ -126,6 +126,7 @@ public final class NalUnitUtilTest {
|
||||
public void parseSpsNalUnit() {
|
||||
NalUnitUtil.SpsData data =
|
||||
NalUnitUtil.parseSpsNalUnit(SPS_TEST_DATA, SPS_TEST_DATA_OFFSET, SPS_TEST_DATA.length);
|
||||
assertThat(data.maxNumRefFrames).isEqualTo(4);
|
||||
assertThat(data.width).isEqualTo(640);
|
||||
assertThat(data.height).isEqualTo(360);
|
||||
assertThat(data.deltaPicOrderAlwaysZeroFlag).isFalse();
|
||||
|
@ -15,6 +15,8 @@
|
||||
*/
|
||||
package androidx.media3.session;
|
||||
|
||||
import static androidx.media3.common.util.Assertions.checkStateNotNull;
|
||||
|
||||
import android.app.Notification;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
@ -25,6 +27,7 @@ import androidx.core.app.NotificationManagerCompat;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.media3.common.Player;
|
||||
import androidx.media3.common.util.Util;
|
||||
import com.google.common.util.concurrent.Futures;
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
@ -50,6 +53,7 @@ import java.util.concurrent.TimeoutException;
|
||||
private final Map<MediaSession, ListenableFuture<MediaController>> controllerMap;
|
||||
|
||||
private int totalNotificationCount;
|
||||
@Nullable private MediaNotification mediaNotification;
|
||||
|
||||
public MediaNotificationManager(
|
||||
MediaSessionService mediaSessionService,
|
||||
@ -122,13 +126,13 @@ import java.util.concurrent.TimeoutException;
|
||||
|
||||
MediaController mediaController;
|
||||
try {
|
||||
mediaController = controllerFuture.get(0, TimeUnit.MILLISECONDS);
|
||||
} catch (ExecutionException | InterruptedException | TimeoutException e) {
|
||||
mediaController = checkStateNotNull(Futures.getDone(controllerFuture));
|
||||
} catch (ExecutionException e) {
|
||||
// We should never reach this point.
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
|
||||
int notificationSequence = ++this.totalNotificationCount;
|
||||
int notificationSequence = ++totalNotificationCount;
|
||||
MediaNotification.Provider.Callback callback =
|
||||
notification ->
|
||||
mainExecutor.execute(
|
||||
@ -141,45 +145,68 @@ import java.util.concurrent.TimeoutException;
|
||||
|
||||
private void onNotificationUpdated(
|
||||
int notificationSequence, MediaSession session, MediaNotification mediaNotification) {
|
||||
if (notificationSequence == this.totalNotificationCount) {
|
||||
if (notificationSequence == totalNotificationCount) {
|
||||
updateNotification(session, mediaNotification);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateNotification(MediaSession session, MediaNotification mediaNotification) {
|
||||
int id = mediaNotification.notificationId;
|
||||
Notification notification = mediaNotification.notification;
|
||||
|
||||
if (Util.SDK_INT >= 21) {
|
||||
// Call Notification.MediaStyle#setMediaSession() indirectly.
|
||||
android.media.session.MediaSession.Token fwkToken =
|
||||
(android.media.session.MediaSession.Token)
|
||||
session.getSessionCompat().getSessionToken().getToken();
|
||||
notification.extras.putParcelable(Notification.EXTRA_MEDIA_SESSION, fwkToken);
|
||||
mediaNotification.notification.extras.putParcelable(
|
||||
Notification.EXTRA_MEDIA_SESSION, fwkToken);
|
||||
}
|
||||
|
||||
this.mediaNotification = mediaNotification;
|
||||
Player player = session.getPlayer();
|
||||
if (player.getPlayWhenReady()) {
|
||||
if (player.getPlayWhenReady() && canStartPlayback(player)) {
|
||||
ContextCompat.startForegroundService(mediaSessionService, startSelfIntent);
|
||||
mediaSessionService.startForeground(id, notification);
|
||||
mediaSessionService.startForeground(
|
||||
mediaNotification.notificationId, mediaNotification.notification);
|
||||
} else {
|
||||
stopForegroundServiceIfNeeded();
|
||||
notificationManagerCompat.notify(id, notification);
|
||||
maybeStopForegroundService(/* removeNotifications= */ false);
|
||||
notificationManagerCompat.notify(
|
||||
mediaNotification.notificationId, mediaNotification.notification);
|
||||
}
|
||||
}
|
||||
|
||||
private void stopForegroundServiceIfNeeded() {
|
||||
/**
|
||||
* Stops the service from the foreground, if no player is actively playing content.
|
||||
*
|
||||
* @param removeNotifications Whether to remove notifications, if the service is stopped from the
|
||||
* foreground.
|
||||
*/
|
||||
private void maybeStopForegroundService(boolean removeNotifications) {
|
||||
List<MediaSession> sessions = mediaSessionService.getSessions();
|
||||
for (int i = 0; i < sessions.size(); i++) {
|
||||
Player player = sessions.get(i).getPlayer();
|
||||
if (player.getPlayWhenReady()) {
|
||||
if (player.getPlayWhenReady() && canStartPlayback(player)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Calling stopForeground(true) is a workaround for pre-L devices which prevents
|
||||
// the media notification from being undismissable.
|
||||
boolean shouldRemoveNotification = Util.SDK_INT < 21;
|
||||
mediaSessionService.stopForeground(shouldRemoveNotification);
|
||||
// To hide the notification on all API levels, we need to call both Service.stopForeground(true)
|
||||
// and notificationManagerCompat.cancelAll(). For pre-L devices, we must also call
|
||||
// Service.stopForeground(true) anyway as a workaround that prevents the media notification from
|
||||
// being undismissable.
|
||||
mediaSessionService.stopForeground(removeNotifications || Util.SDK_INT < 21);
|
||||
if (removeNotifications && mediaNotification != null) {
|
||||
notificationManagerCompat.cancel(mediaNotification.notificationId);
|
||||
// Update the notification count so that if a pending notification callback arrives (e.g., a
|
||||
// bitmap is loaded), we don't show the notification.
|
||||
totalNotificationCount++;
|
||||
mediaNotification = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether {@code player} can start playback and therefore we should present a
|
||||
* notification for this player.
|
||||
*/
|
||||
private static boolean canStartPlayback(Player player) {
|
||||
return player.getPlaybackState() != Player.STATE_IDLE && !player.getCurrentTimeline().isEmpty();
|
||||
}
|
||||
|
||||
private final class MediaControllerListener implements MediaController.Listener, Player.Listener {
|
||||
@ -190,11 +217,20 @@ import java.util.concurrent.TimeoutException;
|
||||
}
|
||||
|
||||
public void onConnected() {
|
||||
updateNotification(session);
|
||||
if (canStartPlayback(session.getPlayer())) {
|
||||
updateNotification(session);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEvents(Player player, Player.Events events) {
|
||||
if (!canStartPlayback(player)) {
|
||||
maybeStopForegroundService(/* removeNotifications= */ true);
|
||||
return;
|
||||
}
|
||||
|
||||
// Limit the events on which we may update the notification to ensure we don't update the
|
||||
// notification too frequently, otherwise the system may suppress notifications.
|
||||
if (events.containsAny(
|
||||
Player.EVENT_PLAYBACK_STATE_CHANGED,
|
||||
Player.EVENT_PLAY_WHEN_READY_CHANGED,
|
||||
@ -206,7 +242,7 @@ import java.util.concurrent.TimeoutException;
|
||||
@Override
|
||||
public void onDisconnected(MediaController controller) {
|
||||
mediaSessionService.removeSession(session);
|
||||
stopForegroundServiceIfNeeded();
|
||||
maybeStopForegroundService(/* removeNotifications= */ true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user