Bump minSdk to 21 and remove resulting simple dead code

All other AndroidX libraries have already increased their min SDK to
21.

This change renames private symbols to remove `V21` suffixes and
similar, but doesn't change public or protected symbols with similar
names, to avoid needless breakages/churn on users of the library.

Some of the dead code removal is more complex, so I've split it out
into follow-up changes to make it easier to review.

PiperOrigin-RevId: 651776556
This commit is contained in:
ibaker 2024-07-12 08:07:40 -07:00 committed by Copybara-Service
parent 5fa9985ce6
commit 6a9ff95bf0
60 changed files with 170 additions and 815 deletions

View File

@ -11,6 +11,8 @@
* Add override for `SimpleBasePlayer.State.Builder.setPlaylist()` to * Add override for `SimpleBasePlayer.State.Builder.setPlaylist()` to
directly specify a `Timeline` and current `Tracks` and `Metadata` directly specify a `Timeline` and current `Tracks` and `Metadata`
instead of building a playlist structure. instead of building a playlist structure.
* Increase `minSdk` to 21 (Android Lollipop). This is aligned with all
other AndroidX libraries.
* ExoPlayer: * ExoPlayer:
* `MediaCodecRenderer.onProcessedStreamChange()` can now be called for * `MediaCodecRenderer.onProcessedStreamChange()` can now be called for
every media item. Previously it was not called for the first one. Use every media item. Previously it was not called for the first one. Use

View File

@ -26,7 +26,7 @@ package androidx.media3.common {
} }
public final class AudioAttributes { public final class AudioAttributes {
method @RequiresApi(21) public androidx.media3.common.AudioAttributes.AudioAttributesV21 getAudioAttributesV21(); method public androidx.media3.common.AudioAttributes.AudioAttributesV21 getAudioAttributesV21();
field public static final androidx.media3.common.AudioAttributes DEFAULT; field public static final androidx.media3.common.AudioAttributes DEFAULT;
field @androidx.media3.common.C.AudioAllowedCapturePolicy public final int allowedCapturePolicy; field @androidx.media3.common.C.AudioAllowedCapturePolicy public final int allowedCapturePolicy;
field @androidx.media3.common.C.AudioContentType public final int contentType; field @androidx.media3.common.C.AudioContentType public final int contentType;
@ -35,7 +35,7 @@ package androidx.media3.common {
field @androidx.media3.common.C.AudioUsage public final int usage; field @androidx.media3.common.C.AudioUsage public final int usage;
} }
@RequiresApi(21) public static final class AudioAttributes.AudioAttributesV21 { public static final class AudioAttributes.AudioAttributesV21 {
field public final android.media.AudioAttributes audioAttributes; field public final android.media.AudioAttributes audioAttributes;
} }

View File

@ -14,7 +14,7 @@
project.ext { project.ext {
releaseVersion = '1.4.0-rc01' releaseVersion = '1.4.0-rc01'
releaseVersionCode = 1_004_000_2_01 releaseVersionCode = 1_004_000_2_01
minSdkVersion = 19 minSdkVersion = 21
// See https://developer.android.com/training/cars/media/automotive-os#automotive-module // See https://developer.android.com/training/cars/media/automotive-os#automotive-module
automotiveMinSdkVersion = 28 automotiveMinSdkVersion = 28
appTargetSdkVersion = 34 appTargetSdkVersion = 34

View File

@ -63,7 +63,7 @@ public class DemoDownloadService extends DownloadService {
@Override @Override
protected Scheduler getScheduler() { protected Scheduler getScheduler() {
return Util.SDK_INT >= 21 ? new PlatformScheduler(this, JOB_ID) : null; return new PlatformScheduler(this, JOB_ID);
} }
@Override @Override

View File

@ -37,7 +37,6 @@ import com.google.errorprone.annotations.CanIgnoreReturnValue;
public final class AudioAttributes { public final class AudioAttributes {
/** A direct wrapper around {@link android.media.AudioAttributes}. */ /** A direct wrapper around {@link android.media.AudioAttributes}. */
@RequiresApi(21)
public static final class AudioAttributesV21 { public static final class AudioAttributesV21 {
public final android.media.AudioAttributes audioAttributes; public final android.media.AudioAttributes audioAttributes;
@ -165,7 +164,6 @@ public final class AudioAttributes {
* <p>Some fields are ignored if the corresponding {@link android.media.AudioAttributes.Builder} * <p>Some fields are ignored if the corresponding {@link android.media.AudioAttributes.Builder}
* setter is not available on the current API level. * setter is not available on the current API level.
*/ */
@RequiresApi(21)
public AudioAttributesV21 getAudioAttributesV21() { public AudioAttributesV21 getAudioAttributesV21() {
if (audioAttributesV21 == null) { if (audioAttributesV21 == null) {
audioAttributesV21 = new AudioAttributesV21(this); audioAttributesV21 = new AudioAttributesV21(this);

View File

@ -32,7 +32,6 @@ import android.media.MediaFormat;
import android.net.Uri; import android.net.Uri;
import android.view.Surface; import android.view.Surface;
import androidx.annotation.IntDef; import androidx.annotation.IntDef;
import androidx.annotation.RequiresApi;
import androidx.media3.common.util.UnstableApi; import androidx.media3.common.util.UnstableApi;
import androidx.media3.common.util.Util; import androidx.media3.common.util.Util;
import com.google.errorprone.annotations.InlineMe; import com.google.errorprone.annotations.InlineMe;
@ -1698,7 +1697,6 @@ public final class C {
replacement = "Util.generateAudioSessionIdV21(context)", replacement = "Util.generateAudioSessionIdV21(context)",
imports = {"androidx.media3.common.util.Util"}) imports = {"androidx.media3.common.util.Util"})
@Deprecated @Deprecated
@RequiresApi(21)
public static int generateAudioSessionIdV21(Context context) { public static int generateAudioSessionIdV21(Context context) {
return Util.generateAudioSessionIdV21(context); return Util.generateAudioSessionIdV21(context);
} }

View File

@ -962,16 +962,14 @@ public final class Util {
/** /**
* Returns the language tag for a {@link Locale}. * Returns the language tag for a {@link Locale}.
* *
* <p>For API levels &ge; 21, this tag is IETF BCP 47 compliant. Use {@link * <p>This tag is IETF BCP 47 compliant.
* #normalizeLanguageCode(String)} to retrieve a normalized IETF BCP 47 language tag for all API
* levels if needed.
* *
* @param locale A {@link Locale}. * @param locale A {@link Locale}.
* @return The language tag. * @return The language tag.
*/ */
@UnstableApi @UnstableApi
public static String getLocaleLanguageTag(Locale locale) { public static String getLocaleLanguageTag(Locale locale) {
return SDK_INT >= 21 ? getLocaleLanguageTagV21(locale) : locale.toString(); return locale.toLanguageTag();
} }
/** /**
@ -2253,7 +2251,6 @@ public final class Util {
/** Creates {@link AudioFormat} with given sampleRate, channelConfig, and encoding. */ /** Creates {@link AudioFormat} with given sampleRate, channelConfig, and encoding. */
@UnstableApi @UnstableApi
@RequiresApi(21)
public static AudioFormat getAudioFormat(int sampleRate, int channelConfig, int encoding) { public static AudioFormat getAudioFormat(int sampleRate, int channelConfig, int encoding) {
return new AudioFormat.Builder() return new AudioFormat.Builder()
.setSampleRate(sampleRate) .setSampleRate(sampleRate)
@ -2417,7 +2414,6 @@ public final class Util {
* @see AudioManager#generateAudioSessionId() * @see AudioManager#generateAudioSessionId()
*/ */
@UnstableApi @UnstableApi
@RequiresApi(21)
public static int generateAudioSessionIdV21(Context context) { public static int generateAudioSessionIdV21(Context context) {
@Nullable @Nullable
AudioManager audioManager = ((AudioManager) context.getSystemService(Context.AUDIO_SERVICE)); AudioManager audioManager = ((AudioManager) context.getSystemService(Context.AUDIO_SERVICE));
@ -3065,8 +3061,7 @@ public final class Util {
*/ */
@UnstableApi @UnstableApi
public static boolean isWear(Context context) { public static boolean isWear(Context context) {
return SDK_INT >= 20 return context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH);
&& context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH);
} }
/** /**
@ -3502,9 +3497,7 @@ public final class Util {
@UnstableApi @UnstableApi
public static Drawable getDrawable( public static Drawable getDrawable(
Context context, Resources resources, @DrawableRes int drawableRes) { Context context, Resources resources, @DrawableRes int drawableRes) {
return SDK_INT >= 21 return resources.getDrawable(drawableRes, context.getTheme());
? Api21.getDrawable(context, resources, drawableRes)
: resources.getDrawable(drawableRes);
} }
/** /**
@ -3666,11 +3659,6 @@ public final class Util {
return split(config.getLocales().toLanguageTags(), ","); return split(config.getLocales().toLanguageTags(), ",");
} }
@RequiresApi(21)
private static String getLocaleLanguageTagV21(Locale locale) {
return locale.toLanguageTag();
}
private static HashMap<String, String> createIsoLanguageReplacementMap() { private static HashMap<String, String> createIsoLanguageReplacementMap() {
String[] iso2Languages = Locale.getISOLanguages(); String[] iso2Languages = Locale.getISOLanguages();
HashMap<String, String> replacedLanguages = HashMap<String, String> replacedLanguages =
@ -3890,14 +3878,6 @@ public final class Util {
0xF3 0xF3
}; };
@RequiresApi(21)
private static final class Api21 {
@DoNotInline
public static Drawable getDrawable(Context context, Resources resources, @DrawableRes int res) {
return resources.getDrawable(res, context.getTheme());
}
}
@RequiresApi(29) @RequiresApi(29)
private static class Api29 { private static class Api29 {

View File

@ -40,7 +40,6 @@ import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.InterruptedIOException; import java.io.InterruptedIOException;
import java.io.OutputStream; import java.io.OutputStream;
import java.lang.reflect.Method;
import java.net.HttpURLConnection; import java.net.HttpURLConnection;
import java.net.MalformedURLException; import java.net.MalformedURLException;
import java.net.NoRouteToHostException; import java.net.NoRouteToHostException;
@ -248,7 +247,6 @@ public class DefaultHttpDataSource extends BaseDataSource implements HttpDataSou
private static final int MAX_REDIRECTS = 20; // Same limit as okhttp. private static final int MAX_REDIRECTS = 20; // Same limit as okhttp.
private static final int HTTP_STATUS_TEMPORARY_REDIRECT = 307; private static final int HTTP_STATUS_TEMPORARY_REDIRECT = 307;
private static final int HTTP_STATUS_PERMANENT_REDIRECT = 308; private static final int HTTP_STATUS_PERMANENT_REDIRECT = 308;
private static final long MAX_BYTES_TO_DRAIN = 2048;
private final boolean allowCrossProtocolRedirects; private final boolean allowCrossProtocolRedirects;
private final boolean crossProtocolRedirectsForceOriginal; private final boolean crossProtocolRedirectsForceOriginal;
@ -482,9 +480,6 @@ public class DefaultHttpDataSource extends BaseDataSource implements HttpDataSou
try { try {
@Nullable InputStream inputStream = this.inputStream; @Nullable InputStream inputStream = this.inputStream;
if (inputStream != null) { if (inputStream != null) {
long bytesRemaining =
bytesToRead == C.LENGTH_UNSET ? C.LENGTH_UNSET : bytesToRead - bytesRead;
maybeTerminateInputStream(connection, bytesRemaining);
try { try {
inputStream.close(); inputStream.close();
} catch (IOException e) { } catch (IOException e) {
@ -784,52 +779,6 @@ public class DefaultHttpDataSource extends BaseDataSource implements HttpDataSou
return read; return read;
} }
/**
* On platform API levels 19 and 20, okhttp's implementation of {@link InputStream#close} can
* block for a long time if the stream has a lot of data remaining. Call this method before
* closing the input stream to make a best effort to cause the input stream to encounter an
* unexpected end of input, working around this issue. On other platform API levels, the method
* does nothing.
*
* @param connection The connection whose {@link InputStream} should be terminated.
* @param bytesRemaining The number of bytes remaining to be read from the input stream if its
* length is known. {@link C#LENGTH_UNSET} otherwise.
*/
private static void maybeTerminateInputStream(
@Nullable HttpURLConnection connection, long bytesRemaining) {
if (connection == null || Util.SDK_INT > 20) {
return;
}
try {
InputStream inputStream = connection.getInputStream();
if (bytesRemaining == C.LENGTH_UNSET) {
// If the input stream has already ended, do nothing. The socket may be re-used.
if (inputStream.read() == -1) {
return;
}
} else if (bytesRemaining <= MAX_BYTES_TO_DRAIN) {
// There isn't much data left. Prefer to allow it to drain, which may allow the socket to be
// re-used.
return;
}
String className = inputStream.getClass().getName();
if ("com.android.okhttp.internal.http.HttpTransport$ChunkedInputStream".equals(className)
|| "com.android.okhttp.internal.http.HttpTransport$FixedLengthInputStream"
.equals(className)) {
Class<?> superclass = inputStream.getClass().getSuperclass();
Method unexpectedEndOfInput =
checkNotNull(superclass).getDeclaredMethod("unexpectedEndOfInput");
unexpectedEndOfInput.setAccessible(true);
unexpectedEndOfInput.invoke(inputStream);
}
} catch (Exception e) {
// If an IOException then the connection didn't ever have an input stream, or it was closed
// already. If another type of exception then something went wrong, most likely the device
// isn't using okhttp.
}
}
/** Closes the current connection quietly, if there is one. */ /** Closes the current connection quietly, if there is one. */
private void closeConnectionQuietly() { private void closeConnectionQuietly() {
if (connection != null) { if (connection != null) {

View File

@ -22,14 +22,11 @@ import android.net.Uri;
import android.system.ErrnoException; import android.system.ErrnoException;
import android.system.OsConstants; import android.system.OsConstants;
import android.text.TextUtils; import android.text.TextUtils;
import androidx.annotation.DoNotInline;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.media3.common.C; import androidx.media3.common.C;
import androidx.media3.common.PlaybackException; import androidx.media3.common.PlaybackException;
import androidx.media3.common.util.Assertions; import androidx.media3.common.util.Assertions;
import androidx.media3.common.util.UnstableApi; import androidx.media3.common.util.UnstableApi;
import androidx.media3.common.util.Util;
import com.google.errorprone.annotations.CanIgnoreReturnValue; import com.google.errorprone.annotations.CanIgnoreReturnValue;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
import java.io.IOException; import java.io.IOException;
@ -200,7 +197,8 @@ public final class FileDataSource extends BaseDataSource {
// different SDK versions. // different SDK versions.
throw new FileDataSourceException( throw new FileDataSourceException(
e, e,
Util.SDK_INT >= 21 && Api21.isPermissionError(e.getCause()) e.getCause() instanceof ErrnoException
&& ((ErrnoException) e.getCause()).errno == OsConstants.EACCES
? PlaybackException.ERROR_CODE_IO_NO_PERMISSION ? PlaybackException.ERROR_CODE_IO_NO_PERMISSION
: PlaybackException.ERROR_CODE_IO_FILE_NOT_FOUND); : PlaybackException.ERROR_CODE_IO_FILE_NOT_FOUND);
} catch (SecurityException e) { } catch (SecurityException e) {
@ -209,12 +207,4 @@ public final class FileDataSource extends BaseDataSource {
throw new FileDataSourceException(e, PlaybackException.ERROR_CODE_IO_UNSPECIFIED); throw new FileDataSourceException(e, PlaybackException.ERROR_CODE_IO_UNSPECIFIED);
} }
} }
@RequiresApi(21)
private static final class Api21 {
@DoNotInline
private static boolean isPermissionError(@Nullable Throwable e) {
return e instanceof ErrnoException && ((ErrnoException) e).errno == OsConstants.EACCES;
}
}
} }

View File

@ -24,7 +24,6 @@ import android.system.ErrnoException;
import android.system.Os; import android.system.Os;
import android.system.OsConstants; import android.system.OsConstants;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.media3.common.C; import androidx.media3.common.C;
import androidx.media3.common.PlaybackException; import androidx.media3.common.PlaybackException;
import androidx.media3.common.util.UnstableApi; import androidx.media3.common.util.UnstableApi;
@ -48,7 +47,6 @@ import java.util.Set;
* #open(DataSpec)} is not actually used for reading data. Instead, the underlying {@link * #open(DataSpec)} is not actually used for reading data. Instead, the underlying {@link
* FileDescriptor} is used for all read operations. * FileDescriptor} is used for all read operations.
*/ */
@RequiresApi(21)
@UnstableApi @UnstableApi
public class FileDescriptorDataSource extends BaseDataSource { public class FileDescriptorDataSource extends BaseDataSource {

View File

@ -748,7 +748,7 @@ public final class MediaMetricsListener
} else if (cause instanceof DrmSession.DrmSessionException) { } else if (cause instanceof DrmSession.DrmSessionException) {
// Unpack DrmSessionException. // Unpack DrmSessionException.
cause = checkNotNull(cause.getCause()); cause = checkNotNull(cause.getCause());
if (Util.SDK_INT >= 21 && cause instanceof MediaDrm.MediaDrmStateException) { if (cause instanceof MediaDrm.MediaDrmStateException) {
String diagnosticsInfo = ((MediaDrm.MediaDrmStateException) cause).getDiagnosticInfo(); String diagnosticsInfo = ((MediaDrm.MediaDrmStateException) cause).getDiagnosticInfo();
int subErrorCode = Util.getErrorCodeFromPlatformDiagnosticsInfo(diagnosticsInfo); int subErrorCode = Util.getErrorCodeFromPlatformDiagnosticsInfo(diagnosticsInfo);
int errorCode = getDrmErrorCode(subErrorCode); int errorCode = getDrmErrorCode(subErrorCode);
@ -771,8 +771,7 @@ public final class MediaMetricsListener
} else if (cause instanceof FileDataSource.FileDataSourceException } else if (cause instanceof FileDataSource.FileDataSourceException
&& cause.getCause() instanceof FileNotFoundException) { && cause.getCause() instanceof FileNotFoundException) {
@Nullable Throwable notFoundCause = checkNotNull(cause.getCause()).getCause(); @Nullable Throwable notFoundCause = checkNotNull(cause.getCause()).getCause();
if (Util.SDK_INT >= 21 if (notFoundCause instanceof ErrnoException
&& notFoundCause instanceof ErrnoException
&& ((ErrnoException) notFoundCause).errno == OsConstants.EACCES) { && ((ErrnoException) notFoundCause).errno == OsConstants.EACCES) {
return new ErrorInfo(PlaybackErrorEvent.ERROR_IO_NO_PERMISSION, /* subErrorCode= */ 0); return new ErrorInfo(PlaybackErrorEvent.ERROR_IO_NO_PERMISSION, /* subErrorCode= */ 0);
} else { } else {

View File

@ -57,7 +57,7 @@ public final class AudioCapabilitiesReceiver {
private final Listener listener; private final Listener listener;
private final Handler handler; private final Handler handler;
@Nullable private final AudioDeviceCallbackV23 audioDeviceCallback; @Nullable private final AudioDeviceCallbackV23 audioDeviceCallback;
@Nullable private final BroadcastReceiver hdmiAudioPlugBroadcastReceiver; private final BroadcastReceiver hdmiAudioPlugBroadcastReceiver;
@Nullable private final ExternalSurroundSoundSettingObserver externalSurroundSoundSettingObserver; @Nullable private final ExternalSurroundSoundSettingObserver externalSurroundSoundSettingObserver;
@Nullable private AudioCapabilities audioCapabilities; @Nullable private AudioCapabilities audioCapabilities;
@ -105,8 +105,7 @@ public final class AudioCapabilitiesReceiver {
this.routedDevice = routedDevice; this.routedDevice = routedDevice;
handler = Util.createHandlerForCurrentOrMainLooper(); handler = Util.createHandlerForCurrentOrMainLooper();
audioDeviceCallback = Util.SDK_INT >= 23 ? new AudioDeviceCallbackV23() : null; audioDeviceCallback = Util.SDK_INT >= 23 ? new AudioDeviceCallbackV23() : null;
hdmiAudioPlugBroadcastReceiver = hdmiAudioPlugBroadcastReceiver = new HdmiAudioPlugBroadcastReceiver();
Util.SDK_INT >= 21 ? new HdmiAudioPlugBroadcastReceiver() : null;
Uri externalSurroundSoundUri = AudioCapabilities.getExternalSurroundSoundGlobalSettingUri(); Uri externalSurroundSoundUri = AudioCapabilities.getExternalSurroundSoundGlobalSettingUri();
externalSurroundSoundSettingObserver = externalSurroundSoundSettingObserver =
externalSurroundSoundUri != null externalSurroundSoundUri != null
@ -162,16 +161,12 @@ public final class AudioCapabilitiesReceiver {
if (Util.SDK_INT >= 23 && audioDeviceCallback != null) { if (Util.SDK_INT >= 23 && audioDeviceCallback != null) {
Api23.registerAudioDeviceCallback(context, audioDeviceCallback, handler); Api23.registerAudioDeviceCallback(context, audioDeviceCallback, handler);
} }
@Nullable Intent stickyIntent = null; Intent stickyIntent =
if (hdmiAudioPlugBroadcastReceiver != null) { context.registerReceiver(
IntentFilter intentFilter = new IntentFilter(AudioManager.ACTION_HDMI_AUDIO_PLUG); hdmiAudioPlugBroadcastReceiver,
stickyIntent = new IntentFilter(AudioManager.ACTION_HDMI_AUDIO_PLUG),
context.registerReceiver( /* broadcastPermission= */ null,
hdmiAudioPlugBroadcastReceiver, handler);
intentFilter,
/* broadcastPermission= */ null,
handler);
}
audioCapabilities = audioCapabilities =
AudioCapabilities.getCapabilitiesInternal( AudioCapabilities.getCapabilitiesInternal(
context, stickyIntent, audioAttributes, routedDevice); context, stickyIntent, audioAttributes, routedDevice);
@ -190,9 +185,7 @@ public final class AudioCapabilitiesReceiver {
if (Util.SDK_INT >= 23 && audioDeviceCallback != null) { if (Util.SDK_INT >= 23 && audioDeviceCallback != null) {
Api23.unregisterAudioDeviceCallback(context, audioDeviceCallback); Api23.unregisterAudioDeviceCallback(context, audioDeviceCallback);
} }
if (hdmiAudioPlugBroadcastReceiver != null) { context.unregisterReceiver(hdmiAudioPlugBroadcastReceiver);
context.unregisterReceiver(hdmiAudioPlugBroadcastReceiver);
}
if (externalSurroundSoundSettingObserver != null) { if (externalSurroundSoundSettingObserver != null) {
externalSurroundSoundSettingObserver.unregister(); externalSurroundSoundSettingObserver.unregister();
} }

View File

@ -244,9 +244,7 @@ public abstract class DecoderAudioRenderer<
if (formatSupport <= C.FORMAT_UNSUPPORTED_DRM) { if (formatSupport <= C.FORMAT_UNSUPPORTED_DRM) {
return RendererCapabilities.create(formatSupport); return RendererCapabilities.create(formatSupport);
} }
@TunnelingSupport return RendererCapabilities.create(formatSupport, ADAPTIVE_NOT_SEAMLESS, TUNNELING_SUPPORTED);
int tunnelingSupport = Util.SDK_INT >= 21 ? TUNNELING_SUPPORTED : TUNNELING_NOT_SUPPORTED;
return RendererCapabilities.create(formatSupport, ADAPTIVE_NOT_SEAMLESS, tunnelingSupport);
} }
/** /**

View File

@ -337,8 +337,8 @@ public final class DefaultAudioSink implements AudioSink {
/** /**
* Sets whether to enable 32-bit float output or integer output. Where possible, 32-bit float * Sets whether to enable 32-bit float output or integer output. Where possible, 32-bit float
* output will be used if the input is 32-bit float, and also if the input is high resolution * output will be used if the input is 32-bit float, and also if the input is high resolution
* (24-bit or 32-bit) integer PCM. Float output is supported from API level 21. Audio processing * (24-bit or 32-bit) integer PCM. Audio processing (for example, speed adjustment) will not be
* (for example, speed adjustment) will not be available when float output is in use. * available when float output is in use.
* *
* <p>The default value is {@code false}. * <p>The default value is {@code false}.
*/ */
@ -541,8 +541,6 @@ public final class DefaultAudioSink implements AudioSink {
@Nullable private ByteBuffer inputBuffer; @Nullable private ByteBuffer inputBuffer;
private int inputBufferAccessUnitCount; private int inputBufferAccessUnitCount;
@Nullable private ByteBuffer outputBuffer; @Nullable private ByteBuffer outputBuffer;
private byte @MonotonicNonNull [] preV21OutputBuffer;
private int preV21OutputBufferOffset;
private boolean handledEndOfStream; private boolean handledEndOfStream;
private boolean stoppedAudioTrack; private boolean stoppedAudioTrack;
private boolean handledOffloadOnPresentationEnded; private boolean handledOffloadOnPresentationEnded;
@ -571,7 +569,7 @@ public final class DefaultAudioSink implements AudioSink {
? getCapabilities(context, audioAttributes, /* routedDevice= */ null) ? getCapabilities(context, audioAttributes, /* routedDevice= */ null)
: builder.audioCapabilities; : builder.audioCapabilities;
audioProcessorChain = builder.audioProcessorChain; audioProcessorChain = builder.audioProcessorChain;
enableFloatOutput = Util.SDK_INT >= 21 && builder.enableFloatOutput; enableFloatOutput = builder.enableFloatOutput;
preferAudioTrackPlaybackParams = Util.SDK_INT >= 23 && builder.enableAudioTrackPlaybackParams; preferAudioTrackPlaybackParams = Util.SDK_INT >= 23 && builder.enableAudioTrackPlaybackParams;
offloadMode = OFFLOAD_MODE_DISABLED; offloadMode = OFFLOAD_MODE_DISABLED;
audioTrackBufferSizeProvider = builder.audioTrackBufferSizeProvider; audioTrackBufferSizeProvider = builder.audioTrackBufferSizeProvider;
@ -700,14 +698,6 @@ public final class DefaultAudioSink implements AudioSink {
trimmingAudioProcessor.setTrimFrameCount( trimmingAudioProcessor.setTrimFrameCount(
inputFormat.encoderDelay, inputFormat.encoderPadding); inputFormat.encoderDelay, inputFormat.encoderPadding);
if (Util.SDK_INT < 21 && inputFormat.channelCount == 8 && outputChannels == null) {
// AudioTrack doesn't support 8 channel output before Android L. Discard the last two (side)
// channels to give a 6 channel stream that is supported.
outputChannels = new int[6];
for (int i = 0; i < outputChannels.length; i++) {
outputChannels[i] = i;
}
}
channelMappingAudioProcessor.setChannelMap(outputChannels); channelMappingAudioProcessor.setChannelMap(outputChannels);
AudioProcessor.AudioFormat outputFormat = new AudioProcessor.AudioFormat(inputFormat); AudioProcessor.AudioFormat outputFormat = new AudioProcessor.AudioFormat(inputFormat);
@ -1160,32 +1150,10 @@ public final class DefaultAudioSink implements AudioSink {
Assertions.checkArgument(outputBuffer == buffer); Assertions.checkArgument(outputBuffer == buffer);
} else { } else {
outputBuffer = buffer; outputBuffer = buffer;
if (Util.SDK_INT < 21) {
int bytesRemaining = buffer.remaining();
if (preV21OutputBuffer == null || preV21OutputBuffer.length < bytesRemaining) {
preV21OutputBuffer = new byte[bytesRemaining];
}
int originalPosition = buffer.position();
buffer.get(preV21OutputBuffer, 0, bytesRemaining);
buffer.position(originalPosition);
preV21OutputBufferOffset = 0;
}
} }
int bytesRemaining = buffer.remaining(); int bytesRemaining = buffer.remaining();
int bytesWrittenOrError = 0; // Error if negative int bytesWrittenOrError = 0; // Error if negative
if (Util.SDK_INT < 21) { // outputMode == OUTPUT_MODE_PCM. if (tunneling) {
// Work out how many bytes we can write without the risk of blocking.
int bytesToWrite = audioTrackPositionTracker.getAvailableBufferSize(writtenPcmBytes);
if (bytesToWrite > 0) {
bytesToWrite = min(bytesRemaining, bytesToWrite);
bytesWrittenOrError =
audioTrack.write(preV21OutputBuffer, preV21OutputBufferOffset, bytesToWrite);
if (bytesWrittenOrError > 0) { // No error
preV21OutputBufferOffset += bytesWrittenOrError;
buffer.position(buffer.position() + bytesWrittenOrError);
}
}
} else if (tunneling) {
Assertions.checkState(avSyncPresentationTimeUs != C.TIME_UNSET); Assertions.checkState(avSyncPresentationTimeUs != C.TIME_UNSET);
if (avSyncPresentationTimeUs == C.TIME_END_OF_SOURCE) { if (avSyncPresentationTimeUs == C.TIME_END_OF_SOURCE) {
// Audio processors during tunneling are required to produce buffers immediately when // Audio processors during tunneling are required to produce buffers immediately when
@ -1196,10 +1164,9 @@ public final class DefaultAudioSink implements AudioSink {
lastTunnelingAvSyncPresentationTimeUs = avSyncPresentationTimeUs; lastTunnelingAvSyncPresentationTimeUs = avSyncPresentationTimeUs;
} }
bytesWrittenOrError = bytesWrittenOrError =
writeNonBlockingWithAvSyncV21( writeNonBlockingWithAvSync(audioTrack, buffer, bytesRemaining, avSyncPresentationTimeUs);
audioTrack, buffer, bytesRemaining, avSyncPresentationTimeUs);
} else { } else {
bytesWrittenOrError = writeNonBlockingV21(audioTrack, buffer, bytesRemaining); bytesWrittenOrError = writeNonBlocking(audioTrack, buffer, bytesRemaining);
} }
lastFeedElapsedRealtimeMs = SystemClock.elapsedRealtime(); lastFeedElapsedRealtimeMs = SystemClock.elapsedRealtime();
@ -1402,7 +1369,6 @@ public final class DefaultAudioSink implements AudioSink {
@Override @Override
public void enableTunnelingV21() { public void enableTunnelingV21() {
Assertions.checkState(Util.SDK_INT >= 21);
Assertions.checkState(externalAudioSessionIdProvided); Assertions.checkState(externalAudioSessionIdProvided);
if (!tunneling) { if (!tunneling) {
tunneling = true; tunneling = true;
@ -1445,12 +1411,8 @@ public final class DefaultAudioSink implements AudioSink {
} }
private void setVolumeInternal() { private void setVolumeInternal() {
if (!isAudioTrackInitialized()) { if (isAudioTrackInitialized()) {
// Do nothing. audioTrack.setVolume(volume);
} else if (Util.SDK_INT >= 21) {
setVolumeInternalV21(audioTrack, volume);
} else {
setVolumeInternalV3(audioTrack, volume);
} }
} }
@ -1474,14 +1436,6 @@ public final class DefaultAudioSink implements AudioSink {
if (isOffloadedPlayback(audioTrack)) { if (isOffloadedPlayback(audioTrack)) {
checkNotNull(offloadStreamEventCallbackV29).unregister(audioTrack); checkNotNull(offloadStreamEventCallbackV29).unregister(audioTrack);
} }
if (Util.SDK_INT < 21 && !externalAudioSessionIdProvided) {
// Prior to API level 21, audio sessions are not kept alive once there are no components
// associated with them. If we generated the session ID internally, the only component
// associated with the session is the audio track that's being released, and therefore
// the session will not be kept alive. As a result, we need to generate a new session when
// we next create an audio track.
audioSessionId = C.AUDIO_SESSION_ID_UNSET;
}
AudioTrackConfig oldAudioTrackConfig = configuration.buildAudioTrackConfig(); AudioTrackConfig oldAudioTrackConfig = configuration.buildAudioTrackConfig();
if (pendingConfiguration != null) { if (pendingConfiguration != null) {
configuration = pendingConfiguration; configuration = pendingConfiguration;
@ -1822,13 +1776,11 @@ public final class DefaultAudioSink implements AudioSink {
} }
} }
@RequiresApi(21) private static int writeNonBlocking(AudioTrack audioTrack, ByteBuffer buffer, int size) {
private static int writeNonBlockingV21(AudioTrack audioTrack, ByteBuffer buffer, int size) {
return audioTrack.write(buffer, size, AudioTrack.WRITE_NON_BLOCKING); return audioTrack.write(buffer, size, AudioTrack.WRITE_NON_BLOCKING);
} }
@RequiresApi(21) private int writeNonBlockingWithAvSync(
private int writeNonBlockingWithAvSyncV21(
AudioTrack audioTrack, ByteBuffer buffer, int size, long presentationTimeUs) { AudioTrack audioTrack, ByteBuffer buffer, int size, long presentationTimeUs) {
if (Util.SDK_INT >= 26) { if (Util.SDK_INT >= 26) {
// The underlying platform AudioTrack writes AV sync headers directly. // The underlying platform AudioTrack writes AV sync headers directly.
@ -1858,7 +1810,7 @@ public final class DefaultAudioSink implements AudioSink {
return 0; return 0;
} }
} }
int result = writeNonBlockingV21(audioTrack, buffer, size); int result = writeNonBlocking(audioTrack, buffer, size);
if (result < 0) { if (result < 0) {
bytesUntilNextAvSync = 0; bytesUntilNextAvSync = 0;
return result; return result;
@ -1867,15 +1819,6 @@ public final class DefaultAudioSink implements AudioSink {
return result; return result;
} }
@RequiresApi(21)
private static void setVolumeInternalV21(AudioTrack audioTrack, float volume) {
audioTrack.setVolume(volume);
}
private static void setVolumeInternalV3(AudioTrack audioTrack, float volume) {
audioTrack.setStereoVolume(volume, volume);
}
private void playPendingData() { private void playPendingData() {
if (!stoppedAudioTrack) { if (!stoppedAudioTrack) {
stoppedAudioTrack = true; stoppedAudioTrack = true;
@ -2253,10 +2196,8 @@ public final class DefaultAudioSink implements AudioSink {
private AudioTrack createAudioTrack(AudioAttributes audioAttributes, int audioSessionId) { private AudioTrack createAudioTrack(AudioAttributes audioAttributes, int audioSessionId) {
if (Util.SDK_INT >= 29) { if (Util.SDK_INT >= 29) {
return createAudioTrackV29(audioAttributes, audioSessionId); return createAudioTrackV29(audioAttributes, audioSessionId);
} else if (Util.SDK_INT >= 21) {
return createAudioTrackV21(audioAttributes, audioSessionId);
} else { } else {
return createAudioTrackV9(audioAttributes, audioSessionId); return createAudioTrackV21(audioAttributes, audioSessionId);
} }
} }
@ -2265,7 +2206,7 @@ public final class DefaultAudioSink implements AudioSink {
AudioFormat audioFormat = AudioFormat audioFormat =
Util.getAudioFormat(outputSampleRate, outputChannelConfig, outputEncoding); Util.getAudioFormat(outputSampleRate, outputChannelConfig, outputEncoding);
android.media.AudioAttributes audioTrackAttributes = android.media.AudioAttributes audioTrackAttributes =
getAudioTrackAttributesV21(audioAttributes, tunneling); getAudioTrackAttributes(audioAttributes, tunneling);
return new AudioTrack.Builder() return new AudioTrack.Builder()
.setAudioAttributes(audioTrackAttributes) .setAudioAttributes(audioTrackAttributes)
.setAudioFormat(audioFormat) .setAudioFormat(audioFormat)
@ -2276,59 +2217,28 @@ public final class DefaultAudioSink implements AudioSink {
.build(); .build();
} }
@RequiresApi(21)
private AudioTrack createAudioTrackV21(AudioAttributes audioAttributes, int audioSessionId) { private AudioTrack createAudioTrackV21(AudioAttributes audioAttributes, int audioSessionId) {
return new AudioTrack( return new AudioTrack(
getAudioTrackAttributesV21(audioAttributes, tunneling), getAudioTrackAttributes(audioAttributes, tunneling),
Util.getAudioFormat(outputSampleRate, outputChannelConfig, outputEncoding), Util.getAudioFormat(outputSampleRate, outputChannelConfig, outputEncoding),
bufferSize, bufferSize,
AudioTrack.MODE_STREAM, AudioTrack.MODE_STREAM,
audioSessionId); audioSessionId);
} }
@SuppressWarnings("deprecation") // Using deprecated AudioTrack constructor. private static android.media.AudioAttributes getAudioTrackAttributes(
private AudioTrack createAudioTrackV9(AudioAttributes audioAttributes, int audioSessionId) {
int streamType = Util.getStreamTypeForAudioUsage(audioAttributes.usage);
if (audioSessionId == C.AUDIO_SESSION_ID_UNSET) {
return new AudioTrack(
streamType,
outputSampleRate,
outputChannelConfig,
outputEncoding,
bufferSize,
AudioTrack.MODE_STREAM);
} else {
// Re-attach to the same audio session.
return new AudioTrack(
streamType,
outputSampleRate,
outputChannelConfig,
outputEncoding,
bufferSize,
AudioTrack.MODE_STREAM,
audioSessionId);
}
}
@RequiresApi(21)
private static android.media.AudioAttributes getAudioTrackAttributesV21(
AudioAttributes audioAttributes, boolean tunneling) { AudioAttributes audioAttributes, boolean tunneling) {
if (tunneling) { if (tunneling) {
return getAudioTrackTunnelingAttributesV21(); return new android.media.AudioAttributes.Builder()
.setContentType(android.media.AudioAttributes.CONTENT_TYPE_MOVIE)
.setFlags(android.media.AudioAttributes.FLAG_HW_AV_SYNC)
.setUsage(android.media.AudioAttributes.USAGE_MEDIA)
.build();
} else { } else {
return audioAttributes.getAudioAttributesV21().audioAttributes; return audioAttributes.getAudioAttributesV21().audioAttributes;
} }
} }
@RequiresApi(21)
private static android.media.AudioAttributes getAudioTrackTunnelingAttributesV21() {
return new android.media.AudioAttributes.Builder()
.setContentType(android.media.AudioAttributes.CONTENT_TYPE_MOVIE)
.setFlags(android.media.AudioAttributes.FLAG_HW_AV_SYNC)
.setUsage(android.media.AudioAttributes.USAGE_MEDIA)
.build();
}
public boolean outputModeIsOffload() { public boolean outputModeIsOffload() {
return outputMode == OUTPUT_MODE_OFFLOAD; return outputMode == OUTPUT_MODE_OFFLOAD;
} }

View File

@ -278,8 +278,6 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
if (!MimeTypes.isAudio(format.sampleMimeType)) { if (!MimeTypes.isAudio(format.sampleMimeType)) {
return RendererCapabilities.create(C.FORMAT_UNSUPPORTED_TYPE); return RendererCapabilities.create(C.FORMAT_UNSUPPORTED_TYPE);
} }
@TunnelingSupport
int tunnelingSupport = Util.SDK_INT >= 21 ? TUNNELING_SUPPORTED : TUNNELING_NOT_SUPPORTED;
boolean formatHasDrm = format.cryptoType != C.CRYPTO_TYPE_NONE; boolean formatHasDrm = format.cryptoType != C.CRYPTO_TYPE_NONE;
boolean supportsFormatDrm = supportsFormatDrm(format); boolean supportsFormatDrm = supportsFormatDrm(format);
@ -291,7 +289,7 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
audioOffloadSupport = getAudioOffloadSupport(format); audioOffloadSupport = getAudioOffloadSupport(format);
if (audioSink.supportsFormat(format)) { if (audioSink.supportsFormat(format)) {
return RendererCapabilities.create( return RendererCapabilities.create(
C.FORMAT_HANDLED, ADAPTIVE_NOT_SEAMLESS, tunnelingSupport, audioOffloadSupport); C.FORMAT_HANDLED, ADAPTIVE_NOT_SEAMLESS, TUNNELING_SUPPORTED, audioOffloadSupport);
} }
} }
// If the input is PCM then it will be passed directly to the sink. Hence the sink must support // If the input is PCM then it will be passed directly to the sink. Hence the sink must support
@ -346,7 +344,7 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
return RendererCapabilities.create( return RendererCapabilities.create(
formatSupport, formatSupport,
adaptiveSupport, adaptiveSupport,
tunnelingSupport, TUNNELING_SUPPORTED,
hardwareAccelerationSupport, hardwareAccelerationSupport,
decoderSupport, decoderSupport,
audioOffloadSupport); audioOffloadSupport);

View File

@ -77,8 +77,11 @@ public final class DrmUtil {
*/ */
public static @PlaybackException.ErrorCode int getErrorCodeForMediaDrmException( public static @PlaybackException.ErrorCode int getErrorCodeForMediaDrmException(
Throwable exception, @ErrorSource int errorSource) { Throwable exception, @ErrorSource int errorSource) {
if (Util.SDK_INT >= 21 && Api21.isMediaDrmStateException(exception)) { if (exception instanceof MediaDrm.MediaDrmStateException) {
return Api21.mediaDrmStateExceptionToErrorCode(exception); @Nullable
String diagnosticsInfo = ((MediaDrm.MediaDrmStateException) exception).getDiagnosticInfo();
int drmErrorCode = Util.getErrorCodeFromPlatformDiagnosticsInfo(diagnosticsInfo);
return Util.getErrorCodeForMediaDrmErrorCode(drmErrorCode);
} else if (Util.SDK_INT >= 23 && Api23.isMediaDrmResetException(exception)) { } else if (Util.SDK_INT >= 23 && Api23.isMediaDrmResetException(exception)) {
return PlaybackException.ERROR_CODE_DRM_SYSTEM_ERROR; return PlaybackException.ERROR_CODE_DRM_SYSTEM_ERROR;
} else if (exception instanceof NotProvisionedException } else if (exception instanceof NotProvisionedException
@ -128,26 +131,6 @@ public final class DrmUtil {
&& e.getMessage().contains("Landroid/media/ResourceBusyException;.<init>("); && e.getMessage().contains("Landroid/media/ResourceBusyException;.<init>(");
} }
// Internal classes.
@RequiresApi(21)
private static final class Api21 {
@DoNotInline
public static boolean isMediaDrmStateException(@Nullable Throwable throwable) {
return throwable instanceof MediaDrm.MediaDrmStateException;
}
@DoNotInline
public static @PlaybackException.ErrorCode int mediaDrmStateExceptionToErrorCode(
Throwable throwable) {
@Nullable
String diagnosticsInfo = ((MediaDrm.MediaDrmStateException) throwable).getDiagnosticInfo();
int drmErrorCode = Util.getErrorCodeFromPlatformDiagnosticsInfo(diagnosticsInfo);
return Util.getErrorCodeForMediaDrmErrorCode(drmErrorCode);
}
}
@RequiresApi(23) @RequiresApi(23)
private static final class Api23 { private static final class Api23 {

View File

@ -53,12 +53,22 @@ public final class FrameworkCryptoConfig implements CryptoConfig {
@Deprecated public final boolean forceAllowInsecureDecoderComponents; @Deprecated public final boolean forceAllowInsecureDecoderComponents;
/** /**
* Constructs an instance.
*
* @param uuid The DRM scheme UUID. * @param uuid The DRM scheme UUID.
* @param sessionId The DRM session id. * @param sessionId The DRM session id.
* @param forceAllowInsecureDecoderComponents Whether to allow use of insecure decoder components */
* even if the underlying platform says otherwise. @SuppressWarnings("deprecation") // Delegating to deprecated constructor
public FrameworkCryptoConfig(UUID uuid, byte[] sessionId) {
this(uuid, sessionId, /* forceAllowInsecureDecoderComponents= */ false);
}
/**
* @deprecated Use {@link FrameworkCryptoConfig#FrameworkCryptoConfig(UUID, byte[])} instead, and
* {@link ExoMediaDrm#requiresSecureDecoder} for the secure decoder handling logic.
*/ */
@SuppressWarnings("deprecation") // Setting deprecated field @SuppressWarnings("deprecation") // Setting deprecated field
@Deprecated
public FrameworkCryptoConfig( public FrameworkCryptoConfig(
UUID uuid, byte[] sessionId, boolean forceAllowInsecureDecoderComponents) { UUID uuid, byte[] sessionId, boolean forceAllowInsecureDecoderComponents) {
this.uuid = uuid; this.uuid = uuid;

View File

@ -308,7 +308,7 @@ public final class FrameworkMediaDrm implements ExoMediaDrm {
} }
} }
} }
return result && !shouldForceAllowInsecureDecoderComponents(); return result;
} }
@UnstableApi @UnstableApi
@ -389,17 +389,7 @@ public final class FrameworkMediaDrm implements ExoMediaDrm {
@UnstableApi @UnstableApi
@Override @Override
public FrameworkCryptoConfig createCryptoConfig(byte[] sessionId) throws MediaCryptoException { public FrameworkCryptoConfig createCryptoConfig(byte[] sessionId) throws MediaCryptoException {
boolean forceAllowInsecureDecoderComponents = shouldForceAllowInsecureDecoderComponents(); return new FrameworkCryptoConfig(adjustUuid(uuid), sessionId);
return new FrameworkCryptoConfig(
adjustUuid(uuid), sessionId, forceAllowInsecureDecoderComponents);
}
// Work around a bug prior to Lollipop where L1 Widevine forced into L3 mode would still
// indicate that it required secure video decoders [Internal ref: b/11428937].
private boolean shouldForceAllowInsecureDecoderComponents() {
return Util.SDK_INT < 21
&& C.WIDEVINE_UUID.equals(uuid)
&& "L3".equals(getPropertyString("securityLevel"));
} }
@UnstableApi @UnstableApi

View File

@ -252,7 +252,6 @@ public interface MediaCodecAdapter {
* *
* @see MediaCodec#releaseOutputBuffer(int, long) * @see MediaCodec#releaseOutputBuffer(int, long)
*/ */
@RequiresApi(21)
void releaseOutputBuffer(int index, long renderTimeStampNs); void releaseOutputBuffer(int index, long renderTimeStampNs);
/** Flushes the adapter and the underlying {@link MediaCodec}. */ /** Flushes the adapter and the underlying {@link MediaCodec}. */
@ -278,7 +277,6 @@ public interface MediaCodecAdapter {
* @see MediaCodec.Callback#onOutputBufferAvailable * @see MediaCodec.Callback#onOutputBufferAvailable
* @return Whether listener was successfully registered. * @return Whether listener was successfully registered.
*/ */
@RequiresApi(21)
default boolean registerOnBufferAvailableListener( default boolean registerOnBufferAvailableListener(
MediaCodecAdapter.OnBufferAvailableListener listener) { MediaCodecAdapter.OnBufferAvailableListener listener) {
return false; return false;

View File

@ -38,22 +38,16 @@ public class MediaCodecDecoderException extends DecoderException {
public MediaCodecDecoderException(Throwable cause, @Nullable MediaCodecInfo codecInfo) { public MediaCodecDecoderException(Throwable cause, @Nullable MediaCodecInfo codecInfo) {
super("Decoder failed: " + (codecInfo == null ? null : codecInfo.name), cause); super("Decoder failed: " + (codecInfo == null ? null : codecInfo.name), cause);
this.codecInfo = codecInfo; this.codecInfo = codecInfo;
diagnosticInfo = Util.SDK_INT >= 21 ? getDiagnosticInfoV21(cause) : null; diagnosticInfo =
cause instanceof MediaCodec.CodecException
? ((MediaCodec.CodecException) cause).getDiagnosticInfo()
: null;
errorCode = errorCode =
Util.SDK_INT >= 23 Util.SDK_INT >= 23
? getErrorCodeV23(cause) ? getErrorCodeV23(cause)
: Util.getErrorCodeFromPlatformDiagnosticsInfo(diagnosticInfo); : Util.getErrorCodeFromPlatformDiagnosticsInfo(diagnosticInfo);
} }
@RequiresApi(21)
@Nullable
private static String getDiagnosticInfoV21(Throwable cause) {
if (cause instanceof MediaCodec.CodecException) {
return ((MediaCodec.CodecException) cause).getDiagnosticInfo();
}
return null;
}
@RequiresApi(23) @RequiresApi(23)
private static int getErrorCodeV23(Throwable cause) { private static int getErrorCodeV23(Throwable cause) {
if (cause instanceof MediaCodec.CodecException) { if (cause instanceof MediaCodec.CodecException) {

View File

@ -258,22 +258,12 @@ public final class MediaCodecInfo {
if (format.width <= 0 || format.height <= 0) { if (format.width <= 0 || format.height <= 0) {
return true; return true;
} }
if (Util.SDK_INT >= 21) { return isVideoSizeAndRateSupportedV21(format.width, format.height, format.frameRate);
return isVideoSizeAndRateSupportedV21(format.width, format.height, format.frameRate);
} else {
boolean isFormatSupported =
format.width * format.height <= MediaCodecUtil.maxH264DecodableFrameSize();
if (!isFormatSupported) {
logNoSupport("legacyFrameSize, " + format.width + "x" + format.height);
}
return isFormatSupported;
}
} else { // Audio } else { // Audio
return Util.SDK_INT < 21 return (format.sampleRate == Format.NO_VALUE
|| ((format.sampleRate == Format.NO_VALUE || isAudioSampleRateSupportedV21(format.sampleRate))
|| isAudioSampleRateSupportedV21(format.sampleRate)) && (format.channelCount == Format.NO_VALUE
&& (format.channelCount == Format.NO_VALUE || isAudioChannelCountSupportedV21(format.channelCount));
|| isAudioChannelCountSupportedV21(format.channelCount)));
} }
} }
@ -482,7 +472,6 @@ public final class MediaCodecInfo {
* Format#NO_VALUE} or any value less than or equal to 0. * Format#NO_VALUE} or any value less than or equal to 0.
* @return Whether the decoder supports video with the given width, height and frame rate. * @return Whether the decoder supports video with the given width, height and frame rate.
*/ */
@RequiresApi(21)
public boolean isVideoSizeAndRateSupportedV21(int width, int height, double frameRate) { public boolean isVideoSizeAndRateSupportedV21(int width, int height, double frameRate) {
if (capabilities == null) { if (capabilities == null) {
logNoSupport("sizeAndRate.caps"); logNoSupport("sizeAndRate.caps");
@ -506,13 +495,13 @@ public final class MediaCodecInfo {
return false; return false;
} }
// If COVERAGE_RESULT_NO_PERFORMANCE_POINTS_UNSUPPORTED then logic falls through // If COVERAGE_RESULT_NO_PERFORMANCE_POINTS_UNSUPPORTED then logic falls through
// to API 21+ code below. // to code below.
} }
if (!areSizeAndRateSupportedV21(videoCapabilities, width, height, frameRate)) { if (!areSizeAndRateSupported(videoCapabilities, width, height, frameRate)) {
if (width >= height if (width >= height
|| !needsRotatedVerticalResolutionWorkaround(name) || !needsRotatedVerticalResolutionWorkaround(name)
|| !areSizeAndRateSupportedV21(videoCapabilities, height, width, frameRate)) { || !areSizeAndRateSupported(videoCapabilities, height, width, frameRate)) {
logNoSupport("sizeAndRate.support, " + width + "x" + height + "@" + frameRate); logNoSupport("sizeAndRate.support, " + width + "x" + height + "@" + frameRate);
return false; return false;
} }
@ -525,8 +514,6 @@ public final class MediaCodecInfo {
* Returns the smallest video size greater than or equal to a specified size that also satisfies * Returns the smallest video size greater than or equal to a specified size that also satisfies
* the {@link MediaCodec}'s width and height alignment requirements. * the {@link MediaCodec}'s width and height alignment requirements.
* *
* <p>Must not be called if the device SDK version is less than 21.
*
* @param width Width in pixels. * @param width Width in pixels.
* @param height Height in pixels. * @param height Height in pixels.
* @return The smallest video size greater than or equal to the specified size that also satisfies * @return The smallest video size greater than or equal to the specified size that also satisfies
@ -534,7 +521,6 @@ public final class MediaCodecInfo {
* codec. * codec.
*/ */
@Nullable @Nullable
@RequiresApi(21)
public Point alignVideoSizeV21(int width, int height) { public Point alignVideoSizeV21(int width, int height) {
if (capabilities == null) { if (capabilities == null) {
return null; return null;
@ -543,18 +529,15 @@ public final class MediaCodecInfo {
if (videoCapabilities == null) { if (videoCapabilities == null) {
return null; return null;
} }
return alignVideoSizeV21(videoCapabilities, width, height); return alignVideoSize(videoCapabilities, width, height);
} }
/** /**
* Whether the decoder supports audio with a given sample rate. * Whether the decoder supports audio with a given sample rate.
* *
* <p>Must not be called if the device SDK version is less than 21.
*
* @param sampleRate The sample rate in Hz. * @param sampleRate The sample rate in Hz.
* @return Whether the decoder supports audio with the given sample rate. * @return Whether the decoder supports audio with the given sample rate.
*/ */
@RequiresApi(21)
public boolean isAudioSampleRateSupportedV21(int sampleRate) { public boolean isAudioSampleRateSupportedV21(int sampleRate) {
if (capabilities == null) { if (capabilities == null) {
logNoSupport("sampleRate.caps"); logNoSupport("sampleRate.caps");
@ -575,12 +558,9 @@ public final class MediaCodecInfo {
/** /**
* Whether the decoder supports audio with a given channel count. * Whether the decoder supports audio with a given channel count.
* *
* <p>Must not be called if the device SDK version is less than 21.
*
* @param channelCount The channel count. * @param channelCount The channel count.
* @return Whether the decoder supports audio with the given channel count. * @return Whether the decoder supports audio with the given channel count.
*/ */
@RequiresApi(21)
public boolean isAudioChannelCountSupportedV21(int channelCount) { public boolean isAudioChannelCountSupportedV21(int channelCount) {
if (capabilities == null) { if (capabilities == null) {
logNoSupport("channelCount.caps"); logNoSupport("channelCount.caps");
@ -674,28 +654,17 @@ public final class MediaCodecInfo {
} }
private static boolean isTunneling(CodecCapabilities capabilities) { private static boolean isTunneling(CodecCapabilities capabilities) {
return Util.SDK_INT >= 21 && isTunnelingV21(capabilities);
}
@RequiresApi(21)
private static boolean isTunnelingV21(CodecCapabilities capabilities) {
return capabilities.isFeatureSupported(CodecCapabilities.FEATURE_TunneledPlayback); return capabilities.isFeatureSupported(CodecCapabilities.FEATURE_TunneledPlayback);
} }
private static boolean isSecure(CodecCapabilities capabilities) { private static boolean isSecure(CodecCapabilities capabilities) {
return Util.SDK_INT >= 21 && isSecureV21(capabilities);
}
@RequiresApi(21)
private static boolean isSecureV21(CodecCapabilities capabilities) {
return capabilities.isFeatureSupported(CodecCapabilities.FEATURE_SecurePlayback); return capabilities.isFeatureSupported(CodecCapabilities.FEATURE_SecurePlayback);
} }
@RequiresApi(21) private static boolean areSizeAndRateSupported(
private static boolean areSizeAndRateSupportedV21(
VideoCapabilities capabilities, int width, int height, double frameRate) { VideoCapabilities capabilities, int width, int height, double frameRate) {
// Don't ever fail due to alignment. See: https://github.com/google/ExoPlayer/issues/6551. // Don't ever fail due to alignment. See: https://github.com/google/ExoPlayer/issues/6551.
Point alignedSize = alignVideoSizeV21(capabilities, width, height); Point alignedSize = alignVideoSize(capabilities, width, height);
width = alignedSize.x; width = alignedSize.x;
height = alignedSize.y; height = alignedSize.y;
@ -712,8 +681,7 @@ public final class MediaCodecInfo {
} }
} }
@RequiresApi(21) private static Point alignVideoSize(VideoCapabilities capabilities, int width, int height) {
private static Point alignVideoSizeV21(VideoCapabilities capabilities, int width, int height) {
int widthAlignment = capabilities.getWidthAlignment(); int widthAlignment = capabilities.getWidthAlignment();
int heightAlignment = capabilities.getHeightAlignment(); int heightAlignment = capabilities.getHeightAlignment();
return new Point( return new Point(

View File

@ -166,12 +166,9 @@ public final class MediaCodecUtil {
if (cachedDecoderInfos != null) { if (cachedDecoderInfos != null) {
return cachedDecoderInfos; return cachedDecoderInfos;
} }
MediaCodecListCompat mediaCodecList = MediaCodecListCompat mediaCodecList = new MediaCodecListCompatV21(secure, tunneling);
Util.SDK_INT >= 21
? new MediaCodecListCompatV21(secure, tunneling)
: new MediaCodecListCompatV16();
ArrayList<MediaCodecInfo> decoderInfos = getDecoderInfosInternal(key, mediaCodecList); ArrayList<MediaCodecInfo> decoderInfos = getDecoderInfosInternal(key, mediaCodecList);
if (secure && decoderInfos.isEmpty() && 21 <= Util.SDK_INT && Util.SDK_INT <= 23) { if (secure && decoderInfos.isEmpty() && Util.SDK_INT <= 23) {
// Some devices don't list secure decoders on API level 21 [Internal: b/18678462]. Try the // Some devices don't list secure decoders on API level 21 [Internal: b/18678462]. Try the
// legacy path. We also try this path on API levels 22 and 23 as a defensive measure. // legacy path. We also try this path on API levels 22 and 23 as a defensive measure.
mediaCodecList = new MediaCodecListCompatV16(); mediaCodecList = new MediaCodecListCompatV16();
@ -290,9 +287,8 @@ public final class MediaCodecUtil {
for (CodecProfileLevel profileLevel : decoderInfo.getProfileLevels()) { for (CodecProfileLevel profileLevel : decoderInfo.getProfileLevels()) {
result = max(avcLevelToMaxFrameSize(profileLevel.level), result); result = max(avcLevelToMaxFrameSize(profileLevel.level), result);
} }
// We assume support for at least 480p (SDK_INT >= 21) or 360p (SDK_INT < 21), which are // We assume support for at least 480p, which is the level mandated by the Android CDD.
// the levels mandated by the Android CDD. result = max(result, 720 * 480);
result = max(result, Util.SDK_INT >= 21 ? (720 * 480) : (480 * 360));
} }
maxH264DecodableFrameSize = result; maxH264DecodableFrameSize = result;
} }
@ -562,17 +558,6 @@ public final class MediaCodecUtil {
return false; return false;
} }
// Work around broken audio decoders.
if (Util.SDK_INT < 21
&& ("CIPAACDecoder".equals(name)
|| "CIPMP3Decoder".equals(name)
|| "CIPVorbisDecoder".equals(name)
|| "CIPAMRNBDecoder".equals(name)
|| "AACDecoder".equals(name)
|| "MP3Decoder".equals(name))) {
return false;
}
// Work around https://github.com/google/ExoPlayer/issues/3249. // Work around https://github.com/google/ExoPlayer/issues/3249.
if (Util.SDK_INT < 24 if (Util.SDK_INT < 24
&& ("OMX.SEC.aac.dec".equals(name) || "OMX.Exynos.AAC.Decoder".equals(name)) && ("OMX.SEC.aac.dec".equals(name) || "OMX.Exynos.AAC.Decoder".equals(name))
@ -588,26 +573,6 @@ public final class MediaCodecUtil {
return false; return false;
} }
// Work around https://github.com/google/ExoPlayer/issues/548.
// VP8 decoder on Samsung Galaxy S3/S4/S4 Mini/Tab 3/Note 2 does not render video.
if (Util.SDK_INT == 19
&& "OMX.SEC.vp8.dec".equals(name)
&& "samsung".equals(Util.MANUFACTURER)
&& (Util.DEVICE.startsWith("d2")
|| Util.DEVICE.startsWith("serrano")
|| Util.DEVICE.startsWith("jflte")
|| Util.DEVICE.startsWith("santos")
|| Util.DEVICE.startsWith("t0"))) {
return false;
}
// VP8 decoder on Samsung Galaxy S4 cannot be queried.
if (Util.SDK_INT == 19
&& Util.DEVICE.startsWith("jflte")
&& "OMX.qcom.video.decoder.vp8".equals(name)) {
return false;
}
// MTK AC3 decoder doesn't support decoding JOC streams in 2-D. See [Internal: b/69400041]. // MTK AC3 decoder doesn't support decoding JOC streams in 2-D. See [Internal: b/69400041].
if (Util.SDK_INT <= 23 if (Util.SDK_INT <= 23
&& MimeTypes.AUDIO_E_AC3_JOC.equals(mimeType) && MimeTypes.AUDIO_E_AC3_JOC.equals(mimeType)
@ -663,19 +628,6 @@ public final class MediaCodecUtil {
}); });
} }
if (Util.SDK_INT < 21 && decoderInfos.size() > 1) {
String firstCodecName = decoderInfos.get(0).name;
if ("OMX.SEC.mp3.dec".equals(firstCodecName)
|| "OMX.SEC.MP3.Decoder".equals(firstCodecName)
|| "OMX.brcm.audio.mp3.decoder".equals(firstCodecName)) {
// Prefer OMX.google codecs over OMX.SEC.mp3.dec, OMX.SEC.MP3.Decoder and
// OMX.brcm.audio.mp3.decoder on older devices. See:
// https://github.com/google/ExoPlayer/issues/398 and
// https://github.com/google/ExoPlayer/issues/4519.
sortByScore(decoderInfos, decoderInfo -> decoderInfo.name.startsWith("OMX.google") ? 1 : 0);
}
}
if (Util.SDK_INT < 32 && decoderInfos.size() > 1) { if (Util.SDK_INT < 32 && decoderInfos.size() > 1) {
String firstCodecName = decoderInfos.get(0).name; String firstCodecName = decoderInfos.get(0).name;
// Prefer anything other than OMX.qti.audio.decoder.flac on older devices. See [Internal // Prefer anything other than OMX.qti.audio.decoder.flac on older devices. See [Internal
@ -1056,7 +1008,6 @@ public final class MediaCodecUtil {
boolean isFeatureRequired(String feature, String mimeType, CodecCapabilities capabilities); boolean isFeatureRequired(String feature, String mimeType, CodecCapabilities capabilities);
} }
@RequiresApi(21)
private static final class MediaCodecListCompatV21 implements MediaCodecListCompat { private static final class MediaCodecListCompatV21 implements MediaCodecListCompat {
private final int codecKind; private final int codecKind;

View File

@ -17,7 +17,6 @@
package androidx.media3.exoplayer.mediacodec; package androidx.media3.exoplayer.mediacodec;
import static androidx.media3.common.util.Assertions.checkNotNull; import static androidx.media3.common.util.Assertions.checkNotNull;
import static androidx.media3.common.util.Util.castNonNull;
import android.media.MediaCodec; import android.media.MediaCodec;
import android.media.MediaFormat; import android.media.MediaFormat;
@ -80,15 +79,9 @@ public final class SynchronousMediaCodecAdapter implements MediaCodecAdapter {
} }
private final MediaCodec codec; private final MediaCodec codec;
@Nullable private ByteBuffer[] inputByteBuffers;
@Nullable private ByteBuffer[] outputByteBuffers;
private SynchronousMediaCodecAdapter(MediaCodec mediaCodec) { private SynchronousMediaCodecAdapter(MediaCodec mediaCodec) {
this.codec = mediaCodec; this.codec = mediaCodec;
if (Util.SDK_INT < 21) {
inputByteBuffers = codec.getInputBuffers();
outputByteBuffers = codec.getOutputBuffers();
}
} }
@Override @Override
@ -106,9 +99,6 @@ public final class SynchronousMediaCodecAdapter implements MediaCodecAdapter {
int index; int index;
do { do {
index = codec.dequeueOutputBuffer(bufferInfo, 0); index = codec.dequeueOutputBuffer(bufferInfo, 0);
if (index == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED && Util.SDK_INT < 21) {
outputByteBuffers = codec.getOutputBuffers();
}
} while (index == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED); } while (index == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED);
return index; return index;
@ -122,21 +112,13 @@ public final class SynchronousMediaCodecAdapter implements MediaCodecAdapter {
@Override @Override
@Nullable @Nullable
public ByteBuffer getInputBuffer(int index) { public ByteBuffer getInputBuffer(int index) {
if (Util.SDK_INT >= 21) { return codec.getInputBuffer(index);
return codec.getInputBuffer(index);
} else {
return castNonNull(inputByteBuffers)[index];
}
} }
@Override @Override
@Nullable @Nullable
public ByteBuffer getOutputBuffer(int index) { public ByteBuffer getOutputBuffer(int index) {
if (Util.SDK_INT >= 21) { return codec.getOutputBuffer(index);
return codec.getOutputBuffer(index);
} else {
return castNonNull(outputByteBuffers)[index];
}
} }
@Override @Override
@ -158,7 +140,6 @@ public final class SynchronousMediaCodecAdapter implements MediaCodecAdapter {
} }
@Override @Override
@RequiresApi(21)
public void releaseOutputBuffer(int index, long renderTimeStampNs) { public void releaseOutputBuffer(int index, long renderTimeStampNs) {
codec.releaseOutputBuffer(index, renderTimeStampNs); codec.releaseOutputBuffer(index, renderTimeStampNs);
} }
@ -170,8 +151,6 @@ public final class SynchronousMediaCodecAdapter implements MediaCodecAdapter {
@Override @Override
public void release() { public void release() {
inputByteBuffers = null;
outputByteBuffers = null;
try { try {
if (Util.SDK_INT >= 30 && Util.SDK_INT < 33) { if (Util.SDK_INT >= 30 && Util.SDK_INT < 33) {
// Stopping the codec before releasing it works around a bug on APIs 30, 31 and 32 where // Stopping the codec before releasing it works around a bug on APIs 30, 31 and 32 where

View File

@ -25,7 +25,6 @@ import android.content.ComponentName;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.os.PersistableBundle; import android.os.PersistableBundle;
import androidx.annotation.RequiresApi;
import androidx.annotation.RequiresPermission; import androidx.annotation.RequiresPermission;
import androidx.media3.common.util.Log; import androidx.media3.common.util.Log;
import androidx.media3.common.util.UnstableApi; import androidx.media3.common.util.UnstableApi;
@ -44,7 +43,6 @@ import androidx.media3.common.util.Util;
* android:exported="true"/> * android:exported="true"/>
* }</pre> * }</pre>
*/ */
@RequiresApi(21)
@UnstableApi @UnstableApi
public final class PlatformScheduler implements Scheduler { public final class PlatformScheduler implements Scheduler {

View File

@ -202,9 +202,7 @@ public final class Requirements implements Parcelable {
private boolean isDeviceIdle(Context context) { private boolean isDeviceIdle(Context context) {
PowerManager powerManager = PowerManager powerManager =
(PowerManager) Assertions.checkNotNull(context.getSystemService(Context.POWER_SERVICE)); (PowerManager) Assertions.checkNotNull(context.getSystemService(Context.POWER_SERVICE));
return Util.SDK_INT >= 23 return Util.SDK_INT >= 23 ? powerManager.isDeviceIdleMode() : !powerManager.isInteractive();
? powerManager.isDeviceIdleMode()
: Util.SDK_INT >= 20 ? !powerManager.isInteractive() : !powerManager.isScreenOn();
} }
private boolean isStorageNotLow(Context context) { private boolean isStorageNotLow(Context context) {

View File

@ -58,13 +58,10 @@ import java.lang.annotation.Documented;
import java.lang.annotation.Retention; import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.concurrent.CopyOnWriteArraySet; import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import org.checkerframework.checker.nullness.qual.EnsuresNonNull;
import org.checkerframework.checker.nullness.qual.EnsuresNonNullIf; import org.checkerframework.checker.nullness.qual.EnsuresNonNullIf;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull; import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
@ -491,7 +488,6 @@ public final class CompositingVideoSinkProvider implements VideoSinkProvider, Vi
private final VideoFrameReleaseControl.FrameReleaseInfo frameReleaseInfo; private final VideoFrameReleaseControl.FrameReleaseInfo frameReleaseInfo;
private @MonotonicNonNull VideoFrameProcessor videoFrameProcessor; private @MonotonicNonNull VideoFrameProcessor videoFrameProcessor;
@Nullable private Effect rotationEffect;
@Nullable private Format inputFormat; @Nullable private Format inputFormat;
private @InputType int inputType; private @InputType int inputType;
private long inputStreamStartPositionUs; private long inputStreamStartPositionUs;
@ -611,21 +607,6 @@ public final class CompositingVideoSinkProvider implements VideoSinkProvider, Vi
throw new UnsupportedOperationException("Unsupported input type " + inputType); throw new UnsupportedOperationException("Unsupported input type " + inputType);
} }
videoFrameReleaseControl.setFrameRate(format.frameRate); videoFrameReleaseControl.setFrameRate(format.frameRate);
// MediaCodec applies rotation after API 21.
if (inputType == INPUT_TYPE_SURFACE
&& Util.SDK_INT < 21
&& format.rotationDegrees != Format.NO_VALUE
&& format.rotationDegrees != 0) {
// We must apply a rotation effect.
if (rotationEffect == null
|| this.inputFormat == null
|| this.inputFormat.rotationDegrees != format.rotationDegrees) {
rotationEffect = ScaleAndRotateAccessor.createRotationEffect(format.rotationDegrees);
} // Else, the rotation effect matches the previous format's rotation degrees, keep the same
// instance.
} else {
rotationEffect = null;
}
this.inputType = inputType; this.inputType = inputType;
this.inputFormat = format; this.inputFormat = format;
@ -884,9 +865,6 @@ public final class CompositingVideoSinkProvider implements VideoSinkProvider, Vi
} }
ArrayList<Effect> effects = new ArrayList<>(); ArrayList<Effect> effects = new ArrayList<>();
if (rotationEffect != null) {
effects.add(rotationEffect);
}
effects.addAll(videoEffects); effects.addAll(videoEffects);
Format inputFormat = checkNotNull(this.inputFormat); Format inputFormat = checkNotNull(this.inputFormat);
checkStateNotNull(videoFrameProcessor) checkStateNotNull(videoFrameProcessor)
@ -1081,42 +1059,4 @@ public final class CompositingVideoSinkProvider implements VideoSinkProvider, Vi
listener); listener);
} }
} }
private static final class ScaleAndRotateAccessor {
private static @MonotonicNonNull Constructor<?> scaleAndRotateTransformationBuilderConstructor;
private static @MonotonicNonNull Method setRotationMethod;
private static @MonotonicNonNull Method buildScaleAndRotateTransformationMethod;
public static Effect createRotationEffect(float rotationDegrees) {
try {
prepare();
Object builder = scaleAndRotateTransformationBuilderConstructor.newInstance();
setRotationMethod.invoke(builder, rotationDegrees);
return (Effect) checkNotNull(buildScaleAndRotateTransformationMethod.invoke(builder));
} catch (Exception e) {
throw new IllegalStateException(e);
}
}
@EnsuresNonNull({
"scaleAndRotateTransformationBuilderConstructor",
"setRotationMethod",
"buildScaleAndRotateTransformationMethod"
})
private static void prepare() throws NoSuchMethodException, ClassNotFoundException {
if (scaleAndRotateTransformationBuilderConstructor == null
|| setRotationMethod == null
|| buildScaleAndRotateTransformationMethod == null) {
// TODO: b/284964524 - Add LINT and proguard checks for media3.effect reflection.
Class<?> scaleAndRotateTransformationBuilderClass =
Class.forName("androidx.media3.effect.ScaleAndRotateTransformation$Builder");
scaleAndRotateTransformationBuilderConstructor =
scaleAndRotateTransformationBuilderClass.getConstructor();
setRotationMethod =
scaleAndRotateTransformationBuilderClass.getMethod("setRotationDegrees", float.class);
buildScaleAndRotateTransformationMethod =
scaleAndRotateTransformationBuilderClass.getMethod("build");
}
}
}
} }

View File

@ -18,7 +18,6 @@ package androidx.media3.exoplayer.audio;
import static androidx.media3.common.C.FORMAT_HANDLED; import static androidx.media3.common.C.FORMAT_HANDLED;
import static androidx.media3.exoplayer.RendererCapabilities.ADAPTIVE_NOT_SEAMLESS; import static androidx.media3.exoplayer.RendererCapabilities.ADAPTIVE_NOT_SEAMLESS;
import static androidx.media3.exoplayer.RendererCapabilities.DECODER_SUPPORT_PRIMARY; import static androidx.media3.exoplayer.RendererCapabilities.DECODER_SUPPORT_PRIMARY;
import static androidx.media3.exoplayer.RendererCapabilities.TUNNELING_NOT_SUPPORTED;
import static androidx.media3.exoplayer.RendererCapabilities.TUNNELING_SUPPORTED; import static androidx.media3.exoplayer.RendererCapabilities.TUNNELING_SUPPORTED;
import static androidx.media3.test.utils.FakeSampleStream.FakeSampleStreamItem.END_OF_STREAM_ITEM; import static androidx.media3.test.utils.FakeSampleStream.FakeSampleStreamItem.END_OF_STREAM_ITEM;
import static androidx.media3.test.utils.FakeSampleStream.FakeSampleStreamItem.oneByteSample; import static androidx.media3.test.utils.FakeSampleStream.FakeSampleStreamItem.oneByteSample;
@ -96,17 +95,6 @@ public class DecoderAudioRendererTest {
audioRenderer.init(/* index= */ 0, PlayerId.UNSET, Clock.DEFAULT); audioRenderer.init(/* index= */ 0, PlayerId.UNSET, Clock.DEFAULT);
} }
@Config(sdk = 19)
@Test
public void supportsFormatAtApi19() {
assertThat(audioRenderer.supportsFormat(FORMAT))
.isEqualTo(
ADAPTIVE_NOT_SEAMLESS
| TUNNELING_NOT_SUPPORTED
| FORMAT_HANDLED
| DECODER_SUPPORT_PRIMARY);
}
@Config(sdk = 21) @Config(sdk = 21)
@Test @Test
public void supportsFormatAtApi21() { public void supportsFormatAtApi21() {

View File

@ -315,20 +315,6 @@ public final class DefaultAudioSinkTest {
.isEqualTo(SINK_FORMAT_SUPPORTED_WITH_TRANSCODING); .isEqualTo(SINK_FORMAT_SUPPORTED_WITH_TRANSCODING);
} }
@Config(maxSdk = 20)
@Test
public void floatPcmNeedsTranscodingIfFloatOutputEnabledBeforeApi21() {
defaultAudioSink = new DefaultAudioSink.Builder().setEnableFloatOutput(true).build();
Format floatFormat =
STEREO_44_1_FORMAT
.buildUpon()
.setSampleMimeType(MimeTypes.AUDIO_RAW)
.setPcmEncoding(C.ENCODING_PCM_FLOAT)
.build();
assertThat(defaultAudioSink.getFormatSupport(floatFormat))
.isEqualTo(SINK_FORMAT_SUPPORTED_WITH_TRANSCODING);
}
@Config(minSdk = 21) @Config(minSdk = 21)
@Test @Test
public void floatOutputSupportedIfFloatOutputEnabledFromApi21() { public void floatOutputSupportedIfFloatOutputEnabledFromApi21() {

View File

@ -21,7 +21,6 @@ import android.media.MediaCodec.BufferInfo;
import androidx.media3.common.C; import androidx.media3.common.C;
import androidx.media3.common.Format; import androidx.media3.common.Format;
import androidx.media3.common.MimeTypes; import androidx.media3.common.MimeTypes;
import androidx.media3.common.util.Util;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
@ -218,8 +217,7 @@ import org.checkerframework.checker.nullness.qual.PolyNull;
return null; return null;
} }
Locale locale = Locale locale = Locale.forLanguageTag(languageTag);
Util.SDK_INT >= 21 ? Locale.forLanguageTag(languageTag) : new Locale(languageTag);
return locale.getISO3Language().isEmpty() ? languageTag : locale.getISO3Language(); return locale.getISO3Language().isEmpty() ? languageTag : locale.getISO3Language();
} }

View File

@ -366,7 +366,7 @@ public class DefaultMediaNotificationProvider implements MediaNotification.Provi
} }
} }
if (player.isCommandAvailable(COMMAND_STOP) || Util.SDK_INT < 21) { if (player.isCommandAvailable(COMMAND_STOP)) {
// We must include a cancel intent for pre-L devices. // We must include a cancel intent for pre-L devices.
mediaStyle.setCancelButtonIntent( mediaStyle.setCancelButtonIntent(
actionFactory.createMediaActionPendingIntent(mediaSession, COMMAND_STOP)); actionFactory.createMediaActionPendingIntent(mediaSession, COMMAND_STOP));
@ -639,9 +639,7 @@ public class DefaultMediaNotificationProvider implements MediaNotification.Provi
} }
private static long getPlaybackStartTimeEpochMs(Player player) { private static long getPlaybackStartTimeEpochMs(Player player) {
// Changing "showWhen" causes notification flicker if SDK_INT < 21. if (player.isPlaying()
if (Util.SDK_INT >= 21
&& player.isPlaying()
&& !player.isPlayingAd() && !player.isPlayingAd()
&& !player.isCurrentMediaItemDynamic() && !player.isCurrentMediaItemDynamic()
&& player.getPlaybackParameters().speed == 1f) { && player.getPlaybackParameters().speed == 1f) {

View File

@ -98,13 +98,8 @@ public abstract class MediaLibraryService extends MediaSessionService {
* <th>{@link ControllerInfo#getPackageName()}<br>for legacy browser</th> * <th>{@link ControllerInfo#getPackageName()}<br>for legacy browser</th>
* <th>{@link ControllerInfo#getUid()}<br>for legacy browser</th></tr> * <th>{@link ControllerInfo#getUid()}<br>for legacy browser</th></tr>
* <tr> * <tr>
* <td>{@code SDK_INT < 21}</td>
* <td>Actual package name via {@link Context#getPackageName()}</td>
* <td>Actual UID</td>
* </tr>
* <tr>
* <td> * <td>
* {@code 21 <= SDK_INT < 28}<br> * {@code SDK_INT < 28}<br>
* for {@link Callback#onConnect onConnect}<br> * for {@link Callback#onConnect onConnect}<br>
* and {@link Callback#onGetLibraryRoot onGetLibraryRoot} * and {@link Callback#onGetLibraryRoot onGetLibraryRoot}
* </td> * </td>
@ -113,7 +108,7 @@ public abstract class MediaLibraryService extends MediaSessionService {
* </tr> * </tr>
* <tr> * <tr>
* <td> * <td>
* {@code 21 <= SDK_INT < 28}<br> * {@code SDK_INT < 28}<br>
* for other {@link Callback callbacks} * for other {@link Callback callbacks}
* </td> * </td>
* <td>{@link ControllerInfo#LEGACY_CONTROLLER_PACKAGE_NAME}</td> * <td>{@link ControllerInfo#LEGACY_CONTROLLER_PACKAGE_NAME}</td>

View File

@ -210,14 +210,11 @@ import java.util.concurrent.TimeoutException;
MediaSession session, MediaSession session,
MediaNotification mediaNotification, MediaNotification mediaNotification,
boolean startInForegroundRequired) { boolean startInForegroundRequired) {
if (Util.SDK_INT >= 21) { // Call Notification.MediaStyle#setMediaSession() indirectly.
// Call Notification.MediaStyle#setMediaSession() indirectly. android.media.session.MediaSession.Token fwkToken =
android.media.session.MediaSession.Token fwkToken = (android.media.session.MediaSession.Token)
(android.media.session.MediaSession.Token) session.getSessionCompat().getSessionToken().getToken();
session.getSessionCompat().getSessionToken().getToken(); mediaNotification.notification.extras.putParcelable(Notification.EXTRA_MEDIA_SESSION, fwkToken);
mediaNotification.notification.extras.putParcelable(
Notification.EXTRA_MEDIA_SESSION, fwkToken);
}
this.mediaNotification = mediaNotification; this.mediaNotification = mediaNotification;
if (startInForegroundRequired) { if (startInForegroundRequired) {
startForeground(mediaNotification); startForeground(mediaNotification);
@ -379,9 +376,7 @@ import java.util.concurrent.TimeoutException;
if (Util.SDK_INT >= 24) { if (Util.SDK_INT >= 24) {
Api24.stopForeground(mediaSessionService, removeNotifications); Api24.stopForeground(mediaSessionService, removeNotifications);
} else { } else {
// For pre-L devices, we must call Service.stopForeground(true) anyway as a workaround mediaSessionService.stopForeground(removeNotifications);
// that prevents the media notification from being undismissable.
mediaSessionService.stopForeground(removeNotifications || Util.SDK_INT < 21);
} }
startedInForeground = false; startedInForeground = false;
} }

View File

@ -25,7 +25,6 @@ import static androidx.media3.session.SessionResult.RESULT_SUCCESS;
import android.app.PendingIntent; import android.app.PendingIntent;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.Uri; import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.os.IBinder; import android.os.IBinder;
@ -524,25 +523,7 @@ public class MediaSession {
return interfaceVersion; return interfaceVersion;
} }
/** /** Returns the package name, or {@link #LEGACY_CONTROLLER_PACKAGE_NAME} on API &le; 24. */
* Returns the package name. Can be {@link #LEGACY_CONTROLLER_PACKAGE_NAME} for
* interoperability.
*
* <p>Interoperability: Package name may not be precisely obtained for legacy controller API on
* older device. Here are details.
*
* <table>
* <caption>Summary when package name isn't precise</caption>
* <tr><th>SDK version when package name isn't precise</th>
* <th>{@code ControllerInfo#getPackageName()} for legacy controller</th>
* <tr><td>{@code SDK_INT < 21}</td>
* <td>Actual package name via {@link PackageManager#getNameForUid} with UID.<br>
* It's sufficient for most cases, but doesn't precisely distinguish caller if it
* uses shared user ID.</td>
* <tr><td>{@code 21 <= SDK_INT < 24}</td>
* <td>{@link #LEGACY_CONTROLLER_PACKAGE_NAME}</td>
* </table>
*/
public String getPackageName() { public String getPackageName() {
return remoteUserInfo.getPackageName(); return remoteUserInfo.getPackageName();
} }
@ -550,8 +531,8 @@ public class MediaSession {
/** /**
* Returns the UID of the controller. Can be a negative value for interoperability. * Returns the UID of the controller. Can be a negative value for interoperability.
* *
* <p>Interoperability: If {@code 21 <= SDK_INT < 28}, then UID would be a negative value * <p>Interoperability: If {@code SDK_INT < 28}, then UID would be a negative value because it
* because it cannot be obtained. * cannot be obtained.
*/ */
public int getUid() { public int getUid() {
return remoteUserInfo.getUid(); return remoteUserInfo.getUid();
@ -1221,7 +1202,6 @@ public class MediaSession {
* android.media.session.MediaSession} created internally by this session. * android.media.session.MediaSession} created internally by this session.
*/ */
@SuppressWarnings("UnnecessarilyFullyQualified") // Avoiding clash with Media3 token. @SuppressWarnings("UnnecessarilyFullyQualified") // Avoiding clash with Media3 token.
@RequiresApi(21)
@UnstableApi @UnstableApi
public final android.media.session.MediaSession.Token getPlatformToken() { public final android.media.session.MediaSession.Token getPlatformToken() {
return (android.media.session.MediaSession.Token) return (android.media.session.MediaSession.Token)

View File

@ -53,11 +53,9 @@ import android.os.SystemClock;
import android.view.KeyEvent; import android.view.KeyEvent;
import android.view.ViewConfiguration; import android.view.ViewConfiguration;
import androidx.annotation.CheckResult; import androidx.annotation.CheckResult;
import androidx.annotation.DoNotInline;
import androidx.annotation.FloatRange; import androidx.annotation.FloatRange;
import androidx.annotation.GuardedBy; import androidx.annotation.GuardedBy;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.media3.common.AudioAttributes; import androidx.media3.common.AudioAttributes;
import androidx.media3.common.DeviceInfo; import androidx.media3.common.DeviceInfo;
import androidx.media3.common.MediaItem; import androidx.media3.common.MediaItem;
@ -1213,7 +1211,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
} }
// Double tap detection. // Double tap detection.
int keyCode = keyEvent.getKeyCode(); int keyCode = keyEvent.getKeyCode();
boolean isTvApp = Util.SDK_INT >= 21 && Api21.isTvApp(context); boolean isTvApp = context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK);
boolean doubleTapCompleted = false; boolean doubleTapCompleted = false;
switch (keyCode) { switch (keyCode) {
case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE: case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
@ -1910,12 +1908,4 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
} }
} }
} }
@RequiresApi(21)
private static final class Api21 {
@DoNotInline
public static boolean isTvApp(Context context) {
return context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK);
}
}
} }

View File

@ -35,7 +35,6 @@ import static androidx.media3.common.util.Assertions.checkNotNull;
import static androidx.media3.common.util.Assertions.checkStateNotNull; import static androidx.media3.common.util.Assertions.checkStateNotNull;
import static androidx.media3.common.util.Util.castNonNull; import static androidx.media3.common.util.Util.castNonNull;
import static androidx.media3.common.util.Util.postOrRun; import static androidx.media3.common.util.Util.postOrRun;
import static androidx.media3.session.MediaUtils.TRANSACTION_SIZE_LIMIT_IN_BYTES;
import static androidx.media3.session.SessionCommand.COMMAND_CODE_CUSTOM; import static androidx.media3.session.SessionCommand.COMMAND_CODE_CUSTOM;
import static androidx.media3.session.SessionError.ERROR_UNKNOWN; import static androidx.media3.session.SessionError.ERROR_UNKNOWN;
import static androidx.media3.session.SessionResult.RESULT_INFO_SKIPPED; import static androidx.media3.session.SessionResult.RESULT_INFO_SKIPPED;
@ -1226,7 +1225,7 @@ import org.checkerframework.checker.initialization.qual.Initialized;
() -> { () -> {
int completedBitmapFutureCount = resultCount.incrementAndGet(); int completedBitmapFutureCount = resultCount.incrementAndGet();
if (completedBitmapFutureCount == mediaItemList.size()) { if (completedBitmapFutureCount == mediaItemList.size()) {
handleBitmapFuturesAllCompletedAndSetQueue(bitmapFutures, timeline, mediaItemList); handleBitmapFuturesAllCompletedAndSetQueue(bitmapFutures, mediaItemList);
} }
}; };
@ -1247,9 +1246,7 @@ import org.checkerframework.checker.initialization.qual.Initialized;
} }
private void handleBitmapFuturesAllCompletedAndSetQueue( private void handleBitmapFuturesAllCompletedAndSetQueue(
List<@NullableType ListenableFuture<Bitmap>> bitmapFutures, List<@NullableType ListenableFuture<Bitmap>> bitmapFutures, List<MediaItem> mediaItems) {
Timeline timeline,
List<MediaItem> mediaItems) {
List<QueueItem> queueItemList = new ArrayList<>(); List<QueueItem> queueItemList = new ArrayList<>();
for (int i = 0; i < bitmapFutures.size(); i++) { for (int i = 0; i < bitmapFutures.size(); i++) {
@Nullable ListenableFuture<Bitmap> future = bitmapFutures.get(i); @Nullable ListenableFuture<Bitmap> future = bitmapFutures.get(i);
@ -1264,22 +1261,9 @@ import org.checkerframework.checker.initialization.qual.Initialized;
queueItemList.add(LegacyConversions.convertToQueueItem(mediaItems.get(i), i, bitmap)); queueItemList.add(LegacyConversions.convertToQueueItem(mediaItems.get(i), i, bitmap));
} }
if (Util.SDK_INT < 21) { // Framework MediaSession#setQueue() uses ParceledListSlice,
// In order to avoid TransactionTooLargeException for below API 21, we need to // which means we can safely send long lists.
// cut the list so that it doesn't exceed the binder transaction limit. setQueue(sessionCompat, queueItemList);
List<QueueItem> truncatedList =
MediaUtils.truncateListBySize(queueItemList, TRANSACTION_SIZE_LIMIT_IN_BYTES);
if (truncatedList.size() != timeline.getWindowCount()) {
Log.i(
TAG,
"Sending " + truncatedList.size() + " items out of " + timeline.getWindowCount());
}
setQueue(sessionCompat, truncatedList);
} else {
// Framework MediaSession#setQueue() uses ParceledListSlice,
// which means we can safely send long lists.
setQueue(sessionCompat, queueItemList);
}
} }
@Override @Override

View File

@ -300,8 +300,7 @@ public final class SessionToken {
private static MediaSessionCompat.Token createCompatToken( private static MediaSessionCompat.Token createCompatToken(
Parcelable platformOrLegacyCompatToken) { Parcelable platformOrLegacyCompatToken) {
if (Util.SDK_INT >= 21 if (platformOrLegacyCompatToken instanceof android.media.session.MediaSession.Token) {
&& platformOrLegacyCompatToken instanceof android.media.session.MediaSession.Token) {
return MediaSessionCompat.Token.fromToken(platformOrLegacyCompatToken); return MediaSessionCompat.Token.fromToken(platformOrLegacyCompatToken);
} }
// Assume this is an android.support.v4.media.session.MediaSessionCompat.Token. // Assume this is an android.support.v4.media.session.MediaSessionCompat.Token.

View File

@ -666,7 +666,7 @@ public final class DashStreamingTest {
MediaCodecUtil.getDecoderInfo( MediaCodecUtil.getDecoderInfo(
MimeTypes.VIDEO_H264, /* secure= */ false, /* tunneling= */ false); MimeTypes.VIDEO_H264, /* secure= */ false, /* tunneling= */ false);
assertThat(decoderInfo).isNotNull(); assertThat(decoderInfo).isNotNull();
assertThat(Util.SDK_INT < 21 || decoderInfo.adaptive).isTrue(); assertThat(decoderInfo.adaptive).isTrue();
} }
@Test @Test

View File

@ -17,13 +17,11 @@ package androidx.media3.test.exoplayer.playback.gts;
import static java.lang.Math.max; import static java.lang.Math.max;
import android.annotation.SuppressLint;
import android.content.Context; import android.content.Context;
import android.media.MediaCrypto; import android.media.MediaCrypto;
import android.media.MediaFormat; import android.media.MediaFormat;
import android.os.Handler; import android.os.Handler;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.media3.common.C; import androidx.media3.common.C;
import androidx.media3.common.Format; import androidx.media3.common.Format;
import androidx.media3.decoder.DecoderInputBuffer; import androidx.media3.decoder.DecoderInputBuffer;
@ -244,8 +242,6 @@ import java.util.ArrayList;
super.renderOutputBuffer(codec, index, presentationTimeUs); super.renderOutputBuffer(codec, index, presentationTimeUs);
} }
@SuppressLint("UseSdkSuppress") // Not a test class or method.
@RequiresApi(21)
@Override @Override
protected void renderOutputBufferV21( protected void renderOutputBufferV21(
MediaCodecAdapter codec, int index, long presentationTimeUs, long releaseTimeNs) { MediaCodecAdapter codec, int index, long presentationTimeUs, long releaseTimeNs) {

View File

@ -115,9 +115,7 @@ public class EnumerateDecodersTest {
boolean isAudio = MimeTypes.isAudio(requestedMimeType); boolean isAudio = MimeTypes.isAudio(requestedMimeType);
StringBuilder result = new StringBuilder(); StringBuilder result = new StringBuilder();
result.append("[requestedMimeType=").append(requestedMimeType); result.append("[requestedMimeType=").append(requestedMimeType);
if (Util.SDK_INT >= 21) { result.append(", mimeType=").append(codecCapabilities.getMimeType());
result.append(", mimeType=").append(codecCapabilities.getMimeType());
}
result.append(", profileLevels="); result.append(", profileLevels=");
appendProfileLevels(codecCapabilities.profileLevels, result); appendProfileLevels(codecCapabilities.profileLevels, result);
if (Util.SDK_INT >= 23) { if (Util.SDK_INT >= 23) {
@ -125,23 +123,19 @@ public class EnumerateDecodersTest {
.append(", maxSupportedInstances=") .append(", maxSupportedInstances=")
.append(codecCapabilities.getMaxSupportedInstances()); .append(codecCapabilities.getMaxSupportedInstances());
} }
if (Util.SDK_INT >= 21) { if (isVideo) {
if (isVideo) { result.append(", videoCapabilities=");
result.append(", videoCapabilities="); appendVideoCapabilities(codecCapabilities.getVideoCapabilities(), result);
appendVideoCapabilities(codecCapabilities.getVideoCapabilities(), result); result.append(", colorFormats=").append(Arrays.toString(codecCapabilities.colorFormats));
result.append(", colorFormats=").append(Arrays.toString(codecCapabilities.colorFormats)); } else if (isAudio) {
} else if (isAudio) { result.append(", audioCapabilities=");
result.append(", audioCapabilities="); appendAudioCapabilities(codecCapabilities.getAudioCapabilities(), result);
appendAudioCapabilities(codecCapabilities.getAudioCapabilities(), result);
}
} }
if (isVideo if (isVideo
&& codecCapabilities.isFeatureSupported(CodecCapabilities.FEATURE_AdaptivePlayback)) { && codecCapabilities.isFeatureSupported(CodecCapabilities.FEATURE_AdaptivePlayback)) {
result.append(", FEATURE_AdaptivePlayback"); result.append(", FEATURE_AdaptivePlayback");
} }
if (Util.SDK_INT >= 21 if (isVideo && codecCapabilities.isFeatureSupported(CodecCapabilities.FEATURE_SecurePlayback)) {
&& isVideo
&& codecCapabilities.isFeatureSupported(CodecCapabilities.FEATURE_SecurePlayback)) {
result.append(", FEATURE_SecurePlayback"); result.append(", FEATURE_SecurePlayback");
} }
if (Util.SDK_INT >= 26 if (Util.SDK_INT >= 26
@ -149,8 +143,7 @@ public class EnumerateDecodersTest {
&& codecCapabilities.isFeatureSupported(CodecCapabilities.FEATURE_PartialFrame)) { && codecCapabilities.isFeatureSupported(CodecCapabilities.FEATURE_PartialFrame)) {
result.append(", FEATURE_PartialFrame"); result.append(", FEATURE_PartialFrame");
} }
if (Util.SDK_INT >= 21 if ((isVideo || isAudio)
&& (isVideo || isAudio)
&& codecCapabilities.isFeatureSupported(CodecCapabilities.FEATURE_TunneledPlayback)) { && codecCapabilities.isFeatureSupported(CodecCapabilities.FEATURE_TunneledPlayback)) {
result.append(", FEATURE_TunneledPlayback"); result.append(", FEATURE_TunneledPlayback");
} }

View File

@ -610,7 +610,7 @@ public class MediaControllerCompatCallbackWithMediaSessionTest {
session.setPlayer(playerConfigToUpdate); session.setPlayer(playerConfigToUpdate);
// In API 21 and 22, onAudioInfoChanged is not called when playback is changed to local. // In API 21 and 22, onAudioInfoChanged is not called when playback is changed to local.
if (Util.SDK_INT == 21 || Util.SDK_INT == 22) { if (Util.SDK_INT <= 22) {
PollingCheck.waitFor( PollingCheck.waitFor(
TIMEOUT_MS, TIMEOUT_MS,
() -> { () -> {
@ -657,21 +657,13 @@ public class MediaControllerCompatCallbackWithMediaSessionTest {
session.setPlayer(playerConfig); session.setPlayer(playerConfig);
// In API 21+, onAudioInfoChanged() is not called when playbackType is not changed. PollingCheck.waitFor(
if (Util.SDK_INT >= 21) { TIMEOUT_MS,
PollingCheck.waitFor( () -> {
TIMEOUT_MS, MediaControllerCompat.PlaybackInfo info = controllerCompat.getPlaybackInfo();
() -> { return info.getPlaybackType() == legacyPlaybackType
MediaControllerCompat.PlaybackInfo info = controllerCompat.getPlaybackInfo(); && info.getAudioAttributes().getLegacyStreamType() == legacyStream;
return info.getPlaybackType() == legacyPlaybackType });
&& info.getAudioAttributes().getLegacyStreamType() == legacyStream;
});
} else {
assertThat(playbackInfoNotified.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
MediaControllerCompat.PlaybackInfo info = controllerCompat.getPlaybackInfo();
assertThat(info.getPlaybackType()).isEqualTo(legacyPlaybackType);
assertThat(info.getAudioAttributes().getLegacyStreamType()).isEqualTo(legacyStream);
}
} }
@Test @Test
@ -709,23 +701,14 @@ public class MediaControllerCompatCallbackWithMediaSessionTest {
session.setPlayer(playerConfigToUpdate); session.setPlayer(playerConfigToUpdate);
// In API 21+, onAudioInfoChanged() is not called when playbackType is not changed. PollingCheck.waitFor(
if (Util.SDK_INT >= 21) { TIMEOUT_MS,
PollingCheck.waitFor( () -> {
TIMEOUT_MS, MediaControllerCompat.PlaybackInfo info = controllerCompat.getPlaybackInfo();
() -> { return info.getPlaybackType() == legacyPlaybackTypeToUpdate
MediaControllerCompat.PlaybackInfo info = controllerCompat.getPlaybackInfo(); && info.getMaxVolume() == deviceInfoToUpdate.maxVolume
return info.getPlaybackType() == legacyPlaybackTypeToUpdate && info.getCurrentVolume() == deviceVolumeToUpdate;
&& info.getMaxVolume() == deviceInfoToUpdate.maxVolume });
&& info.getCurrentVolume() == deviceVolumeToUpdate;
});
} else {
assertThat(playbackInfoNotified.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
MediaControllerCompat.PlaybackInfo info = controllerCompat.getPlaybackInfo();
assertThat(info.getPlaybackType()).isEqualTo(legacyPlaybackTypeToUpdate);
assertThat(info.getMaxVolume()).isEqualTo(deviceInfoToUpdate.maxVolume);
assertThat(info.getCurrentVolume()).isEqualTo(deviceVolumeToUpdate);
}
} }
@Test @Test
@ -1555,15 +1538,8 @@ public class MediaControllerCompatCallbackWithMediaSessionTest {
assertThat(latch.await(LONG_TIMEOUT_MS, MILLISECONDS)).isTrue(); assertThat(latch.await(LONG_TIMEOUT_MS, MILLISECONDS)).isTrue();
List<QueueItem> queueFromParam = queueRef.get(); List<QueueItem> queueFromParam = queueRef.get();
List<QueueItem> queueFromGetter = controllerCompat.getQueue(); List<QueueItem> queueFromGetter = controllerCompat.getQueue();
if (Util.SDK_INT >= 21) { assertThat(queueFromParam).hasSize(listSize);
assertThat(queueFromParam).hasSize(listSize); assertThat(queueFromGetter).hasSize(listSize);
assertThat(queueFromGetter).hasSize(listSize);
} else {
// Below API 21, only the initial part of the playlist is sent to the
// MediaControllerCompat when the list is too long.
assertThat(queueFromParam.size() < listSize).isTrue();
assertThat(queueFromGetter).hasSize(queueFromParam.size());
}
for (int i = 0; i < queueFromParam.size(); i++) { for (int i = 0; i < queueFromParam.size(); i++) {
assertThat(queueFromParam.get(i).getDescription().getMediaId()) assertThat(queueFromParam.get(i).getDescription().getMediaId())
.isEqualTo(TestUtils.getMediaIdInFakeTimeline(i)); .isEqualTo(TestUtils.getMediaIdInFakeTimeline(i));

View File

@ -347,7 +347,7 @@ public class MediaControllerListenerWithMediaSessionCompatTest {
// We need to trigger MediaControllerCompat.Callback.onAudioInfoChanged in order to raise the // We need to trigger MediaControllerCompat.Callback.onAudioInfoChanged in order to raise the
// onAudioAttributesChanged() callback. In API 21 and 22, onAudioInfoChanged is not called when // onAudioAttributesChanged() callback. In API 21 and 22, onAudioInfoChanged is not called when
// playback is changed to local. // playback is changed to local.
assumeTrue(Util.SDK_INT != 21 && Util.SDK_INT != 22); assumeTrue(Util.SDK_INT > 22);
session.setPlaybackToRemote( session.setPlaybackToRemote(
/* volumeControl= */ VolumeProviderCompat.VOLUME_CONTROL_ABSOLUTE, /* volumeControl= */ VolumeProviderCompat.VOLUME_CONTROL_ABSOLUTE,

View File

@ -49,7 +49,6 @@ import androidx.media3.common.Player.PositionInfo;
import androidx.media3.common.Timeline; import androidx.media3.common.Timeline;
import androidx.media3.common.Timeline.Window; import androidx.media3.common.Timeline.Window;
import androidx.media3.common.util.BitmapLoader; import androidx.media3.common.util.BitmapLoader;
import androidx.media3.common.util.Util;
import androidx.media3.datasource.DataSourceBitmapLoader; import androidx.media3.datasource.DataSourceBitmapLoader;
import androidx.media3.test.session.common.HandlerThreadTestRule; import androidx.media3.test.session.common.HandlerThreadTestRule;
import androidx.test.core.app.ApplicationProvider; import androidx.test.core.app.ApplicationProvider;
@ -200,24 +199,6 @@ public class MediaControllerMediaSessionCompatCallbackAggregationTest {
MediaItem mediaItem = timelineRef.get().getWindow(i, new Window()).mediaItem; MediaItem mediaItem = timelineRef.get().getWindow(i, new Window()).mediaItem;
MediaItem expectedMediaItem = MediaItem expectedMediaItem =
(i == testMediaItemIndex) ? testCurrentMediaItem : testMediaItems.get(i); (i == testMediaItemIndex) ? testCurrentMediaItem : testMediaItems.get(i);
if (Util.SDK_INT < 21) {
// Bitmap conversion and back gives not exactly the same byte array below API 21
MediaMetadata mediaMetadata =
mediaItem
.mediaMetadata
.buildUpon()
.setArtworkData(/* artworkData= */ null, /* artworkDataType= */ null)
.build();
MediaMetadata expectedMediaMetadata =
expectedMediaItem
.mediaMetadata
.buildUpon()
.setArtworkData(/* artworkData= */ null, /* artworkDataType= */ null)
.build();
mediaItem = mediaItem.buildUpon().setMediaMetadata(mediaMetadata).build();
expectedMediaItem =
expectedMediaItem.buildUpon().setMediaMetadata(expectedMediaMetadata).build();
}
assertThat(mediaItem).isEqualTo(expectedMediaItem); assertThat(mediaItem).isEqualTo(expectedMediaItem);
} }
assertThat(timelineChangeReasonRef.get()).isEqualTo(TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED); assertThat(timelineChangeReasonRef.get()).isEqualTo(TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED);

View File

@ -27,7 +27,6 @@ import android.media.session.PlaybackState;
import android.os.HandlerThread; import android.os.HandlerThread;
import androidx.media3.common.Player; import androidx.media3.common.Player;
import androidx.media3.common.Player.State; import androidx.media3.common.Player.State;
import androidx.media3.common.util.Util;
import androidx.media3.test.session.common.MainLooperTestRule; import androidx.media3.test.session.common.MainLooperTestRule;
import androidx.media3.test.session.common.TestHandler; import androidx.media3.test.session.common.TestHandler;
import androidx.test.core.app.ApplicationProvider; import androidx.test.core.app.ApplicationProvider;
@ -37,7 +36,6 @@ import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import org.junit.After; import org.junit.After;
import org.junit.Assume;
import org.junit.Before; import org.junit.Before;
import org.junit.ClassRule; import org.junit.ClassRule;
import org.junit.Test; import org.junit.Test;
@ -57,9 +55,6 @@ public class MediaControllerWithFrameworkMediaSessionTest {
@Before @Before
public void setUp() { public void setUp() {
if (Util.SDK_INT < 21) {
return;
}
context = ApplicationProvider.getApplicationContext(); context = ApplicationProvider.getApplicationContext();
HandlerThread handlerThread = new HandlerThread(TAG); HandlerThread handlerThread = new HandlerThread(TAG);
@ -79,7 +74,6 @@ public class MediaControllerWithFrameworkMediaSessionTest {
@SuppressWarnings("UnnecessarilyFullyQualified") // Intentionally fully qualified for fwk session. @SuppressWarnings("UnnecessarilyFullyQualified") // Intentionally fully qualified for fwk session.
@Test @Test
public void createController() throws Exception { public void createController() throws Exception {
Assume.assumeTrue(Util.SDK_INT >= 21); // For framework MediaSession.
MediaSession fwkSession = new android.media.session.MediaSession(context, TAG); MediaSession fwkSession = new android.media.session.MediaSession(context, TAG);
fwkSession.setActive(true); fwkSession.setActive(true);
fwkSession.setFlags( fwkSession.setFlags(
@ -100,7 +94,6 @@ public class MediaControllerWithFrameworkMediaSessionTest {
@SuppressWarnings("UnnecessarilyFullyQualified") // Intentionally fully qualified for fwk session. @SuppressWarnings("UnnecessarilyFullyQualified") // Intentionally fully qualified for fwk session.
@Test @Test
public void onPlaybackStateChanged_isNotifiedByFwkSessionChanges() throws Exception { public void onPlaybackStateChanged_isNotifiedByFwkSessionChanges() throws Exception {
Assume.assumeTrue(Util.SDK_INT >= 21); // For framework MediaSession.
MediaSession fwkSession = new android.media.session.MediaSession(context, TAG); MediaSession fwkSession = new android.media.session.MediaSession(context, TAG);
fwkSession.setActive(true); fwkSession.setActive(true);
fwkSession.setFlags( fwkSession.setFlags(

View File

@ -571,11 +571,8 @@ public class MediaControllerWithMediaSessionCompatTest {
assertThat(TextUtils.equals(metadata.subtitle, testSubtitle)).isTrue(); assertThat(TextUtils.equals(metadata.subtitle, testSubtitle)).isTrue();
assertThat(TextUtils.equals(metadata.description, testDescription)).isTrue(); assertThat(TextUtils.equals(metadata.description, testDescription)).isTrue();
assertThat(metadata.artworkUri).isEqualTo(testIconUri); assertThat(metadata.artworkUri).isEqualTo(testIconUri);
if (Util.SDK_INT >= 21) { assertThat(metadata.artworkData).isEqualTo(testArtworkData);
// Bitmap conversion and back gives not exactly the same byte array below API 21 if (Util.SDK_INT >= 23) {
assertThat(metadata.artworkData).isEqualTo(testArtworkData);
}
if (Util.SDK_INT < 21 || Util.SDK_INT >= 23) {
// TODO(b/199055952): Test mediaUri for all API levels once the bug is fixed. // TODO(b/199055952): Test mediaUri for all API levels once the bug is fixed.
assertThat(mediaItem.requestMetadata.mediaUri).isEqualTo(testMediaUri); assertThat(mediaItem.requestMetadata.mediaUri).isEqualTo(testMediaUri);
} }
@ -909,19 +906,6 @@ public class MediaControllerWithMediaSessionCompatTest {
threadTestRule.getHandler().postAndSync(controller::getMediaMetadata); threadTestRule.getHandler().postAndSync(controller::getMediaMetadata);
assertThat(mediaMetadata.artworkData).isNotNull(); assertThat(mediaMetadata.artworkData).isNotNull();
if (Util.SDK_INT < 21) {
// Bitmap conversion and back gives not exactly the same byte array below API 21
mediaMetadata =
mediaMetadata
.buildUpon()
.setArtworkData(/* artworkData= */ null, /* artworkDataType= */ null)
.build();
testMediaMetadata =
testMediaMetadata
.buildUpon()
.setArtworkData(/* artworkData= */ null, /* artworkDataType= */ null)
.build();
}
assertThat(mediaMetadata.artworkData).isEqualTo(testMediaMetadata.artworkData); assertThat(mediaMetadata.artworkData).isEqualTo(testMediaMetadata.artworkData);
} }
@ -1573,7 +1557,7 @@ public class MediaControllerWithMediaSessionCompatTest {
@Test @Test
public void setPlaybackToLocal_notifiesDeviceInfoAndVolume() throws Exception { public void setPlaybackToLocal_notifiesDeviceInfoAndVolume() throws Exception {
if (Util.SDK_INT == 21 || Util.SDK_INT == 22) { if (Util.SDK_INT <= 22) {
// In API 21 and 22, onAudioInfoChanged is not called. // In API 21 and 22, onAudioInfoChanged is not called.
return; return;
} }

View File

@ -91,7 +91,7 @@ public class MediaSessionCallbackWithMediaControllerCompatTest {
private static final String TEST_URI = "http://test.test"; private static final String TEST_URI = "http://test.test";
private static final String EXPECTED_CONTROLLER_PACKAGE_NAME = private static final String EXPECTED_CONTROLLER_PACKAGE_NAME =
(Util.SDK_INT < 21 || Util.SDK_INT >= 24) ? SUPPORT_APP_PACKAGE_NAME : LEGACY_CONTROLLER; Util.SDK_INT >= 24 ? SUPPORT_APP_PACKAGE_NAME : LEGACY_CONTROLLER;
@ClassRule public static MainLooperTestRule mainLooperTestRule = new MainLooperTestRule(); @ClassRule public static MainLooperTestRule mainLooperTestRule = new MainLooperTestRule();
@ -1200,7 +1200,7 @@ public class MediaSessionCallbackWithMediaControllerCompatTest {
@Test @Test
public void setVolumeWithLocalVolume() throws Exception { public void setVolumeWithLocalVolume() throws Exception {
if (Util.SDK_INT >= 21 && audioManager.isVolumeFixed()) { if (audioManager.isVolumeFixed()) {
// This test is not eligible for this device. // This test is not eligible for this device.
return; return;
} }
@ -1250,7 +1250,7 @@ public class MediaSessionCallbackWithMediaControllerCompatTest {
@Test @Test
public void adjustVolumeWithLocalVolume() throws Exception { public void adjustVolumeWithLocalVolume() throws Exception {
if (Util.SDK_INT >= 21 && audioManager.isVolumeFixed()) { if (audioManager.isVolumeFixed()) {
// This test is not eligible for this device. // This test is not eligible for this device.
return; return;
} }

View File

@ -541,7 +541,7 @@ public class MediaSessionCompatCallbackWithMediaControllerTest {
@Test @Test
public void setDeviceVolume_forLocalPlayback_setsStreamVolume() throws Exception { public void setDeviceVolume_forLocalPlayback_setsStreamVolume() throws Exception {
if (Util.SDK_INT >= 21 && audioManager.isVolumeFixed()) { if (audioManager.isVolumeFixed()) {
// This test is not eligible for this device. // This test is not eligible for this device.
return; return;
} }
@ -570,7 +570,7 @@ public class MediaSessionCompatCallbackWithMediaControllerTest {
@Test @Test
public void increaseDeviceVolume_forLocalPlayback_increasesStreamVolume() throws Exception { public void increaseDeviceVolume_forLocalPlayback_increasesStreamVolume() throws Exception {
if (Util.SDK_INT >= 21 && audioManager.isVolumeFixed()) { if (audioManager.isVolumeFixed()) {
// This test is not eligible for this device. // This test is not eligible for this device.
return; return;
} }
@ -600,7 +600,7 @@ public class MediaSessionCompatCallbackWithMediaControllerTest {
@Test @Test
public void decreaseDeviceVolume_forLocalPlayback_decreasesStreamVolume() throws Exception { public void decreaseDeviceVolume_forLocalPlayback_decreasesStreamVolume() throws Exception {
if (Util.SDK_INT >= 21 && audioManager.isVolumeFixed()) { if (audioManager.isVolumeFixed()) {
// This test is not eligible for this device. // This test is not eligible for this device.
return; return;
} }

View File

@ -45,7 +45,6 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.concurrent.CountDownLatch; import java.util.concurrent.CountDownLatch;
import org.junit.After; import org.junit.After;
import org.junit.Assume;
import org.junit.Before; import org.junit.Before;
import org.junit.ClassRule; import org.junit.ClassRule;
import org.junit.Rule; import org.junit.Rule;
@ -79,9 +78,6 @@ public class MediaSessionKeyEventTest {
@Before @Before
public void setUp() throws Exception { public void setUp() throws Exception {
if (Util.SDK_INT < 21) {
return;
}
Context context = ApplicationProvider.getApplicationContext(); Context context = ApplicationProvider.getApplicationContext();
audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
handler = threadTestRule.getHandler(); handler = threadTestRule.getHandler();
@ -128,9 +124,6 @@ public class MediaSessionKeyEventTest {
@After @After
public void tearDown() throws Exception { public void tearDown() throws Exception {
if (Util.SDK_INT < 21) {
return;
}
handler.postAndSync( handler.postAndSync(
() -> { () -> {
if (mediaPlayer != null) { if (mediaPlayer != null) {
@ -143,7 +136,6 @@ public class MediaSessionKeyEventTest {
@Test @Test
public void playKeyEvent() throws Exception { public void playKeyEvent() throws Exception {
Assume.assumeTrue(Util.SDK_INT >= 21); // TODO: b/199064299 - Lower minSdk to 19.
dispatchMediaKeyEvent(KeyEvent.KEYCODE_MEDIA_PLAY, false); dispatchMediaKeyEvent(KeyEvent.KEYCODE_MEDIA_PLAY, false);
player.awaitMethodCalled(MockPlayer.METHOD_PLAY, TIMEOUT_MS); player.awaitMethodCalled(MockPlayer.METHOD_PLAY, TIMEOUT_MS);
@ -151,7 +143,6 @@ public class MediaSessionKeyEventTest {
@Test @Test
public void pauseKeyEvent() throws Exception { public void pauseKeyEvent() throws Exception {
Assume.assumeTrue(Util.SDK_INT >= 21); // TODO: b/199064299 - Lower minSdk to 19.
dispatchMediaKeyEvent(KeyEvent.KEYCODE_MEDIA_PAUSE, false); dispatchMediaKeyEvent(KeyEvent.KEYCODE_MEDIA_PAUSE, false);
player.awaitMethodCalled(MockPlayer.METHOD_PAUSE, TIMEOUT_MS); player.awaitMethodCalled(MockPlayer.METHOD_PAUSE, TIMEOUT_MS);
@ -159,7 +150,6 @@ public class MediaSessionKeyEventTest {
@Test @Test
public void nextKeyEvent() throws Exception { public void nextKeyEvent() throws Exception {
Assume.assumeTrue(Util.SDK_INT >= 21); // TODO: b/199064299 - Lower minSdk to 19.
dispatchMediaKeyEvent(KeyEvent.KEYCODE_MEDIA_NEXT, false); dispatchMediaKeyEvent(KeyEvent.KEYCODE_MEDIA_NEXT, false);
player.awaitMethodCalled(MockPlayer.METHOD_SEEK_TO_NEXT, TIMEOUT_MS); player.awaitMethodCalled(MockPlayer.METHOD_SEEK_TO_NEXT, TIMEOUT_MS);
@ -167,7 +157,6 @@ public class MediaSessionKeyEventTest {
@Test @Test
public void previousKeyEvent() throws Exception { public void previousKeyEvent() throws Exception {
Assume.assumeTrue(Util.SDK_INT >= 21); // TODO: b/199064299 - Lower minSdk to 19.
dispatchMediaKeyEvent(KeyEvent.KEYCODE_MEDIA_PREVIOUS, false); dispatchMediaKeyEvent(KeyEvent.KEYCODE_MEDIA_PREVIOUS, false);
player.awaitMethodCalled(MockPlayer.METHOD_SEEK_TO_PREVIOUS, TIMEOUT_MS); player.awaitMethodCalled(MockPlayer.METHOD_SEEK_TO_PREVIOUS, TIMEOUT_MS);
@ -177,7 +166,6 @@ public class MediaSessionKeyEventTest {
public void public void
fastForwardKeyEvent_mediaNotificationControllerConnected_callFromNotificationController() fastForwardKeyEvent_mediaNotificationControllerConnected_callFromNotificationController()
throws Exception { throws Exception {
Assume.assumeTrue(Util.SDK_INT >= 21); // TODO: b/199064299 - Lower minSdk to 19.
MediaController controller = connectMediaNotificationController(); MediaController controller = connectMediaNotificationController();
dispatchMediaKeyEvent(KeyEvent.KEYCODE_MEDIA_FAST_FORWARD, /* doubleTap= */ false); dispatchMediaKeyEvent(KeyEvent.KEYCODE_MEDIA_FAST_FORWARD, /* doubleTap= */ false);
@ -204,8 +192,6 @@ public class MediaSessionKeyEventTest {
public void public void
fastForwardKeyEvent_mediaNotificationControllerNotConnected_callFromLegacyFallbackController() fastForwardKeyEvent_mediaNotificationControllerNotConnected_callFromLegacyFallbackController()
throws Exception { throws Exception {
Assume.assumeTrue(Util.SDK_INT >= 21); // TODO: b/199064299 - Lower minSdk to 19.
dispatchMediaKeyEvent(KeyEvent.KEYCODE_MEDIA_FAST_FORWARD, false); dispatchMediaKeyEvent(KeyEvent.KEYCODE_MEDIA_FAST_FORWARD, false);
player.awaitMethodCalled(MockPlayer.METHOD_SEEK_FORWARD, TIMEOUT_MS); player.awaitMethodCalled(MockPlayer.METHOD_SEEK_FORWARD, TIMEOUT_MS);
@ -220,7 +206,6 @@ public class MediaSessionKeyEventTest {
@Test @Test
public void rewindKeyEvent_mediaNotificationControllerConnected_callFromNotificationController() public void rewindKeyEvent_mediaNotificationControllerConnected_callFromNotificationController()
throws Exception { throws Exception {
Assume.assumeTrue(Util.SDK_INT >= 21); // TODO: b/199064299 - Lower minSdk to 19.
MediaController controller = connectMediaNotificationController(); MediaController controller = connectMediaNotificationController();
dispatchMediaKeyEvent(KeyEvent.KEYCODE_MEDIA_REWIND, false); dispatchMediaKeyEvent(KeyEvent.KEYCODE_MEDIA_REWIND, false);
@ -246,8 +231,6 @@ public class MediaSessionKeyEventTest {
public void public void
rewindKeyEvent_mediaNotificationControllerNotConnected_callFromLegacyFallbackController() rewindKeyEvent_mediaNotificationControllerNotConnected_callFromLegacyFallbackController()
throws Exception { throws Exception {
Assume.assumeTrue(Util.SDK_INT >= 21); // TODO: b/199064299 - Lower minSdk to 19.
dispatchMediaKeyEvent(KeyEvent.KEYCODE_MEDIA_REWIND, false); dispatchMediaKeyEvent(KeyEvent.KEYCODE_MEDIA_REWIND, false);
player.awaitMethodCalled(MockPlayer.METHOD_SEEK_BACK, TIMEOUT_MS); player.awaitMethodCalled(MockPlayer.METHOD_SEEK_BACK, TIMEOUT_MS);
@ -261,7 +244,6 @@ public class MediaSessionKeyEventTest {
@Test @Test
public void stopKeyEvent() throws Exception { public void stopKeyEvent() throws Exception {
Assume.assumeTrue(Util.SDK_INT >= 21); // TODO: b/199064299 - Lower minSdk to 19.
dispatchMediaKeyEvent(KeyEvent.KEYCODE_MEDIA_STOP, false); dispatchMediaKeyEvent(KeyEvent.KEYCODE_MEDIA_STOP, false);
player.awaitMethodCalled(MockPlayer.METHOD_STOP, TIMEOUT_MS); player.awaitMethodCalled(MockPlayer.METHOD_STOP, TIMEOUT_MS);
@ -320,7 +302,6 @@ public class MediaSessionKeyEventTest {
@Test @Test
public void playPauseKeyEvent_playing_pause() throws Exception { public void playPauseKeyEvent_playing_pause() throws Exception {
Assume.assumeTrue(Util.SDK_INT >= 21); // TODO: b/199064299 - Lower minSdk to 19.
handler.postAndSync( handler.postAndSync(
() -> { () -> {
player.playWhenReady = true; player.playWhenReady = true;
@ -334,7 +315,6 @@ public class MediaSessionKeyEventTest {
@Test @Test
public void playPauseKeyEvent_doubleTapOnPlayPause_seekNext() throws Exception { public void playPauseKeyEvent_doubleTapOnPlayPause_seekNext() throws Exception {
Assume.assumeTrue(Util.SDK_INT >= 21); // TODO: b/199064299 - Lower minSdk to 19.
handler.postAndSync( handler.postAndSync(
() -> { () -> {
player.playWhenReady = true; player.playWhenReady = true;
@ -348,7 +328,6 @@ public class MediaSessionKeyEventTest {
@Test @Test
public void playPauseKeyEvent_doubleTapOnHeadsetHook_seekNext() throws Exception { public void playPauseKeyEvent_doubleTapOnHeadsetHook_seekNext() throws Exception {
Assume.assumeTrue(Util.SDK_INT >= 21); // TODO: b/199064299 - Lower minSdk to 19.
handler.postAndSync( handler.postAndSync(
() -> { () -> {
player.playWhenReady = true; player.playWhenReady = true;
@ -390,10 +369,8 @@ public class MediaSessionKeyEventTest {
return SUPPORT_APP_PACKAGE_NAME; return SUPPORT_APP_PACKAGE_NAME;
} }
// Legacy controllers // Legacy controllers
if (Util.SDK_INT < 21 || Util.SDK_INT >= 28) { if (Util.SDK_INT >= 28) {
// Above API 28: package of the app using AudioManager. // Above API 28: package of the app using AudioManager.
// Below 21: package of the owner of the session. Note: This is specific to this test setup
// where `ApplicationProvider.getContext().packageName == SUPPORT_APP_PACKAGE_NAME`.
return SUPPORT_APP_PACKAGE_NAME; return SUPPORT_APP_PACKAGE_NAME;
} else if (Util.SDK_INT >= 24) { } else if (Util.SDK_INT >= 24) {
// API 24 - 27: KeyEvent from system service has the package name "android". // API 24 - 27: KeyEvent from system service has the package name "android".

View File

@ -1014,8 +1014,7 @@ public class MediaSessionTest {
* <p>Calling this method should only be required to test legacy behaviour. * <p>Calling this method should only be required to test legacy behaviour.
*/ */
private static String getControllerCallerPackageName(ControllerInfo controllerInfo) { private static String getControllerCallerPackageName(ControllerInfo controllerInfo) {
return (Util.SDK_INT < 21 return (Util.SDK_INT > 23
|| Util.SDK_INT > 23
|| controllerInfo.getControllerVersion() != ControllerInfo.LEGACY_CONTROLLER_VERSION) || controllerInfo.getControllerVersion() != ControllerInfo.LEGACY_CONTROLLER_VERSION)
? ApplicationProvider.getApplicationContext().getPackageName() ? ApplicationProvider.getApplicationContext().getPackageName()
: MediaSessionManager.RemoteUserInfo.LEGACY_CONTROLLER; : MediaSessionManager.RemoteUserInfo.LEGACY_CONTROLLER;

View File

@ -20,7 +20,6 @@ import static androidx.media3.test.session.common.CommonConstants.MOCK_MEDIA3_SE
import static androidx.media3.test.session.common.CommonConstants.SUPPORT_APP_PACKAGE_NAME; import static androidx.media3.test.session.common.CommonConstants.SUPPORT_APP_PACKAGE_NAME;
import static androidx.media3.test.session.common.TestUtils.TIMEOUT_MS; import static androidx.media3.test.session.common.TestUtils.TIMEOUT_MS;
import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assume.assumeTrue;
import android.content.ComponentName; import android.content.ComponentName;
import android.content.Context; import android.content.Context;
@ -28,7 +27,6 @@ import android.os.Bundle;
import android.os.Process; import android.os.Process;
import android.support.v4.media.session.MediaSessionCompat; import android.support.v4.media.session.MediaSessionCompat;
import androidx.media3.common.MediaLibraryInfo; import androidx.media3.common.MediaLibraryInfo;
import androidx.media3.common.util.Util;
import androidx.media3.test.session.common.HandlerThreadTestRule; import androidx.media3.test.session.common.HandlerThreadTestRule;
import androidx.media3.test.session.common.MainLooperTestRule; import androidx.media3.test.session.common.MainLooperTestRule;
import androidx.media3.test.session.common.TestUtils; import androidx.media3.test.session.common.TestUtils;
@ -117,8 +115,6 @@ public class SessionTokenTest {
@Test @Test
public void createSessionToken_withPlatformTokenFromMedia1Session_returnsTokenForLegacySession() public void createSessionToken_withPlatformTokenFromMedia1Session_returnsTokenForLegacySession()
throws Exception { throws Exception {
assumeTrue(Util.SDK_INT >= 21);
MediaSessionCompat sessionCompat = MediaSessionCompat sessionCompat =
sessionTestRule.ensureReleaseAfterTest( sessionTestRule.ensureReleaseAfterTest(
new MediaSessionCompat(context, "createSessionToken_withLegacyToken")); new MediaSessionCompat(context, "createSessionToken_withLegacyToken"));

View File

@ -29,7 +29,6 @@ import android.system.ErrnoException;
import android.system.OsConstants; import android.system.OsConstants;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.media3.common.util.UnstableApi; import androidx.media3.common.util.UnstableApi;
import androidx.media3.common.util.Util;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
@ -148,8 +147,7 @@ public final class AssetContentProvider extends ContentProvider
} }
private static boolean isBrokenPipe(IOException e) { private static boolean isBrokenPipe(IOException e) {
return Util.SDK_INT >= 21 return e.getCause() instanceof ErrnoException
&& e.getCause() instanceof ErrnoException
&& ((ErrnoException) e.getCause()).errno == OsConstants.EPIPE; && ((ErrnoException) e.getCause()).errno == OsConstants.EPIPE;
} }
} }

View File

@ -331,7 +331,6 @@ public class CapturingRenderersFactory implements RenderersFactory, Dumper.Dumpa
dequeuedOutputBuffers.delete(index); dequeuedOutputBuffers.delete(index);
} }
@RequiresApi(21)
@Override @Override
public void releaseOutputBuffer(int index, long renderTimeStampNs) { public void releaseOutputBuffer(int index, long renderTimeStampNs) {
MediaCodec.BufferInfo bufferInfo = checkNotNull(dequeuedOutputBuffers.get(index)); MediaCodec.BufferInfo bufferInfo = checkNotNull(dequeuedOutputBuffers.get(index));

View File

@ -204,9 +204,6 @@ public final class DecodeOneFrameUtil {
*/ */
@Nullable @Nullable
private static String getSupportedDecoderName(MediaFormat format) { private static String getSupportedDecoderName(MediaFormat format) {
if (Util.SDK_INT < 21) {
throw new UnsupportedOperationException("Unable to detect decoder support under API 21.");
}
// TODO(b/266923205): De-duplicate logic from EncoderUtil.java#findCodecForFormat(). // TODO(b/266923205): De-duplicate logic from EncoderUtil.java#findCodecForFormat().
MediaCodecList mediaCodecList = new MediaCodecList(MediaCodecList.REGULAR_CODECS); MediaCodecList mediaCodecList = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
// Format must not include KEY_FRAME_RATE on API21. // Format must not include KEY_FRAME_RATE on API21.

View File

@ -24,7 +24,6 @@ import android.content.Context;
import android.media.Image; import android.media.Image;
import android.media.MediaCodec; import android.media.MediaCodec;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.media3.common.util.UnstableApi; import androidx.media3.common.util.UnstableApi;
import java.io.IOException; import java.io.IOException;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
@ -43,7 +42,6 @@ import java.nio.ByteBuffer;
* channel (Y') because the {@linkplain MediaCodec decoder} decodes to luma. * channel (Y') because the {@linkplain MediaCodec decoder} decodes to luma.
*/ */
@UnstableApi @UnstableApi
@RequiresApi(21)
public final class SsimHelper { public final class SsimHelper {
/** The default comparison interval. */ /** The default comparison interval. */

View File

@ -32,7 +32,6 @@ import android.media.MediaFormat;
import android.net.Uri; import android.net.Uri;
import android.os.Handler; import android.os.Handler;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.media3.common.Format; import androidx.media3.common.Format;
import androidx.media3.common.MimeTypes; import androidx.media3.common.MimeTypes;
import androidx.media3.common.util.ConditionVariable; import androidx.media3.common.util.ConditionVariable;
@ -55,7 +54,6 @@ import java.util.List;
* ImageReader} for use in CPU test utility functions. * ImageReader} for use in CPU test utility functions.
*/ */
@UnstableApi @UnstableApi
@RequiresApi(21)
public final class VideoDecodingWrapper implements AutoCloseable { public final class VideoDecodingWrapper implements AutoCloseable {
private static final String TAG = "VideoDecodingWrapper"; private static final String TAG = "VideoDecodingWrapper";

View File

@ -25,7 +25,6 @@ import android.media.MediaFormat;
import androidx.media3.common.C; import androidx.media3.common.C;
import androidx.media3.common.MimeTypes; import androidx.media3.common.MimeTypes;
import androidx.media3.common.util.UnstableApi; import androidx.media3.common.util.UnstableApi;
import androidx.media3.common.util.Util;
import androidx.media3.exoplayer.mediacodec.MediaCodecUtil; import androidx.media3.exoplayer.mediacodec.MediaCodecUtil;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap;
@ -77,10 +76,6 @@ public final class ShadowMediaCodecConfig extends ExternalResource {
@Override @Override
protected void before() throws Throwable { protected void before() throws Throwable {
if (Util.SDK_INT == 19) {
// Codec config not supported with Robolectric on API == 19. Skip rule set up step.
return;
}
configureCodecs(supportedMimeTypes); configureCodecs(supportedMimeTypes);
} }
@ -88,10 +83,6 @@ public final class ShadowMediaCodecConfig extends ExternalResource {
protected void after() { protected void after() {
supportedMimeTypes.clear(); supportedMimeTypes.clear();
MediaCodecUtil.clearDecoderInfoCache(); MediaCodecUtil.clearDecoderInfoCache();
if (Util.SDK_INT == 19) {
// Codec config not supported with Robolectric on API == 19. Skip rule tear down step.
return;
}
ShadowMediaCodecList.reset(); ShadowMediaCodecList.reset();
ShadowMediaCodec.clearCodecs(); ShadowMediaCodec.clearCodecs();
} }

View File

@ -23,9 +23,7 @@ import android.view.accessibility.CaptioningManager;
import android.view.accessibility.CaptioningManager.CaptionStyle; import android.view.accessibility.CaptioningManager.CaptionStyle;
import androidx.annotation.IntDef; import androidx.annotation.IntDef;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.media3.common.util.UnstableApi; import androidx.media3.common.util.UnstableApi;
import androidx.media3.common.util.Util;
import java.lang.annotation.Documented; import java.lang.annotation.Documented;
import java.lang.annotation.Retention; import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy;
@ -116,17 +114,13 @@ public final class CaptionStyleCompat {
*/ */
public static CaptionStyleCompat createFromCaptionStyle( public static CaptionStyleCompat createFromCaptionStyle(
CaptioningManager.CaptionStyle captionStyle) { CaptioningManager.CaptionStyle captionStyle) {
if (Util.SDK_INT >= 21) { return new CaptionStyleCompat(
return createFromCaptionStyleV21(captionStyle); captionStyle.hasForegroundColor() ? captionStyle.foregroundColor : DEFAULT.foregroundColor,
} else { captionStyle.hasBackgroundColor() ? captionStyle.backgroundColor : DEFAULT.backgroundColor,
return new CaptionStyleCompat( captionStyle.hasWindowColor() ? captionStyle.windowColor : DEFAULT.windowColor,
captionStyle.foregroundColor, captionStyle.hasEdgeType() ? captionStyle.edgeType : DEFAULT.edgeType,
captionStyle.backgroundColor, captionStyle.hasEdgeColor() ? captionStyle.edgeColor : DEFAULT.edgeColor,
Color.TRANSPARENT, captionStyle.getTypeface());
captionStyle.edgeType,
captionStyle.edgeColor,
captionStyle.getTypeface());
}
} }
/** /**
@ -151,17 +145,4 @@ public final class CaptionStyleCompat {
this.edgeColor = edgeColor; this.edgeColor = edgeColor;
this.typeface = typeface; this.typeface = typeface;
} }
@RequiresApi(21)
@SuppressWarnings("ResourceType")
private static CaptionStyleCompat createFromCaptionStyleV21(
CaptioningManager.CaptionStyle captionStyle) {
return new CaptionStyleCompat(
captionStyle.hasForegroundColor() ? captionStyle.foregroundColor : DEFAULT.foregroundColor,
captionStyle.hasBackgroundColor() ? captionStyle.backgroundColor : DEFAULT.backgroundColor,
captionStyle.hasWindowColor() ? captionStyle.windowColor : DEFAULT.windowColor,
captionStyle.hasEdgeType() ? captionStyle.edgeType : DEFAULT.edgeType,
captionStyle.hasEdgeColor() ? captionStyle.edgeColor : DEFAULT.edgeColor,
captionStyle.getTypeface());
}
} }

View File

@ -759,13 +759,8 @@ public class DefaultTimeBar extends View implements TimeBar {
if (duration <= 0) { if (duration <= 0) {
return; return;
} }
if (Util.SDK_INT >= 21) { info.addAction(AccessibilityAction.ACTION_SCROLL_FORWARD);
info.addAction(AccessibilityAction.ACTION_SCROLL_FORWARD); info.addAction(AccessibilityAction.ACTION_SCROLL_BACKWARD);
info.addAction(AccessibilityAction.ACTION_SCROLL_BACKWARD);
} else {
info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD);
info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD);
}
} }
@Override @Override

View File

@ -115,8 +115,7 @@ public class DefaultTrackNameProvider implements TrackNameProvider {
if (TextUtils.isEmpty(language) || C.LANGUAGE_UNDETERMINED.equals(language)) { if (TextUtils.isEmpty(language) || C.LANGUAGE_UNDETERMINED.equals(language)) {
return ""; return "";
} }
Locale languageLocale = Locale languageLocale = Locale.forLanguageTag(language);
Util.SDK_INT >= 21 ? Locale.forLanguageTag(language) : new Locale(language);
Locale displayLocale = Util.getDefaultDisplayLocale(); Locale displayLocale = Util.getDefaultDisplayLocale();
String languageName = languageLocale.getDisplayName(displayLocale); String languageName = languageLocale.getDisplayName(displayLocale);
if (TextUtils.isEmpty(languageName)) { if (TextUtils.isEmpty(languageName)) {

View File

@ -47,9 +47,7 @@ import android.view.accessibility.AccessibilityEvent;
import android.widget.FrameLayout; import android.widget.FrameLayout;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.TextView; import android.widget.TextView;
import androidx.annotation.DoNotInline;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.media3.common.C; import androidx.media3.common.C;
import androidx.media3.common.MediaLibraryInfo; import androidx.media3.common.MediaLibraryInfo;
import androidx.media3.common.Player; import androidx.media3.common.Player;
@ -857,19 +855,15 @@ public class LegacyPlayerControlView extends FrameLayout {
boolean requestPlayPauseAccessibilityFocus = false; boolean requestPlayPauseAccessibilityFocus = false;
boolean shouldShowPlayButton = Util.shouldShowPlayButton(player, showPlayButtonIfSuppressed); boolean shouldShowPlayButton = Util.shouldShowPlayButton(player, showPlayButtonIfSuppressed);
if (playButton != null) { if (playButton != null) {
requestPlayPauseFocus |= !shouldShowPlayButton && playButton.isFocused(); requestPlayPauseFocus = !shouldShowPlayButton && playButton.isFocused();
requestPlayPauseAccessibilityFocus |= requestPlayPauseAccessibilityFocus =
Util.SDK_INT < 21 (!shouldShowPlayButton && playButton.isAccessibilityFocused());
? requestPlayPauseFocus
: (!shouldShowPlayButton && Api21.isAccessibilityFocused(playButton));
playButton.setVisibility(shouldShowPlayButton ? VISIBLE : GONE); playButton.setVisibility(shouldShowPlayButton ? VISIBLE : GONE);
} }
if (pauseButton != null) { if (pauseButton != null) {
requestPlayPauseFocus |= shouldShowPlayButton && pauseButton.isFocused(); requestPlayPauseFocus |= shouldShowPlayButton && pauseButton.isFocused();
requestPlayPauseAccessibilityFocus |= requestPlayPauseAccessibilityFocus |=
Util.SDK_INT < 21 shouldShowPlayButton && pauseButton.isAccessibilityFocused();
? requestPlayPauseFocus
: (shouldShowPlayButton && Api21.isAccessibilityFocused(pauseButton));
pauseButton.setVisibility(shouldShowPlayButton ? GONE : VISIBLE); pauseButton.setVisibility(shouldShowPlayButton ? GONE : VISIBLE);
} }
if (requestPlayPauseFocus) { if (requestPlayPauseFocus) {
@ -1357,12 +1351,4 @@ public class LegacyPlayerControlView extends FrameLayout {
} }
} }
} }
@RequiresApi(21)
private static final class Api21 {
@DoNotInline
public static boolean isAccessibilityFocused(View view) {
return view.isAccessibilityFocused();
}
}
} }

View File

@ -55,7 +55,6 @@ import androidx.annotation.DrawableRes;
import androidx.annotation.IntDef; import androidx.annotation.IntDef;
import androidx.annotation.IntRange; import androidx.annotation.IntRange;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.core.app.NotificationBuilderWithBuilderAccessor; import androidx.core.app.NotificationBuilderWithBuilderAccessor;
import androidx.core.app.NotificationCompat; import androidx.core.app.NotificationCompat;
import androidx.core.app.NotificationManagerCompat; import androidx.core.app.NotificationManagerCompat;
@ -1013,11 +1012,10 @@ public class PlayerNotificationManager {
* @deprecated Use {@link #setMediaSessionToken(MediaSession.Token)} and pass in {@code * @deprecated Use {@link #setMediaSessionToken(MediaSession.Token)} and pass in {@code
* (MediaSession.Token) compatToken.getToken()}. * (MediaSession.Token) compatToken.getToken()}.
*/ */
// TODO: b/333355694 - Remove the dependency on androidx.media when this method is removed.
@Deprecated @Deprecated
public final void setMediaSessionToken(MediaSessionCompat.Token compatToken) { public final void setMediaSessionToken(MediaSessionCompat.Token compatToken) {
if (Util.SDK_INT >= 21) { setMediaSessionToken((MediaSession.Token) compatToken.getToken());
setMediaSessionToken((MediaSession.Token) compatToken.getToken());
}
} }
/** /**
@ -1028,7 +1026,6 @@ public class PlayerNotificationManager {
* *
* @param token The {@link MediaSession.Token}. * @param token The {@link MediaSession.Token}.
*/ */
@RequiresApi(21)
public final void setMediaSessionToken(MediaSession.Token token) { public final void setMediaSessionToken(MediaSession.Token token) {
if (!Util.areEqual(this.mediaSessionToken, token)) { if (!Util.areEqual(this.mediaSessionToken, token)) {
mediaSessionToken = token; mediaSessionToken = token;
@ -1247,8 +1244,7 @@ public class PlayerNotificationManager {
* Creates the notification given the current player state. * Creates the notification given the current player state.
* *
* @param player The player for which state to build a notification. * @param player The player for which state to build a notification.
* @param builder The builder used to build the last notification, or {@code null}. Re-using the * @param builder The builder used to build the last notification, or {@code null}.
* builder when possible can prevent notification flicker when {@code Util#SDK_INT} &lt; 21.
* @param ongoing Whether the notification should be ongoing. * @param ongoing Whether the notification should be ongoing.
* @param largeIcon The large icon to be used. * @param largeIcon The large icon to be used.
* @return The {@link NotificationCompat.Builder} on which to call {@link * @return The {@link NotificationCompat.Builder} on which to call {@link
@ -1291,17 +1287,7 @@ public class PlayerNotificationManager {
} }
int[] actionIndicesForCompactView = getActionIndicesForCompactView(actionNames, player); int[] actionIndicesForCompactView = getActionIndicesForCompactView(actionNames, player);
if (Util.SDK_INT >= 21) { builder.setStyle(new MediaStyle(mediaSessionToken, actionIndicesForCompactView));
builder.setStyle(new MediaStyle(mediaSessionToken, actionIndicesForCompactView));
} else {
// TODO: b/333355694 - Remove dependency on androidx.media once this logic is gone.
androidx.media.app.NotificationCompat.MediaStyle mediaStyle =
new androidx.media.app.NotificationCompat.MediaStyle();
mediaStyle.setShowActionsInCompactView(actionIndicesForCompactView);
mediaStyle.setShowCancelButton(!ongoing);
mediaStyle.setCancelButtonIntent(dismissPendingIntent);
builder.setStyle(mediaStyle);
}
// Set intent which is sent if the user selects 'clear all' // Set intent which is sent if the user selects 'clear all'
builder.setDeleteIntent(dismissPendingIntent); builder.setDeleteIntent(dismissPendingIntent);
@ -1317,9 +1303,7 @@ public class PlayerNotificationManager {
.setPriority(priority) .setPriority(priority)
.setDefaults(defaults); .setDefaults(defaults);
// Changing "showWhen" causes notification flicker if SDK_INT < 21. if (useChronometer
if (Util.SDK_INT >= 21
&& useChronometer
&& player.isCommandAvailable(COMMAND_GET_CURRENT_MEDIA_ITEM) && player.isCommandAvailable(COMMAND_GET_CURRENT_MEDIA_ITEM)
&& player.isPlaying() && player.isPlaying()
&& !player.isPlayingAd() && !player.isPlayingAd()
@ -1628,7 +1612,6 @@ public class PlayerNotificationManager {
} }
} }
@RequiresApi(21)
private static final class MediaStyle extends androidx.core.app.NotificationCompat.Style { private static final class MediaStyle extends androidx.core.app.NotificationCompat.Style {
private final int[] actionsToShowInCompact; private final int[] actionsToShowInCompact;