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:
parent
5fa9985ce6
commit
6a9ff95bf0
@ -11,6 +11,8 @@
|
||||
* Add override for `SimpleBasePlayer.State.Builder.setPlaylist()` to
|
||||
directly specify a `Timeline` and current `Tracks` and `Metadata`
|
||||
instead of building a playlist structure.
|
||||
* Increase `minSdk` to 21 (Android Lollipop). This is aligned with all
|
||||
other AndroidX libraries.
|
||||
* ExoPlayer:
|
||||
* `MediaCodecRenderer.onProcessedStreamChange()` can now be called for
|
||||
every media item. Previously it was not called for the first one. Use
|
||||
|
4
api.txt
4
api.txt
@ -26,7 +26,7 @@ package androidx.media3.common {
|
||||
}
|
||||
|
||||
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 @androidx.media3.common.C.AudioAllowedCapturePolicy public final int allowedCapturePolicy;
|
||||
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;
|
||||
}
|
||||
|
||||
@RequiresApi(21) public static final class AudioAttributes.AudioAttributesV21 {
|
||||
public static final class AudioAttributes.AudioAttributesV21 {
|
||||
field public final android.media.AudioAttributes audioAttributes;
|
||||
}
|
||||
|
||||
|
@ -14,7 +14,7 @@
|
||||
project.ext {
|
||||
releaseVersion = '1.4.0-rc01'
|
||||
releaseVersionCode = 1_004_000_2_01
|
||||
minSdkVersion = 19
|
||||
minSdkVersion = 21
|
||||
// See https://developer.android.com/training/cars/media/automotive-os#automotive-module
|
||||
automotiveMinSdkVersion = 28
|
||||
appTargetSdkVersion = 34
|
||||
|
@ -63,7 +63,7 @@ public class DemoDownloadService extends DownloadService {
|
||||
|
||||
@Override
|
||||
protected Scheduler getScheduler() {
|
||||
return Util.SDK_INT >= 21 ? new PlatformScheduler(this, JOB_ID) : null;
|
||||
return new PlatformScheduler(this, JOB_ID);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -37,7 +37,6 @@ import com.google.errorprone.annotations.CanIgnoreReturnValue;
|
||||
public final class AudioAttributes {
|
||||
|
||||
/** A direct wrapper around {@link android.media.AudioAttributes}. */
|
||||
@RequiresApi(21)
|
||||
public static final class AudioAttributesV21 {
|
||||
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}
|
||||
* setter is not available on the current API level.
|
||||
*/
|
||||
@RequiresApi(21)
|
||||
public AudioAttributesV21 getAudioAttributesV21() {
|
||||
if (audioAttributesV21 == null) {
|
||||
audioAttributesV21 = new AudioAttributesV21(this);
|
||||
|
@ -32,7 +32,6 @@ import android.media.MediaFormat;
|
||||
import android.net.Uri;
|
||||
import android.view.Surface;
|
||||
import androidx.annotation.IntDef;
|
||||
import androidx.annotation.RequiresApi;
|
||||
import androidx.media3.common.util.UnstableApi;
|
||||
import androidx.media3.common.util.Util;
|
||||
import com.google.errorprone.annotations.InlineMe;
|
||||
@ -1698,7 +1697,6 @@ public final class C {
|
||||
replacement = "Util.generateAudioSessionIdV21(context)",
|
||||
imports = {"androidx.media3.common.util.Util"})
|
||||
@Deprecated
|
||||
@RequiresApi(21)
|
||||
public static int generateAudioSessionIdV21(Context context) {
|
||||
return Util.generateAudioSessionIdV21(context);
|
||||
}
|
||||
|
@ -962,16 +962,14 @@ public final class Util {
|
||||
/**
|
||||
* Returns the language tag for a {@link Locale}.
|
||||
*
|
||||
* <p>For API levels ≥ 21, this tag is IETF BCP 47 compliant. Use {@link
|
||||
* #normalizeLanguageCode(String)} to retrieve a normalized IETF BCP 47 language tag for all API
|
||||
* levels if needed.
|
||||
* <p>This tag is IETF BCP 47 compliant.
|
||||
*
|
||||
* @param locale A {@link Locale}.
|
||||
* @return The language tag.
|
||||
*/
|
||||
@UnstableApi
|
||||
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. */
|
||||
@UnstableApi
|
||||
@RequiresApi(21)
|
||||
public static AudioFormat getAudioFormat(int sampleRate, int channelConfig, int encoding) {
|
||||
return new AudioFormat.Builder()
|
||||
.setSampleRate(sampleRate)
|
||||
@ -2417,7 +2414,6 @@ public final class Util {
|
||||
* @see AudioManager#generateAudioSessionId()
|
||||
*/
|
||||
@UnstableApi
|
||||
@RequiresApi(21)
|
||||
public static int generateAudioSessionIdV21(Context context) {
|
||||
@Nullable
|
||||
AudioManager audioManager = ((AudioManager) context.getSystemService(Context.AUDIO_SERVICE));
|
||||
@ -3065,8 +3061,7 @@ public final class Util {
|
||||
*/
|
||||
@UnstableApi
|
||||
public static boolean isWear(Context context) {
|
||||
return SDK_INT >= 20
|
||||
&& context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH);
|
||||
return context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -3502,9 +3497,7 @@ public final class Util {
|
||||
@UnstableApi
|
||||
public static Drawable getDrawable(
|
||||
Context context, Resources resources, @DrawableRes int drawableRes) {
|
||||
return SDK_INT >= 21
|
||||
? Api21.getDrawable(context, resources, drawableRes)
|
||||
: resources.getDrawable(drawableRes);
|
||||
return resources.getDrawable(drawableRes, context.getTheme());
|
||||
}
|
||||
|
||||
/**
|
||||
@ -3666,11 +3659,6 @@ public final class Util {
|
||||
return split(config.getLocales().toLanguageTags(), ",");
|
||||
}
|
||||
|
||||
@RequiresApi(21)
|
||||
private static String getLocaleLanguageTagV21(Locale locale) {
|
||||
return locale.toLanguageTag();
|
||||
}
|
||||
|
||||
private static HashMap<String, String> createIsoLanguageReplacementMap() {
|
||||
String[] iso2Languages = Locale.getISOLanguages();
|
||||
HashMap<String, String> replacedLanguages =
|
||||
@ -3890,14 +3878,6 @@ public final class Util {
|
||||
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)
|
||||
private static class Api29 {
|
||||
|
||||
|
@ -40,7 +40,6 @@ import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InterruptedIOException;
|
||||
import java.io.OutputStream;
|
||||
import java.lang.reflect.Method;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.MalformedURLException;
|
||||
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 HTTP_STATUS_TEMPORARY_REDIRECT = 307;
|
||||
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 crossProtocolRedirectsForceOriginal;
|
||||
@ -482,9 +480,6 @@ public class DefaultHttpDataSource extends BaseDataSource implements HttpDataSou
|
||||
try {
|
||||
@Nullable InputStream inputStream = this.inputStream;
|
||||
if (inputStream != null) {
|
||||
long bytesRemaining =
|
||||
bytesToRead == C.LENGTH_UNSET ? C.LENGTH_UNSET : bytesToRead - bytesRead;
|
||||
maybeTerminateInputStream(connection, bytesRemaining);
|
||||
try {
|
||||
inputStream.close();
|
||||
} catch (IOException e) {
|
||||
@ -784,52 +779,6 @@ public class DefaultHttpDataSource extends BaseDataSource implements HttpDataSou
|
||||
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. */
|
||||
private void closeConnectionQuietly() {
|
||||
if (connection != null) {
|
||||
|
@ -22,14 +22,11 @@ import android.net.Uri;
|
||||
import android.system.ErrnoException;
|
||||
import android.system.OsConstants;
|
||||
import android.text.TextUtils;
|
||||
import androidx.annotation.DoNotInline;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.RequiresApi;
|
||||
import androidx.media3.common.C;
|
||||
import androidx.media3.common.PlaybackException;
|
||||
import androidx.media3.common.util.Assertions;
|
||||
import androidx.media3.common.util.UnstableApi;
|
||||
import androidx.media3.common.util.Util;
|
||||
import com.google.errorprone.annotations.CanIgnoreReturnValue;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
@ -200,7 +197,8 @@ public final class FileDataSource extends BaseDataSource {
|
||||
// different SDK versions.
|
||||
throw new FileDataSourceException(
|
||||
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_FILE_NOT_FOUND);
|
||||
} catch (SecurityException e) {
|
||||
@ -209,12 +207,4 @@ public final class FileDataSource extends BaseDataSource {
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -24,7 +24,6 @@ import android.system.ErrnoException;
|
||||
import android.system.Os;
|
||||
import android.system.OsConstants;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.RequiresApi;
|
||||
import androidx.media3.common.C;
|
||||
import androidx.media3.common.PlaybackException;
|
||||
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
|
||||
* FileDescriptor} is used for all read operations.
|
||||
*/
|
||||
@RequiresApi(21)
|
||||
@UnstableApi
|
||||
public class FileDescriptorDataSource extends BaseDataSource {
|
||||
|
||||
|
@ -748,7 +748,7 @@ public final class MediaMetricsListener
|
||||
} else if (cause instanceof DrmSession.DrmSessionException) {
|
||||
// Unpack DrmSessionException.
|
||||
cause = checkNotNull(cause.getCause());
|
||||
if (Util.SDK_INT >= 21 && cause instanceof MediaDrm.MediaDrmStateException) {
|
||||
if (cause instanceof MediaDrm.MediaDrmStateException) {
|
||||
String diagnosticsInfo = ((MediaDrm.MediaDrmStateException) cause).getDiagnosticInfo();
|
||||
int subErrorCode = Util.getErrorCodeFromPlatformDiagnosticsInfo(diagnosticsInfo);
|
||||
int errorCode = getDrmErrorCode(subErrorCode);
|
||||
@ -771,8 +771,7 @@ public final class MediaMetricsListener
|
||||
} else if (cause instanceof FileDataSource.FileDataSourceException
|
||||
&& cause.getCause() instanceof FileNotFoundException) {
|
||||
@Nullable Throwable notFoundCause = checkNotNull(cause.getCause()).getCause();
|
||||
if (Util.SDK_INT >= 21
|
||||
&& notFoundCause instanceof ErrnoException
|
||||
if (notFoundCause instanceof ErrnoException
|
||||
&& ((ErrnoException) notFoundCause).errno == OsConstants.EACCES) {
|
||||
return new ErrorInfo(PlaybackErrorEvent.ERROR_IO_NO_PERMISSION, /* subErrorCode= */ 0);
|
||||
} else {
|
||||
|
@ -57,7 +57,7 @@ public final class AudioCapabilitiesReceiver {
|
||||
private final Listener listener;
|
||||
private final Handler handler;
|
||||
@Nullable private final AudioDeviceCallbackV23 audioDeviceCallback;
|
||||
@Nullable private final BroadcastReceiver hdmiAudioPlugBroadcastReceiver;
|
||||
private final BroadcastReceiver hdmiAudioPlugBroadcastReceiver;
|
||||
@Nullable private final ExternalSurroundSoundSettingObserver externalSurroundSoundSettingObserver;
|
||||
|
||||
@Nullable private AudioCapabilities audioCapabilities;
|
||||
@ -105,8 +105,7 @@ public final class AudioCapabilitiesReceiver {
|
||||
this.routedDevice = routedDevice;
|
||||
handler = Util.createHandlerForCurrentOrMainLooper();
|
||||
audioDeviceCallback = Util.SDK_INT >= 23 ? new AudioDeviceCallbackV23() : null;
|
||||
hdmiAudioPlugBroadcastReceiver =
|
||||
Util.SDK_INT >= 21 ? new HdmiAudioPlugBroadcastReceiver() : null;
|
||||
hdmiAudioPlugBroadcastReceiver = new HdmiAudioPlugBroadcastReceiver();
|
||||
Uri externalSurroundSoundUri = AudioCapabilities.getExternalSurroundSoundGlobalSettingUri();
|
||||
externalSurroundSoundSettingObserver =
|
||||
externalSurroundSoundUri != null
|
||||
@ -162,16 +161,12 @@ public final class AudioCapabilitiesReceiver {
|
||||
if (Util.SDK_INT >= 23 && audioDeviceCallback != null) {
|
||||
Api23.registerAudioDeviceCallback(context, audioDeviceCallback, handler);
|
||||
}
|
||||
@Nullable Intent stickyIntent = null;
|
||||
if (hdmiAudioPlugBroadcastReceiver != null) {
|
||||
IntentFilter intentFilter = new IntentFilter(AudioManager.ACTION_HDMI_AUDIO_PLUG);
|
||||
stickyIntent =
|
||||
context.registerReceiver(
|
||||
hdmiAudioPlugBroadcastReceiver,
|
||||
intentFilter,
|
||||
/* broadcastPermission= */ null,
|
||||
handler);
|
||||
}
|
||||
Intent stickyIntent =
|
||||
context.registerReceiver(
|
||||
hdmiAudioPlugBroadcastReceiver,
|
||||
new IntentFilter(AudioManager.ACTION_HDMI_AUDIO_PLUG),
|
||||
/* broadcastPermission= */ null,
|
||||
handler);
|
||||
audioCapabilities =
|
||||
AudioCapabilities.getCapabilitiesInternal(
|
||||
context, stickyIntent, audioAttributes, routedDevice);
|
||||
@ -190,9 +185,7 @@ public final class AudioCapabilitiesReceiver {
|
||||
if (Util.SDK_INT >= 23 && audioDeviceCallback != null) {
|
||||
Api23.unregisterAudioDeviceCallback(context, audioDeviceCallback);
|
||||
}
|
||||
if (hdmiAudioPlugBroadcastReceiver != null) {
|
||||
context.unregisterReceiver(hdmiAudioPlugBroadcastReceiver);
|
||||
}
|
||||
context.unregisterReceiver(hdmiAudioPlugBroadcastReceiver);
|
||||
if (externalSurroundSoundSettingObserver != null) {
|
||||
externalSurroundSoundSettingObserver.unregister();
|
||||
}
|
||||
|
@ -244,9 +244,7 @@ public abstract class DecoderAudioRenderer<
|
||||
if (formatSupport <= C.FORMAT_UNSUPPORTED_DRM) {
|
||||
return RendererCapabilities.create(formatSupport);
|
||||
}
|
||||
@TunnelingSupport
|
||||
int tunnelingSupport = Util.SDK_INT >= 21 ? TUNNELING_SUPPORTED : TUNNELING_NOT_SUPPORTED;
|
||||
return RendererCapabilities.create(formatSupport, ADAPTIVE_NOT_SEAMLESS, tunnelingSupport);
|
||||
return RendererCapabilities.create(formatSupport, ADAPTIVE_NOT_SEAMLESS, TUNNELING_SUPPORTED);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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
|
||||
* 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
|
||||
* (for example, speed adjustment) will not be available when float output is in use.
|
||||
* (24-bit or 32-bit) integer PCM. Audio processing (for example, speed adjustment) will not be
|
||||
* available when float output is in use.
|
||||
*
|
||||
* <p>The default value is {@code false}.
|
||||
*/
|
||||
@ -541,8 +541,6 @@ public final class DefaultAudioSink implements AudioSink {
|
||||
@Nullable private ByteBuffer inputBuffer;
|
||||
private int inputBufferAccessUnitCount;
|
||||
@Nullable private ByteBuffer outputBuffer;
|
||||
private byte @MonotonicNonNull [] preV21OutputBuffer;
|
||||
private int preV21OutputBufferOffset;
|
||||
private boolean handledEndOfStream;
|
||||
private boolean stoppedAudioTrack;
|
||||
private boolean handledOffloadOnPresentationEnded;
|
||||
@ -571,7 +569,7 @@ public final class DefaultAudioSink implements AudioSink {
|
||||
? getCapabilities(context, audioAttributes, /* routedDevice= */ null)
|
||||
: builder.audioCapabilities;
|
||||
audioProcessorChain = builder.audioProcessorChain;
|
||||
enableFloatOutput = Util.SDK_INT >= 21 && builder.enableFloatOutput;
|
||||
enableFloatOutput = builder.enableFloatOutput;
|
||||
preferAudioTrackPlaybackParams = Util.SDK_INT >= 23 && builder.enableAudioTrackPlaybackParams;
|
||||
offloadMode = OFFLOAD_MODE_DISABLED;
|
||||
audioTrackBufferSizeProvider = builder.audioTrackBufferSizeProvider;
|
||||
@ -700,14 +698,6 @@ public final class DefaultAudioSink implements AudioSink {
|
||||
trimmingAudioProcessor.setTrimFrameCount(
|
||||
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);
|
||||
|
||||
AudioProcessor.AudioFormat outputFormat = new AudioProcessor.AudioFormat(inputFormat);
|
||||
@ -1160,32 +1150,10 @@ public final class DefaultAudioSink implements AudioSink {
|
||||
Assertions.checkArgument(outputBuffer == buffer);
|
||||
} else {
|
||||
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 bytesWrittenOrError = 0; // Error if negative
|
||||
if (Util.SDK_INT < 21) { // outputMode == OUTPUT_MODE_PCM.
|
||||
// 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) {
|
||||
if (tunneling) {
|
||||
Assertions.checkState(avSyncPresentationTimeUs != C.TIME_UNSET);
|
||||
if (avSyncPresentationTimeUs == C.TIME_END_OF_SOURCE) {
|
||||
// Audio processors during tunneling are required to produce buffers immediately when
|
||||
@ -1196,10 +1164,9 @@ public final class DefaultAudioSink implements AudioSink {
|
||||
lastTunnelingAvSyncPresentationTimeUs = avSyncPresentationTimeUs;
|
||||
}
|
||||
bytesWrittenOrError =
|
||||
writeNonBlockingWithAvSyncV21(
|
||||
audioTrack, buffer, bytesRemaining, avSyncPresentationTimeUs);
|
||||
writeNonBlockingWithAvSync(audioTrack, buffer, bytesRemaining, avSyncPresentationTimeUs);
|
||||
} else {
|
||||
bytesWrittenOrError = writeNonBlockingV21(audioTrack, buffer, bytesRemaining);
|
||||
bytesWrittenOrError = writeNonBlocking(audioTrack, buffer, bytesRemaining);
|
||||
}
|
||||
|
||||
lastFeedElapsedRealtimeMs = SystemClock.elapsedRealtime();
|
||||
@ -1402,7 +1369,6 @@ public final class DefaultAudioSink implements AudioSink {
|
||||
|
||||
@Override
|
||||
public void enableTunnelingV21() {
|
||||
Assertions.checkState(Util.SDK_INT >= 21);
|
||||
Assertions.checkState(externalAudioSessionIdProvided);
|
||||
if (!tunneling) {
|
||||
tunneling = true;
|
||||
@ -1445,12 +1411,8 @@ public final class DefaultAudioSink implements AudioSink {
|
||||
}
|
||||
|
||||
private void setVolumeInternal() {
|
||||
if (!isAudioTrackInitialized()) {
|
||||
// Do nothing.
|
||||
} else if (Util.SDK_INT >= 21) {
|
||||
setVolumeInternalV21(audioTrack, volume);
|
||||
} else {
|
||||
setVolumeInternalV3(audioTrack, volume);
|
||||
if (isAudioTrackInitialized()) {
|
||||
audioTrack.setVolume(volume);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1474,14 +1436,6 @@ public final class DefaultAudioSink implements AudioSink {
|
||||
if (isOffloadedPlayback(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();
|
||||
if (pendingConfiguration != null) {
|
||||
configuration = pendingConfiguration;
|
||||
@ -1822,13 +1776,11 @@ public final class DefaultAudioSink implements AudioSink {
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresApi(21)
|
||||
private static int writeNonBlockingV21(AudioTrack audioTrack, ByteBuffer buffer, int size) {
|
||||
private static int writeNonBlocking(AudioTrack audioTrack, ByteBuffer buffer, int size) {
|
||||
return audioTrack.write(buffer, size, AudioTrack.WRITE_NON_BLOCKING);
|
||||
}
|
||||
|
||||
@RequiresApi(21)
|
||||
private int writeNonBlockingWithAvSyncV21(
|
||||
private int writeNonBlockingWithAvSync(
|
||||
AudioTrack audioTrack, ByteBuffer buffer, int size, long presentationTimeUs) {
|
||||
if (Util.SDK_INT >= 26) {
|
||||
// The underlying platform AudioTrack writes AV sync headers directly.
|
||||
@ -1858,7 +1810,7 @@ public final class DefaultAudioSink implements AudioSink {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
int result = writeNonBlockingV21(audioTrack, buffer, size);
|
||||
int result = writeNonBlocking(audioTrack, buffer, size);
|
||||
if (result < 0) {
|
||||
bytesUntilNextAvSync = 0;
|
||||
return result;
|
||||
@ -1867,15 +1819,6 @@ public final class DefaultAudioSink implements AudioSink {
|
||||
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() {
|
||||
if (!stoppedAudioTrack) {
|
||||
stoppedAudioTrack = true;
|
||||
@ -2253,10 +2196,8 @@ public final class DefaultAudioSink implements AudioSink {
|
||||
private AudioTrack createAudioTrack(AudioAttributes audioAttributes, int audioSessionId) {
|
||||
if (Util.SDK_INT >= 29) {
|
||||
return createAudioTrackV29(audioAttributes, audioSessionId);
|
||||
} else if (Util.SDK_INT >= 21) {
|
||||
return createAudioTrackV21(audioAttributes, audioSessionId);
|
||||
} else {
|
||||
return createAudioTrackV9(audioAttributes, audioSessionId);
|
||||
return createAudioTrackV21(audioAttributes, audioSessionId);
|
||||
}
|
||||
}
|
||||
|
||||
@ -2265,7 +2206,7 @@ public final class DefaultAudioSink implements AudioSink {
|
||||
AudioFormat audioFormat =
|
||||
Util.getAudioFormat(outputSampleRate, outputChannelConfig, outputEncoding);
|
||||
android.media.AudioAttributes audioTrackAttributes =
|
||||
getAudioTrackAttributesV21(audioAttributes, tunneling);
|
||||
getAudioTrackAttributes(audioAttributes, tunneling);
|
||||
return new AudioTrack.Builder()
|
||||
.setAudioAttributes(audioTrackAttributes)
|
||||
.setAudioFormat(audioFormat)
|
||||
@ -2276,59 +2217,28 @@ public final class DefaultAudioSink implements AudioSink {
|
||||
.build();
|
||||
}
|
||||
|
||||
@RequiresApi(21)
|
||||
private AudioTrack createAudioTrackV21(AudioAttributes audioAttributes, int audioSessionId) {
|
||||
return new AudioTrack(
|
||||
getAudioTrackAttributesV21(audioAttributes, tunneling),
|
||||
getAudioTrackAttributes(audioAttributes, tunneling),
|
||||
Util.getAudioFormat(outputSampleRate, outputChannelConfig, outputEncoding),
|
||||
bufferSize,
|
||||
AudioTrack.MODE_STREAM,
|
||||
audioSessionId);
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation") // Using deprecated AudioTrack constructor.
|
||||
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(
|
||||
private static android.media.AudioAttributes getAudioTrackAttributes(
|
||||
AudioAttributes audioAttributes, boolean 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 {
|
||||
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() {
|
||||
return outputMode == OUTPUT_MODE_OFFLOAD;
|
||||
}
|
||||
|
@ -278,8 +278,6 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
|
||||
if (!MimeTypes.isAudio(format.sampleMimeType)) {
|
||||
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 supportsFormatDrm = supportsFormatDrm(format);
|
||||
|
||||
@ -291,7 +289,7 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
|
||||
audioOffloadSupport = getAudioOffloadSupport(format);
|
||||
if (audioSink.supportsFormat(format)) {
|
||||
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
|
||||
@ -346,7 +344,7 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
|
||||
return RendererCapabilities.create(
|
||||
formatSupport,
|
||||
adaptiveSupport,
|
||||
tunnelingSupport,
|
||||
TUNNELING_SUPPORTED,
|
||||
hardwareAccelerationSupport,
|
||||
decoderSupport,
|
||||
audioOffloadSupport);
|
||||
|
@ -77,8 +77,11 @@ public final class DrmUtil {
|
||||
*/
|
||||
public static @PlaybackException.ErrorCode int getErrorCodeForMediaDrmException(
|
||||
Throwable exception, @ErrorSource int errorSource) {
|
||||
if (Util.SDK_INT >= 21 && Api21.isMediaDrmStateException(exception)) {
|
||||
return Api21.mediaDrmStateExceptionToErrorCode(exception);
|
||||
if (exception instanceof MediaDrm.MediaDrmStateException) {
|
||||
@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)) {
|
||||
return PlaybackException.ERROR_CODE_DRM_SYSTEM_ERROR;
|
||||
} else if (exception instanceof NotProvisionedException
|
||||
@ -128,26 +131,6 @@ public final class DrmUtil {
|
||||
&& 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)
|
||||
private static final class Api23 {
|
||||
|
||||
|
@ -53,12 +53,22 @@ public final class FrameworkCryptoConfig implements CryptoConfig {
|
||||
@Deprecated public final boolean forceAllowInsecureDecoderComponents;
|
||||
|
||||
/**
|
||||
* Constructs an instance.
|
||||
*
|
||||
* @param uuid The DRM scheme UUID.
|
||||
* @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
|
||||
@Deprecated
|
||||
public FrameworkCryptoConfig(
|
||||
UUID uuid, byte[] sessionId, boolean forceAllowInsecureDecoderComponents) {
|
||||
this.uuid = uuid;
|
||||
|
@ -308,7 +308,7 @@ public final class FrameworkMediaDrm implements ExoMediaDrm {
|
||||
}
|
||||
}
|
||||
}
|
||||
return result && !shouldForceAllowInsecureDecoderComponents();
|
||||
return result;
|
||||
}
|
||||
|
||||
@UnstableApi
|
||||
@ -389,17 +389,7 @@ public final class FrameworkMediaDrm implements ExoMediaDrm {
|
||||
@UnstableApi
|
||||
@Override
|
||||
public FrameworkCryptoConfig createCryptoConfig(byte[] sessionId) throws MediaCryptoException {
|
||||
boolean forceAllowInsecureDecoderComponents = shouldForceAllowInsecureDecoderComponents();
|
||||
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"));
|
||||
return new FrameworkCryptoConfig(adjustUuid(uuid), sessionId);
|
||||
}
|
||||
|
||||
@UnstableApi
|
||||
|
@ -252,7 +252,6 @@ public interface MediaCodecAdapter {
|
||||
*
|
||||
* @see MediaCodec#releaseOutputBuffer(int, long)
|
||||
*/
|
||||
@RequiresApi(21)
|
||||
void releaseOutputBuffer(int index, long renderTimeStampNs);
|
||||
|
||||
/** Flushes the adapter and the underlying {@link MediaCodec}. */
|
||||
@ -278,7 +277,6 @@ public interface MediaCodecAdapter {
|
||||
* @see MediaCodec.Callback#onOutputBufferAvailable
|
||||
* @return Whether listener was successfully registered.
|
||||
*/
|
||||
@RequiresApi(21)
|
||||
default boolean registerOnBufferAvailableListener(
|
||||
MediaCodecAdapter.OnBufferAvailableListener listener) {
|
||||
return false;
|
||||
|
@ -38,22 +38,16 @@ public class MediaCodecDecoderException extends DecoderException {
|
||||
public MediaCodecDecoderException(Throwable cause, @Nullable MediaCodecInfo codecInfo) {
|
||||
super("Decoder failed: " + (codecInfo == null ? null : codecInfo.name), cause);
|
||||
this.codecInfo = codecInfo;
|
||||
diagnosticInfo = Util.SDK_INT >= 21 ? getDiagnosticInfoV21(cause) : null;
|
||||
diagnosticInfo =
|
||||
cause instanceof MediaCodec.CodecException
|
||||
? ((MediaCodec.CodecException) cause).getDiagnosticInfo()
|
||||
: null;
|
||||
errorCode =
|
||||
Util.SDK_INT >= 23
|
||||
? getErrorCodeV23(cause)
|
||||
: 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)
|
||||
private static int getErrorCodeV23(Throwable cause) {
|
||||
if (cause instanceof MediaCodec.CodecException) {
|
||||
|
@ -258,22 +258,12 @@ public final class MediaCodecInfo {
|
||||
if (format.width <= 0 || format.height <= 0) {
|
||||
return true;
|
||||
}
|
||||
if (Util.SDK_INT >= 21) {
|
||||
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;
|
||||
}
|
||||
return isVideoSizeAndRateSupportedV21(format.width, format.height, format.frameRate);
|
||||
} else { // Audio
|
||||
return Util.SDK_INT < 21
|
||||
|| ((format.sampleRate == Format.NO_VALUE
|
||||
|| isAudioSampleRateSupportedV21(format.sampleRate))
|
||||
&& (format.channelCount == Format.NO_VALUE
|
||||
|| isAudioChannelCountSupportedV21(format.channelCount)));
|
||||
return (format.sampleRate == Format.NO_VALUE
|
||||
|| isAudioSampleRateSupportedV21(format.sampleRate))
|
||||
&& (format.channelCount == Format.NO_VALUE
|
||||
|| isAudioChannelCountSupportedV21(format.channelCount));
|
||||
}
|
||||
}
|
||||
|
||||
@ -482,7 +472,6 @@ public final class MediaCodecInfo {
|
||||
* 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.
|
||||
*/
|
||||
@RequiresApi(21)
|
||||
public boolean isVideoSizeAndRateSupportedV21(int width, int height, double frameRate) {
|
||||
if (capabilities == null) {
|
||||
logNoSupport("sizeAndRate.caps");
|
||||
@ -506,13 +495,13 @@ public final class MediaCodecInfo {
|
||||
return false;
|
||||
}
|
||||
// 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
|
||||
|| !needsRotatedVerticalResolutionWorkaround(name)
|
||||
|| !areSizeAndRateSupportedV21(videoCapabilities, height, width, frameRate)) {
|
||||
|| !areSizeAndRateSupported(videoCapabilities, height, width, frameRate)) {
|
||||
logNoSupport("sizeAndRate.support, " + width + "x" + height + "@" + frameRate);
|
||||
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
|
||||
* 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 height Height in pixels.
|
||||
* @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.
|
||||
*/
|
||||
@Nullable
|
||||
@RequiresApi(21)
|
||||
public Point alignVideoSizeV21(int width, int height) {
|
||||
if (capabilities == null) {
|
||||
return null;
|
||||
@ -543,18 +529,15 @@ public final class MediaCodecInfo {
|
||||
if (videoCapabilities == null) {
|
||||
return null;
|
||||
}
|
||||
return alignVideoSizeV21(videoCapabilities, width, height);
|
||||
return alignVideoSize(videoCapabilities, width, height);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* @return Whether the decoder supports audio with the given sample rate.
|
||||
*/
|
||||
@RequiresApi(21)
|
||||
public boolean isAudioSampleRateSupportedV21(int sampleRate) {
|
||||
if (capabilities == null) {
|
||||
logNoSupport("sampleRate.caps");
|
||||
@ -575,12 +558,9 @@ public final class MediaCodecInfo {
|
||||
/**
|
||||
* 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.
|
||||
* @return Whether the decoder supports audio with the given channel count.
|
||||
*/
|
||||
@RequiresApi(21)
|
||||
public boolean isAudioChannelCountSupportedV21(int channelCount) {
|
||||
if (capabilities == null) {
|
||||
logNoSupport("channelCount.caps");
|
||||
@ -674,28 +654,17 @@ public final class MediaCodecInfo {
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
@RequiresApi(21)
|
||||
private static boolean areSizeAndRateSupportedV21(
|
||||
private static boolean areSizeAndRateSupported(
|
||||
VideoCapabilities capabilities, int width, int height, double frameRate) {
|
||||
// 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;
|
||||
height = alignedSize.y;
|
||||
|
||||
@ -712,8 +681,7 @@ public final class MediaCodecInfo {
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresApi(21)
|
||||
private static Point alignVideoSizeV21(VideoCapabilities capabilities, int width, int height) {
|
||||
private static Point alignVideoSize(VideoCapabilities capabilities, int width, int height) {
|
||||
int widthAlignment = capabilities.getWidthAlignment();
|
||||
int heightAlignment = capabilities.getHeightAlignment();
|
||||
return new Point(
|
||||
|
@ -166,12 +166,9 @@ public final class MediaCodecUtil {
|
||||
if (cachedDecoderInfos != null) {
|
||||
return cachedDecoderInfos;
|
||||
}
|
||||
MediaCodecListCompat mediaCodecList =
|
||||
Util.SDK_INT >= 21
|
||||
? new MediaCodecListCompatV21(secure, tunneling)
|
||||
: new MediaCodecListCompatV16();
|
||||
MediaCodecListCompat mediaCodecList = new MediaCodecListCompatV21(secure, tunneling);
|
||||
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
|
||||
// legacy path. We also try this path on API levels 22 and 23 as a defensive measure.
|
||||
mediaCodecList = new MediaCodecListCompatV16();
|
||||
@ -290,9 +287,8 @@ public final class MediaCodecUtil {
|
||||
for (CodecProfileLevel profileLevel : decoderInfo.getProfileLevels()) {
|
||||
result = max(avcLevelToMaxFrameSize(profileLevel.level), result);
|
||||
}
|
||||
// We assume support for at least 480p (SDK_INT >= 21) or 360p (SDK_INT < 21), which are
|
||||
// the levels mandated by the Android CDD.
|
||||
result = max(result, Util.SDK_INT >= 21 ? (720 * 480) : (480 * 360));
|
||||
// We assume support for at least 480p, which is the level mandated by the Android CDD.
|
||||
result = max(result, 720 * 480);
|
||||
}
|
||||
maxH264DecodableFrameSize = result;
|
||||
}
|
||||
@ -562,17 +558,6 @@ public final class MediaCodecUtil {
|
||||
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.
|
||||
if (Util.SDK_INT < 24
|
||||
&& ("OMX.SEC.aac.dec".equals(name) || "OMX.Exynos.AAC.Decoder".equals(name))
|
||||
@ -588,26 +573,6 @@ public final class MediaCodecUtil {
|
||||
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].
|
||||
if (Util.SDK_INT <= 23
|
||||
&& 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) {
|
||||
String firstCodecName = decoderInfos.get(0).name;
|
||||
// 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);
|
||||
}
|
||||
|
||||
@RequiresApi(21)
|
||||
private static final class MediaCodecListCompatV21 implements MediaCodecListCompat {
|
||||
|
||||
private final int codecKind;
|
||||
|
@ -17,7 +17,6 @@
|
||||
package androidx.media3.exoplayer.mediacodec;
|
||||
|
||||
import static androidx.media3.common.util.Assertions.checkNotNull;
|
||||
import static androidx.media3.common.util.Util.castNonNull;
|
||||
|
||||
import android.media.MediaCodec;
|
||||
import android.media.MediaFormat;
|
||||
@ -80,15 +79,9 @@ public final class SynchronousMediaCodecAdapter implements MediaCodecAdapter {
|
||||
}
|
||||
|
||||
private final MediaCodec codec;
|
||||
@Nullable private ByteBuffer[] inputByteBuffers;
|
||||
@Nullable private ByteBuffer[] outputByteBuffers;
|
||||
|
||||
private SynchronousMediaCodecAdapter(MediaCodec mediaCodec) {
|
||||
this.codec = mediaCodec;
|
||||
if (Util.SDK_INT < 21) {
|
||||
inputByteBuffers = codec.getInputBuffers();
|
||||
outputByteBuffers = codec.getOutputBuffers();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -106,9 +99,6 @@ public final class SynchronousMediaCodecAdapter implements MediaCodecAdapter {
|
||||
int index;
|
||||
do {
|
||||
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);
|
||||
|
||||
return index;
|
||||
@ -122,21 +112,13 @@ public final class SynchronousMediaCodecAdapter implements MediaCodecAdapter {
|
||||
@Override
|
||||
@Nullable
|
||||
public ByteBuffer getInputBuffer(int index) {
|
||||
if (Util.SDK_INT >= 21) {
|
||||
return codec.getInputBuffer(index);
|
||||
} else {
|
||||
return castNonNull(inputByteBuffers)[index];
|
||||
}
|
||||
return codec.getInputBuffer(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public ByteBuffer getOutputBuffer(int index) {
|
||||
if (Util.SDK_INT >= 21) {
|
||||
return codec.getOutputBuffer(index);
|
||||
} else {
|
||||
return castNonNull(outputByteBuffers)[index];
|
||||
}
|
||||
return codec.getOutputBuffer(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -158,7 +140,6 @@ public final class SynchronousMediaCodecAdapter implements MediaCodecAdapter {
|
||||
}
|
||||
|
||||
@Override
|
||||
@RequiresApi(21)
|
||||
public void releaseOutputBuffer(int index, long renderTimeStampNs) {
|
||||
codec.releaseOutputBuffer(index, renderTimeStampNs);
|
||||
}
|
||||
@ -170,8 +151,6 @@ public final class SynchronousMediaCodecAdapter implements MediaCodecAdapter {
|
||||
|
||||
@Override
|
||||
public void release() {
|
||||
inputByteBuffers = null;
|
||||
outputByteBuffers = null;
|
||||
try {
|
||||
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
|
||||
|
@ -25,7 +25,6 @@ import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.PersistableBundle;
|
||||
import androidx.annotation.RequiresApi;
|
||||
import androidx.annotation.RequiresPermission;
|
||||
import androidx.media3.common.util.Log;
|
||||
import androidx.media3.common.util.UnstableApi;
|
||||
@ -44,7 +43,6 @@ import androidx.media3.common.util.Util;
|
||||
* android:exported="true"/>
|
||||
* }</pre>
|
||||
*/
|
||||
@RequiresApi(21)
|
||||
@UnstableApi
|
||||
public final class PlatformScheduler implements Scheduler {
|
||||
|
||||
|
@ -202,9 +202,7 @@ public final class Requirements implements Parcelable {
|
||||
private boolean isDeviceIdle(Context context) {
|
||||
PowerManager powerManager =
|
||||
(PowerManager) Assertions.checkNotNull(context.getSystemService(Context.POWER_SERVICE));
|
||||
return Util.SDK_INT >= 23
|
||||
? powerManager.isDeviceIdleMode()
|
||||
: Util.SDK_INT >= 20 ? !powerManager.isInteractive() : !powerManager.isScreenOn();
|
||||
return Util.SDK_INT >= 23 ? powerManager.isDeviceIdleMode() : !powerManager.isInteractive();
|
||||
}
|
||||
|
||||
private boolean isStorageNotLow(Context context) {
|
||||
|
@ -58,13 +58,10 @@ import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CopyOnWriteArraySet;
|
||||
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.MonotonicNonNull;
|
||||
|
||||
@ -491,7 +488,6 @@ public final class CompositingVideoSinkProvider implements VideoSinkProvider, Vi
|
||||
private final VideoFrameReleaseControl.FrameReleaseInfo frameReleaseInfo;
|
||||
|
||||
private @MonotonicNonNull VideoFrameProcessor videoFrameProcessor;
|
||||
@Nullable private Effect rotationEffect;
|
||||
@Nullable private Format inputFormat;
|
||||
private @InputType int inputType;
|
||||
private long inputStreamStartPositionUs;
|
||||
@ -611,21 +607,6 @@ public final class CompositingVideoSinkProvider implements VideoSinkProvider, Vi
|
||||
throw new UnsupportedOperationException("Unsupported input type " + inputType);
|
||||
}
|
||||
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.inputFormat = format;
|
||||
|
||||
@ -884,9 +865,6 @@ public final class CompositingVideoSinkProvider implements VideoSinkProvider, Vi
|
||||
}
|
||||
|
||||
ArrayList<Effect> effects = new ArrayList<>();
|
||||
if (rotationEffect != null) {
|
||||
effects.add(rotationEffect);
|
||||
}
|
||||
effects.addAll(videoEffects);
|
||||
Format inputFormat = checkNotNull(this.inputFormat);
|
||||
checkStateNotNull(videoFrameProcessor)
|
||||
@ -1081,42 +1059,4 @@ public final class CompositingVideoSinkProvider implements VideoSinkProvider, Vi
|
||||
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");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -18,7 +18,6 @@ package androidx.media3.exoplayer.audio;
|
||||
import static androidx.media3.common.C.FORMAT_HANDLED;
|
||||
import static androidx.media3.exoplayer.RendererCapabilities.ADAPTIVE_NOT_SEAMLESS;
|
||||
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.test.utils.FakeSampleStream.FakeSampleStreamItem.END_OF_STREAM_ITEM;
|
||||
import static androidx.media3.test.utils.FakeSampleStream.FakeSampleStreamItem.oneByteSample;
|
||||
@ -96,17 +95,6 @@ public class DecoderAudioRendererTest {
|
||||
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)
|
||||
@Test
|
||||
public void supportsFormatAtApi21() {
|
||||
|
@ -315,20 +315,6 @@ public final class DefaultAudioSinkTest {
|
||||
.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)
|
||||
@Test
|
||||
public void floatOutputSupportedIfFloatOutputEnabledFromApi21() {
|
||||
|
@ -21,7 +21,6 @@ import android.media.MediaCodec.BufferInfo;
|
||||
import androidx.media3.common.C;
|
||||
import androidx.media3.common.Format;
|
||||
import androidx.media3.common.MimeTypes;
|
||||
import androidx.media3.common.util.Util;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.Lists;
|
||||
import java.nio.ByteBuffer;
|
||||
@ -218,8 +217,7 @@ import org.checkerframework.checker.nullness.qual.PolyNull;
|
||||
return null;
|
||||
}
|
||||
|
||||
Locale locale =
|
||||
Util.SDK_INT >= 21 ? Locale.forLanguageTag(languageTag) : new Locale(languageTag);
|
||||
Locale locale = Locale.forLanguageTag(languageTag);
|
||||
|
||||
return locale.getISO3Language().isEmpty() ? languageTag : locale.getISO3Language();
|
||||
}
|
||||
|
@ -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.
|
||||
mediaStyle.setCancelButtonIntent(
|
||||
actionFactory.createMediaActionPendingIntent(mediaSession, COMMAND_STOP));
|
||||
@ -639,9 +639,7 @@ public class DefaultMediaNotificationProvider implements MediaNotification.Provi
|
||||
}
|
||||
|
||||
private static long getPlaybackStartTimeEpochMs(Player player) {
|
||||
// Changing "showWhen" causes notification flicker if SDK_INT < 21.
|
||||
if (Util.SDK_INT >= 21
|
||||
&& player.isPlaying()
|
||||
if (player.isPlaying()
|
||||
&& !player.isPlayingAd()
|
||||
&& !player.isCurrentMediaItemDynamic()
|
||||
&& player.getPlaybackParameters().speed == 1f) {
|
||||
|
@ -98,13 +98,8 @@ public abstract class MediaLibraryService extends MediaSessionService {
|
||||
* <th>{@link ControllerInfo#getPackageName()}<br>for legacy browser</th>
|
||||
* <th>{@link ControllerInfo#getUid()}<br>for legacy browser</th></tr>
|
||||
* <tr>
|
||||
* <td>{@code SDK_INT < 21}</td>
|
||||
* <td>Actual package name via {@link Context#getPackageName()}</td>
|
||||
* <td>Actual UID</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>
|
||||
* {@code 21 <= SDK_INT < 28}<br>
|
||||
* {@code SDK_INT < 28}<br>
|
||||
* for {@link Callback#onConnect onConnect}<br>
|
||||
* and {@link Callback#onGetLibraryRoot onGetLibraryRoot}
|
||||
* </td>
|
||||
@ -113,7 +108,7 @@ public abstract class MediaLibraryService extends MediaSessionService {
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>
|
||||
* {@code 21 <= SDK_INT < 28}<br>
|
||||
* {@code SDK_INT < 28}<br>
|
||||
* for other {@link Callback callbacks}
|
||||
* </td>
|
||||
* <td>{@link ControllerInfo#LEGACY_CONTROLLER_PACKAGE_NAME}</td>
|
||||
|
@ -210,14 +210,11 @@ import java.util.concurrent.TimeoutException;
|
||||
MediaSession session,
|
||||
MediaNotification mediaNotification,
|
||||
boolean startInForegroundRequired) {
|
||||
if (Util.SDK_INT >= 21) {
|
||||
// Call Notification.MediaStyle#setMediaSession() indirectly.
|
||||
android.media.session.MediaSession.Token fwkToken =
|
||||
(android.media.session.MediaSession.Token)
|
||||
session.getSessionCompat().getSessionToken().getToken();
|
||||
mediaNotification.notification.extras.putParcelable(
|
||||
Notification.EXTRA_MEDIA_SESSION, fwkToken);
|
||||
}
|
||||
// Call Notification.MediaStyle#setMediaSession() indirectly.
|
||||
android.media.session.MediaSession.Token fwkToken =
|
||||
(android.media.session.MediaSession.Token)
|
||||
session.getSessionCompat().getSessionToken().getToken();
|
||||
mediaNotification.notification.extras.putParcelable(Notification.EXTRA_MEDIA_SESSION, fwkToken);
|
||||
this.mediaNotification = mediaNotification;
|
||||
if (startInForegroundRequired) {
|
||||
startForeground(mediaNotification);
|
||||
@ -379,9 +376,7 @@ import java.util.concurrent.TimeoutException;
|
||||
if (Util.SDK_INT >= 24) {
|
||||
Api24.stopForeground(mediaSessionService, removeNotifications);
|
||||
} else {
|
||||
// For pre-L devices, we must call Service.stopForeground(true) anyway as a workaround
|
||||
// that prevents the media notification from being undismissable.
|
||||
mediaSessionService.stopForeground(removeNotifications || Util.SDK_INT < 21);
|
||||
mediaSessionService.stopForeground(removeNotifications);
|
||||
}
|
||||
startedInForeground = false;
|
||||
}
|
||||
|
@ -25,7 +25,6 @@ import static androidx.media3.session.SessionResult.RESULT_SUCCESS;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.IBinder;
|
||||
@ -524,25 +523,7 @@ public class MediaSession {
|
||||
return interfaceVersion;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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>
|
||||
*/
|
||||
/** Returns the package name, or {@link #LEGACY_CONTROLLER_PACKAGE_NAME} on API ≤ 24. */
|
||||
public String getPackageName() {
|
||||
return remoteUserInfo.getPackageName();
|
||||
}
|
||||
@ -550,8 +531,8 @@ public class MediaSession {
|
||||
/**
|
||||
* 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
|
||||
* because it cannot be obtained.
|
||||
* <p>Interoperability: If {@code SDK_INT < 28}, then UID would be a negative value because it
|
||||
* cannot be obtained.
|
||||
*/
|
||||
public int getUid() {
|
||||
return remoteUserInfo.getUid();
|
||||
@ -1221,7 +1202,6 @@ public class MediaSession {
|
||||
* android.media.session.MediaSession} created internally by this session.
|
||||
*/
|
||||
@SuppressWarnings("UnnecessarilyFullyQualified") // Avoiding clash with Media3 token.
|
||||
@RequiresApi(21)
|
||||
@UnstableApi
|
||||
public final android.media.session.MediaSession.Token getPlatformToken() {
|
||||
return (android.media.session.MediaSession.Token)
|
||||
|
@ -53,11 +53,9 @@ import android.os.SystemClock;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.ViewConfiguration;
|
||||
import androidx.annotation.CheckResult;
|
||||
import androidx.annotation.DoNotInline;
|
||||
import androidx.annotation.FloatRange;
|
||||
import androidx.annotation.GuardedBy;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.RequiresApi;
|
||||
import androidx.media3.common.AudioAttributes;
|
||||
import androidx.media3.common.DeviceInfo;
|
||||
import androidx.media3.common.MediaItem;
|
||||
@ -1213,7 +1211,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
}
|
||||
// Double tap detection.
|
||||
int keyCode = keyEvent.getKeyCode();
|
||||
boolean isTvApp = Util.SDK_INT >= 21 && Api21.isTvApp(context);
|
||||
boolean isTvApp = context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK);
|
||||
boolean doubleTapCompleted = false;
|
||||
switch (keyCode) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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.Util.castNonNull;
|
||||
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.SessionError.ERROR_UNKNOWN;
|
||||
import static androidx.media3.session.SessionResult.RESULT_INFO_SKIPPED;
|
||||
@ -1226,7 +1225,7 @@ import org.checkerframework.checker.initialization.qual.Initialized;
|
||||
() -> {
|
||||
int completedBitmapFutureCount = resultCount.incrementAndGet();
|
||||
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(
|
||||
List<@NullableType ListenableFuture<Bitmap>> bitmapFutures,
|
||||
Timeline timeline,
|
||||
List<MediaItem> mediaItems) {
|
||||
List<@NullableType ListenableFuture<Bitmap>> bitmapFutures, List<MediaItem> mediaItems) {
|
||||
List<QueueItem> queueItemList = new ArrayList<>();
|
||||
for (int i = 0; i < bitmapFutures.size(); 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));
|
||||
}
|
||||
|
||||
if (Util.SDK_INT < 21) {
|
||||
// In order to avoid TransactionTooLargeException for below API 21, we need to
|
||||
// cut the list so that it doesn't exceed the binder transaction limit.
|
||||
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);
|
||||
}
|
||||
// Framework MediaSession#setQueue() uses ParceledListSlice,
|
||||
// which means we can safely send long lists.
|
||||
setQueue(sessionCompat, queueItemList);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -300,8 +300,7 @@ public final class SessionToken {
|
||||
|
||||
private static MediaSessionCompat.Token createCompatToken(
|
||||
Parcelable platformOrLegacyCompatToken) {
|
||||
if (Util.SDK_INT >= 21
|
||||
&& platformOrLegacyCompatToken instanceof android.media.session.MediaSession.Token) {
|
||||
if (platformOrLegacyCompatToken instanceof android.media.session.MediaSession.Token) {
|
||||
return MediaSessionCompat.Token.fromToken(platformOrLegacyCompatToken);
|
||||
}
|
||||
// Assume this is an android.support.v4.media.session.MediaSessionCompat.Token.
|
||||
|
@ -666,7 +666,7 @@ public final class DashStreamingTest {
|
||||
MediaCodecUtil.getDecoderInfo(
|
||||
MimeTypes.VIDEO_H264, /* secure= */ false, /* tunneling= */ false);
|
||||
assertThat(decoderInfo).isNotNull();
|
||||
assertThat(Util.SDK_INT < 21 || decoderInfo.adaptive).isTrue();
|
||||
assertThat(decoderInfo.adaptive).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -17,13 +17,11 @@ package androidx.media3.test.exoplayer.playback.gts;
|
||||
|
||||
import static java.lang.Math.max;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.media.MediaCrypto;
|
||||
import android.media.MediaFormat;
|
||||
import android.os.Handler;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.RequiresApi;
|
||||
import androidx.media3.common.C;
|
||||
import androidx.media3.common.Format;
|
||||
import androidx.media3.decoder.DecoderInputBuffer;
|
||||
@ -244,8 +242,6 @@ import java.util.ArrayList;
|
||||
super.renderOutputBuffer(codec, index, presentationTimeUs);
|
||||
}
|
||||
|
||||
@SuppressLint("UseSdkSuppress") // Not a test class or method.
|
||||
@RequiresApi(21)
|
||||
@Override
|
||||
protected void renderOutputBufferV21(
|
||||
MediaCodecAdapter codec, int index, long presentationTimeUs, long releaseTimeNs) {
|
||||
|
@ -115,9 +115,7 @@ public class EnumerateDecodersTest {
|
||||
boolean isAudio = MimeTypes.isAudio(requestedMimeType);
|
||||
StringBuilder result = new StringBuilder();
|
||||
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=");
|
||||
appendProfileLevels(codecCapabilities.profileLevels, result);
|
||||
if (Util.SDK_INT >= 23) {
|
||||
@ -125,23 +123,19 @@ public class EnumerateDecodersTest {
|
||||
.append(", maxSupportedInstances=")
|
||||
.append(codecCapabilities.getMaxSupportedInstances());
|
||||
}
|
||||
if (Util.SDK_INT >= 21) {
|
||||
if (isVideo) {
|
||||
result.append(", videoCapabilities=");
|
||||
appendVideoCapabilities(codecCapabilities.getVideoCapabilities(), result);
|
||||
result.append(", colorFormats=").append(Arrays.toString(codecCapabilities.colorFormats));
|
||||
} else if (isAudio) {
|
||||
result.append(", audioCapabilities=");
|
||||
appendAudioCapabilities(codecCapabilities.getAudioCapabilities(), result);
|
||||
}
|
||||
if (isVideo) {
|
||||
result.append(", videoCapabilities=");
|
||||
appendVideoCapabilities(codecCapabilities.getVideoCapabilities(), result);
|
||||
result.append(", colorFormats=").append(Arrays.toString(codecCapabilities.colorFormats));
|
||||
} else if (isAudio) {
|
||||
result.append(", audioCapabilities=");
|
||||
appendAudioCapabilities(codecCapabilities.getAudioCapabilities(), result);
|
||||
}
|
||||
if (isVideo
|
||||
&& codecCapabilities.isFeatureSupported(CodecCapabilities.FEATURE_AdaptivePlayback)) {
|
||||
result.append(", FEATURE_AdaptivePlayback");
|
||||
}
|
||||
if (Util.SDK_INT >= 21
|
||||
&& isVideo
|
||||
&& codecCapabilities.isFeatureSupported(CodecCapabilities.FEATURE_SecurePlayback)) {
|
||||
if (isVideo && codecCapabilities.isFeatureSupported(CodecCapabilities.FEATURE_SecurePlayback)) {
|
||||
result.append(", FEATURE_SecurePlayback");
|
||||
}
|
||||
if (Util.SDK_INT >= 26
|
||||
@ -149,8 +143,7 @@ public class EnumerateDecodersTest {
|
||||
&& codecCapabilities.isFeatureSupported(CodecCapabilities.FEATURE_PartialFrame)) {
|
||||
result.append(", FEATURE_PartialFrame");
|
||||
}
|
||||
if (Util.SDK_INT >= 21
|
||||
&& (isVideo || isAudio)
|
||||
if ((isVideo || isAudio)
|
||||
&& codecCapabilities.isFeatureSupported(CodecCapabilities.FEATURE_TunneledPlayback)) {
|
||||
result.append(", FEATURE_TunneledPlayback");
|
||||
}
|
||||
|
@ -610,7 +610,7 @@ public class MediaControllerCompatCallbackWithMediaSessionTest {
|
||||
session.setPlayer(playerConfigToUpdate);
|
||||
|
||||
// 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(
|
||||
TIMEOUT_MS,
|
||||
() -> {
|
||||
@ -657,21 +657,13 @@ public class MediaControllerCompatCallbackWithMediaSessionTest {
|
||||
|
||||
session.setPlayer(playerConfig);
|
||||
|
||||
// In API 21+, onAudioInfoChanged() is not called when playbackType is not changed.
|
||||
if (Util.SDK_INT >= 21) {
|
||||
PollingCheck.waitFor(
|
||||
TIMEOUT_MS,
|
||||
() -> {
|
||||
MediaControllerCompat.PlaybackInfo info = controllerCompat.getPlaybackInfo();
|
||||
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);
|
||||
}
|
||||
PollingCheck.waitFor(
|
||||
TIMEOUT_MS,
|
||||
() -> {
|
||||
MediaControllerCompat.PlaybackInfo info = controllerCompat.getPlaybackInfo();
|
||||
return info.getPlaybackType() == legacyPlaybackType
|
||||
&& info.getAudioAttributes().getLegacyStreamType() == legacyStream;
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -709,23 +701,14 @@ public class MediaControllerCompatCallbackWithMediaSessionTest {
|
||||
|
||||
session.setPlayer(playerConfigToUpdate);
|
||||
|
||||
// In API 21+, onAudioInfoChanged() is not called when playbackType is not changed.
|
||||
if (Util.SDK_INT >= 21) {
|
||||
PollingCheck.waitFor(
|
||||
TIMEOUT_MS,
|
||||
() -> {
|
||||
MediaControllerCompat.PlaybackInfo info = controllerCompat.getPlaybackInfo();
|
||||
return info.getPlaybackType() == legacyPlaybackTypeToUpdate
|
||||
&& 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);
|
||||
}
|
||||
PollingCheck.waitFor(
|
||||
TIMEOUT_MS,
|
||||
() -> {
|
||||
MediaControllerCompat.PlaybackInfo info = controllerCompat.getPlaybackInfo();
|
||||
return info.getPlaybackType() == legacyPlaybackTypeToUpdate
|
||||
&& info.getMaxVolume() == deviceInfoToUpdate.maxVolume
|
||||
&& info.getCurrentVolume() == deviceVolumeToUpdate;
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -1555,15 +1538,8 @@ public class MediaControllerCompatCallbackWithMediaSessionTest {
|
||||
assertThat(latch.await(LONG_TIMEOUT_MS, MILLISECONDS)).isTrue();
|
||||
List<QueueItem> queueFromParam = queueRef.get();
|
||||
List<QueueItem> queueFromGetter = controllerCompat.getQueue();
|
||||
if (Util.SDK_INT >= 21) {
|
||||
assertThat(queueFromParam).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());
|
||||
}
|
||||
assertThat(queueFromParam).hasSize(listSize);
|
||||
assertThat(queueFromGetter).hasSize(listSize);
|
||||
for (int i = 0; i < queueFromParam.size(); i++) {
|
||||
assertThat(queueFromParam.get(i).getDescription().getMediaId())
|
||||
.isEqualTo(TestUtils.getMediaIdInFakeTimeline(i));
|
||||
|
@ -347,7 +347,7 @@ public class MediaControllerListenerWithMediaSessionCompatTest {
|
||||
// We need to trigger MediaControllerCompat.Callback.onAudioInfoChanged in order to raise the
|
||||
// onAudioAttributesChanged() callback. In API 21 and 22, onAudioInfoChanged is not called when
|
||||
// playback is changed to local.
|
||||
assumeTrue(Util.SDK_INT != 21 && Util.SDK_INT != 22);
|
||||
assumeTrue(Util.SDK_INT > 22);
|
||||
|
||||
session.setPlaybackToRemote(
|
||||
/* volumeControl= */ VolumeProviderCompat.VOLUME_CONTROL_ABSOLUTE,
|
||||
|
@ -49,7 +49,6 @@ import androidx.media3.common.Player.PositionInfo;
|
||||
import androidx.media3.common.Timeline;
|
||||
import androidx.media3.common.Timeline.Window;
|
||||
import androidx.media3.common.util.BitmapLoader;
|
||||
import androidx.media3.common.util.Util;
|
||||
import androidx.media3.datasource.DataSourceBitmapLoader;
|
||||
import androidx.media3.test.session.common.HandlerThreadTestRule;
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
@ -200,24 +199,6 @@ public class MediaControllerMediaSessionCompatCallbackAggregationTest {
|
||||
MediaItem mediaItem = timelineRef.get().getWindow(i, new Window()).mediaItem;
|
||||
MediaItem expectedMediaItem =
|
||||
(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(timelineChangeReasonRef.get()).isEqualTo(TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED);
|
||||
|
@ -27,7 +27,6 @@ import android.media.session.PlaybackState;
|
||||
import android.os.HandlerThread;
|
||||
import androidx.media3.common.Player;
|
||||
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.TestHandler;
|
||||
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.AtomicInteger;
|
||||
import org.junit.After;
|
||||
import org.junit.Assume;
|
||||
import org.junit.Before;
|
||||
import org.junit.ClassRule;
|
||||
import org.junit.Test;
|
||||
@ -57,9 +55,6 @@ public class MediaControllerWithFrameworkMediaSessionTest {
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
if (Util.SDK_INT < 21) {
|
||||
return;
|
||||
}
|
||||
context = ApplicationProvider.getApplicationContext();
|
||||
|
||||
HandlerThread handlerThread = new HandlerThread(TAG);
|
||||
@ -79,7 +74,6 @@ public class MediaControllerWithFrameworkMediaSessionTest {
|
||||
@SuppressWarnings("UnnecessarilyFullyQualified") // Intentionally fully qualified for fwk session.
|
||||
@Test
|
||||
public void createController() throws Exception {
|
||||
Assume.assumeTrue(Util.SDK_INT >= 21); // For framework MediaSession.
|
||||
MediaSession fwkSession = new android.media.session.MediaSession(context, TAG);
|
||||
fwkSession.setActive(true);
|
||||
fwkSession.setFlags(
|
||||
@ -100,7 +94,6 @@ public class MediaControllerWithFrameworkMediaSessionTest {
|
||||
@SuppressWarnings("UnnecessarilyFullyQualified") // Intentionally fully qualified for fwk session.
|
||||
@Test
|
||||
public void onPlaybackStateChanged_isNotifiedByFwkSessionChanges() throws Exception {
|
||||
Assume.assumeTrue(Util.SDK_INT >= 21); // For framework MediaSession.
|
||||
MediaSession fwkSession = new android.media.session.MediaSession(context, TAG);
|
||||
fwkSession.setActive(true);
|
||||
fwkSession.setFlags(
|
||||
|
@ -571,11 +571,8 @@ public class MediaControllerWithMediaSessionCompatTest {
|
||||
assertThat(TextUtils.equals(metadata.subtitle, testSubtitle)).isTrue();
|
||||
assertThat(TextUtils.equals(metadata.description, testDescription)).isTrue();
|
||||
assertThat(metadata.artworkUri).isEqualTo(testIconUri);
|
||||
if (Util.SDK_INT >= 21) {
|
||||
// Bitmap conversion and back gives not exactly the same byte array below API 21
|
||||
assertThat(metadata.artworkData).isEqualTo(testArtworkData);
|
||||
}
|
||||
if (Util.SDK_INT < 21 || Util.SDK_INT >= 23) {
|
||||
assertThat(metadata.artworkData).isEqualTo(testArtworkData);
|
||||
if (Util.SDK_INT >= 23) {
|
||||
// TODO(b/199055952): Test mediaUri for all API levels once the bug is fixed.
|
||||
assertThat(mediaItem.requestMetadata.mediaUri).isEqualTo(testMediaUri);
|
||||
}
|
||||
@ -909,19 +906,6 @@ public class MediaControllerWithMediaSessionCompatTest {
|
||||
threadTestRule.getHandler().postAndSync(controller::getMediaMetadata);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
@ -1573,7 +1557,7 @@ public class MediaControllerWithMediaSessionCompatTest {
|
||||
|
||||
@Test
|
||||
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.
|
||||
return;
|
||||
}
|
||||
|
@ -91,7 +91,7 @@ public class MediaSessionCallbackWithMediaControllerCompatTest {
|
||||
|
||||
private static final String TEST_URI = "http://test.test";
|
||||
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();
|
||||
|
||||
@ -1200,7 +1200,7 @@ public class MediaSessionCallbackWithMediaControllerCompatTest {
|
||||
|
||||
@Test
|
||||
public void setVolumeWithLocalVolume() throws Exception {
|
||||
if (Util.SDK_INT >= 21 && audioManager.isVolumeFixed()) {
|
||||
if (audioManager.isVolumeFixed()) {
|
||||
// This test is not eligible for this device.
|
||||
return;
|
||||
}
|
||||
@ -1250,7 +1250,7 @@ public class MediaSessionCallbackWithMediaControllerCompatTest {
|
||||
|
||||
@Test
|
||||
public void adjustVolumeWithLocalVolume() throws Exception {
|
||||
if (Util.SDK_INT >= 21 && audioManager.isVolumeFixed()) {
|
||||
if (audioManager.isVolumeFixed()) {
|
||||
// This test is not eligible for this device.
|
||||
return;
|
||||
}
|
||||
|
@ -541,7 +541,7 @@ public class MediaSessionCompatCallbackWithMediaControllerTest {
|
||||
|
||||
@Test
|
||||
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.
|
||||
return;
|
||||
}
|
||||
@ -570,7 +570,7 @@ public class MediaSessionCompatCallbackWithMediaControllerTest {
|
||||
|
||||
@Test
|
||||
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.
|
||||
return;
|
||||
}
|
||||
@ -600,7 +600,7 @@ public class MediaSessionCompatCallbackWithMediaControllerTest {
|
||||
|
||||
@Test
|
||||
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.
|
||||
return;
|
||||
}
|
||||
|
@ -45,7 +45,6 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import org.junit.After;
|
||||
import org.junit.Assume;
|
||||
import org.junit.Before;
|
||||
import org.junit.ClassRule;
|
||||
import org.junit.Rule;
|
||||
@ -79,9 +78,6 @@ public class MediaSessionKeyEventTest {
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
if (Util.SDK_INT < 21) {
|
||||
return;
|
||||
}
|
||||
Context context = ApplicationProvider.getApplicationContext();
|
||||
audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
|
||||
handler = threadTestRule.getHandler();
|
||||
@ -128,9 +124,6 @@ public class MediaSessionKeyEventTest {
|
||||
|
||||
@After
|
||||
public void tearDown() throws Exception {
|
||||
if (Util.SDK_INT < 21) {
|
||||
return;
|
||||
}
|
||||
handler.postAndSync(
|
||||
() -> {
|
||||
if (mediaPlayer != null) {
|
||||
@ -143,7 +136,6 @@ public class MediaSessionKeyEventTest {
|
||||
|
||||
@Test
|
||||
public void playKeyEvent() throws Exception {
|
||||
Assume.assumeTrue(Util.SDK_INT >= 21); // TODO: b/199064299 - Lower minSdk to 19.
|
||||
dispatchMediaKeyEvent(KeyEvent.KEYCODE_MEDIA_PLAY, false);
|
||||
|
||||
player.awaitMethodCalled(MockPlayer.METHOD_PLAY, TIMEOUT_MS);
|
||||
@ -151,7 +143,6 @@ public class MediaSessionKeyEventTest {
|
||||
|
||||
@Test
|
||||
public void pauseKeyEvent() throws Exception {
|
||||
Assume.assumeTrue(Util.SDK_INT >= 21); // TODO: b/199064299 - Lower minSdk to 19.
|
||||
dispatchMediaKeyEvent(KeyEvent.KEYCODE_MEDIA_PAUSE, false);
|
||||
|
||||
player.awaitMethodCalled(MockPlayer.METHOD_PAUSE, TIMEOUT_MS);
|
||||
@ -159,7 +150,6 @@ public class MediaSessionKeyEventTest {
|
||||
|
||||
@Test
|
||||
public void nextKeyEvent() throws Exception {
|
||||
Assume.assumeTrue(Util.SDK_INT >= 21); // TODO: b/199064299 - Lower minSdk to 19.
|
||||
dispatchMediaKeyEvent(KeyEvent.KEYCODE_MEDIA_NEXT, false);
|
||||
|
||||
player.awaitMethodCalled(MockPlayer.METHOD_SEEK_TO_NEXT, TIMEOUT_MS);
|
||||
@ -167,7 +157,6 @@ public class MediaSessionKeyEventTest {
|
||||
|
||||
@Test
|
||||
public void previousKeyEvent() throws Exception {
|
||||
Assume.assumeTrue(Util.SDK_INT >= 21); // TODO: b/199064299 - Lower minSdk to 19.
|
||||
dispatchMediaKeyEvent(KeyEvent.KEYCODE_MEDIA_PREVIOUS, false);
|
||||
|
||||
player.awaitMethodCalled(MockPlayer.METHOD_SEEK_TO_PREVIOUS, TIMEOUT_MS);
|
||||
@ -177,7 +166,6 @@ public class MediaSessionKeyEventTest {
|
||||
public void
|
||||
fastForwardKeyEvent_mediaNotificationControllerConnected_callFromNotificationController()
|
||||
throws Exception {
|
||||
Assume.assumeTrue(Util.SDK_INT >= 21); // TODO: b/199064299 - Lower minSdk to 19.
|
||||
MediaController controller = connectMediaNotificationController();
|
||||
dispatchMediaKeyEvent(KeyEvent.KEYCODE_MEDIA_FAST_FORWARD, /* doubleTap= */ false);
|
||||
|
||||
@ -204,8 +192,6 @@ public class MediaSessionKeyEventTest {
|
||||
public void
|
||||
fastForwardKeyEvent_mediaNotificationControllerNotConnected_callFromLegacyFallbackController()
|
||||
throws Exception {
|
||||
Assume.assumeTrue(Util.SDK_INT >= 21); // TODO: b/199064299 - Lower minSdk to 19.
|
||||
|
||||
dispatchMediaKeyEvent(KeyEvent.KEYCODE_MEDIA_FAST_FORWARD, false);
|
||||
|
||||
player.awaitMethodCalled(MockPlayer.METHOD_SEEK_FORWARD, TIMEOUT_MS);
|
||||
@ -220,7 +206,6 @@ public class MediaSessionKeyEventTest {
|
||||
@Test
|
||||
public void rewindKeyEvent_mediaNotificationControllerConnected_callFromNotificationController()
|
||||
throws Exception {
|
||||
Assume.assumeTrue(Util.SDK_INT >= 21); // TODO: b/199064299 - Lower minSdk to 19.
|
||||
MediaController controller = connectMediaNotificationController();
|
||||
|
||||
dispatchMediaKeyEvent(KeyEvent.KEYCODE_MEDIA_REWIND, false);
|
||||
@ -246,8 +231,6 @@ public class MediaSessionKeyEventTest {
|
||||
public void
|
||||
rewindKeyEvent_mediaNotificationControllerNotConnected_callFromLegacyFallbackController()
|
||||
throws Exception {
|
||||
Assume.assumeTrue(Util.SDK_INT >= 21); // TODO: b/199064299 - Lower minSdk to 19.
|
||||
|
||||
dispatchMediaKeyEvent(KeyEvent.KEYCODE_MEDIA_REWIND, false);
|
||||
|
||||
player.awaitMethodCalled(MockPlayer.METHOD_SEEK_BACK, TIMEOUT_MS);
|
||||
@ -261,7 +244,6 @@ public class MediaSessionKeyEventTest {
|
||||
|
||||
@Test
|
||||
public void stopKeyEvent() throws Exception {
|
||||
Assume.assumeTrue(Util.SDK_INT >= 21); // TODO: b/199064299 - Lower minSdk to 19.
|
||||
dispatchMediaKeyEvent(KeyEvent.KEYCODE_MEDIA_STOP, false);
|
||||
|
||||
player.awaitMethodCalled(MockPlayer.METHOD_STOP, TIMEOUT_MS);
|
||||
@ -320,7 +302,6 @@ public class MediaSessionKeyEventTest {
|
||||
|
||||
@Test
|
||||
public void playPauseKeyEvent_playing_pause() throws Exception {
|
||||
Assume.assumeTrue(Util.SDK_INT >= 21); // TODO: b/199064299 - Lower minSdk to 19.
|
||||
handler.postAndSync(
|
||||
() -> {
|
||||
player.playWhenReady = true;
|
||||
@ -334,7 +315,6 @@ public class MediaSessionKeyEventTest {
|
||||
|
||||
@Test
|
||||
public void playPauseKeyEvent_doubleTapOnPlayPause_seekNext() throws Exception {
|
||||
Assume.assumeTrue(Util.SDK_INT >= 21); // TODO: b/199064299 - Lower minSdk to 19.
|
||||
handler.postAndSync(
|
||||
() -> {
|
||||
player.playWhenReady = true;
|
||||
@ -348,7 +328,6 @@ public class MediaSessionKeyEventTest {
|
||||
|
||||
@Test
|
||||
public void playPauseKeyEvent_doubleTapOnHeadsetHook_seekNext() throws Exception {
|
||||
Assume.assumeTrue(Util.SDK_INT >= 21); // TODO: b/199064299 - Lower minSdk to 19.
|
||||
handler.postAndSync(
|
||||
() -> {
|
||||
player.playWhenReady = true;
|
||||
@ -390,10 +369,8 @@ public class MediaSessionKeyEventTest {
|
||||
return SUPPORT_APP_PACKAGE_NAME;
|
||||
}
|
||||
// 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.
|
||||
// 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;
|
||||
} else if (Util.SDK_INT >= 24) {
|
||||
// API 24 - 27: KeyEvent from system service has the package name "android".
|
||||
|
@ -1014,8 +1014,7 @@ public class MediaSessionTest {
|
||||
* <p>Calling this method should only be required to test legacy behaviour.
|
||||
*/
|
||||
private static String getControllerCallerPackageName(ControllerInfo controllerInfo) {
|
||||
return (Util.SDK_INT < 21
|
||||
|| Util.SDK_INT > 23
|
||||
return (Util.SDK_INT > 23
|
||||
|| controllerInfo.getControllerVersion() != ControllerInfo.LEGACY_CONTROLLER_VERSION)
|
||||
? ApplicationProvider.getApplicationContext().getPackageName()
|
||||
: MediaSessionManager.RemoteUserInfo.LEGACY_CONTROLLER;
|
||||
|
@ -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.TestUtils.TIMEOUT_MS;
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static org.junit.Assume.assumeTrue;
|
||||
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
@ -28,7 +27,6 @@ import android.os.Bundle;
|
||||
import android.os.Process;
|
||||
import android.support.v4.media.session.MediaSessionCompat;
|
||||
import androidx.media3.common.MediaLibraryInfo;
|
||||
import androidx.media3.common.util.Util;
|
||||
import androidx.media3.test.session.common.HandlerThreadTestRule;
|
||||
import androidx.media3.test.session.common.MainLooperTestRule;
|
||||
import androidx.media3.test.session.common.TestUtils;
|
||||
@ -117,8 +115,6 @@ public class SessionTokenTest {
|
||||
@Test
|
||||
public void createSessionToken_withPlatformTokenFromMedia1Session_returnsTokenForLegacySession()
|
||||
throws Exception {
|
||||
assumeTrue(Util.SDK_INT >= 21);
|
||||
|
||||
MediaSessionCompat sessionCompat =
|
||||
sessionTestRule.ensureReleaseAfterTest(
|
||||
new MediaSessionCompat(context, "createSessionToken_withLegacyToken"));
|
||||
|
@ -29,7 +29,6 @@ import android.system.ErrnoException;
|
||||
import android.system.OsConstants;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.media3.common.util.UnstableApi;
|
||||
import androidx.media3.common.util.Util;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
@ -148,8 +147,7 @@ public final class AssetContentProvider extends ContentProvider
|
||||
}
|
||||
|
||||
private static boolean isBrokenPipe(IOException e) {
|
||||
return Util.SDK_INT >= 21
|
||||
&& e.getCause() instanceof ErrnoException
|
||||
return e.getCause() instanceof ErrnoException
|
||||
&& ((ErrnoException) e.getCause()).errno == OsConstants.EPIPE;
|
||||
}
|
||||
}
|
||||
|
@ -331,7 +331,6 @@ public class CapturingRenderersFactory implements RenderersFactory, Dumper.Dumpa
|
||||
dequeuedOutputBuffers.delete(index);
|
||||
}
|
||||
|
||||
@RequiresApi(21)
|
||||
@Override
|
||||
public void releaseOutputBuffer(int index, long renderTimeStampNs) {
|
||||
MediaCodec.BufferInfo bufferInfo = checkNotNull(dequeuedOutputBuffers.get(index));
|
||||
|
@ -204,9 +204,6 @@ public final class DecodeOneFrameUtil {
|
||||
*/
|
||||
@Nullable
|
||||
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().
|
||||
MediaCodecList mediaCodecList = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
|
||||
// Format must not include KEY_FRAME_RATE on API21.
|
||||
|
@ -24,7 +24,6 @@ import android.content.Context;
|
||||
import android.media.Image;
|
||||
import android.media.MediaCodec;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.RequiresApi;
|
||||
import androidx.media3.common.util.UnstableApi;
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
@ -43,7 +42,6 @@ import java.nio.ByteBuffer;
|
||||
* channel (Y') because the {@linkplain MediaCodec decoder} decodes to luma.
|
||||
*/
|
||||
@UnstableApi
|
||||
@RequiresApi(21)
|
||||
public final class SsimHelper {
|
||||
|
||||
/** The default comparison interval. */
|
||||
|
@ -32,7 +32,6 @@ import android.media.MediaFormat;
|
||||
import android.net.Uri;
|
||||
import android.os.Handler;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.RequiresApi;
|
||||
import androidx.media3.common.Format;
|
||||
import androidx.media3.common.MimeTypes;
|
||||
import androidx.media3.common.util.ConditionVariable;
|
||||
@ -55,7 +54,6 @@ import java.util.List;
|
||||
* ImageReader} for use in CPU test utility functions.
|
||||
*/
|
||||
@UnstableApi
|
||||
@RequiresApi(21)
|
||||
public final class VideoDecodingWrapper implements AutoCloseable {
|
||||
|
||||
private static final String TAG = "VideoDecodingWrapper";
|
||||
|
@ -25,7 +25,6 @@ import android.media.MediaFormat;
|
||||
import androidx.media3.common.C;
|
||||
import androidx.media3.common.MimeTypes;
|
||||
import androidx.media3.common.util.UnstableApi;
|
||||
import androidx.media3.common.util.Util;
|
||||
import androidx.media3.exoplayer.mediacodec.MediaCodecUtil;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
@ -77,10 +76,6 @@ public final class ShadowMediaCodecConfig extends ExternalResource {
|
||||
|
||||
@Override
|
||||
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);
|
||||
}
|
||||
|
||||
@ -88,10 +83,6 @@ public final class ShadowMediaCodecConfig extends ExternalResource {
|
||||
protected void after() {
|
||||
supportedMimeTypes.clear();
|
||||
MediaCodecUtil.clearDecoderInfoCache();
|
||||
if (Util.SDK_INT == 19) {
|
||||
// Codec config not supported with Robolectric on API == 19. Skip rule tear down step.
|
||||
return;
|
||||
}
|
||||
ShadowMediaCodecList.reset();
|
||||
ShadowMediaCodec.clearCodecs();
|
||||
}
|
||||
|
@ -23,9 +23,7 @@ import android.view.accessibility.CaptioningManager;
|
||||
import android.view.accessibility.CaptioningManager.CaptionStyle;
|
||||
import androidx.annotation.IntDef;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.RequiresApi;
|
||||
import androidx.media3.common.util.UnstableApi;
|
||||
import androidx.media3.common.util.Util;
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
@ -116,17 +114,13 @@ public final class CaptionStyleCompat {
|
||||
*/
|
||||
public static CaptionStyleCompat createFromCaptionStyle(
|
||||
CaptioningManager.CaptionStyle captionStyle) {
|
||||
if (Util.SDK_INT >= 21) {
|
||||
return createFromCaptionStyleV21(captionStyle);
|
||||
} else {
|
||||
return new CaptionStyleCompat(
|
||||
captionStyle.foregroundColor,
|
||||
captionStyle.backgroundColor,
|
||||
Color.TRANSPARENT,
|
||||
captionStyle.edgeType,
|
||||
captionStyle.edgeColor,
|
||||
captionStyle.getTypeface());
|
||||
}
|
||||
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());
|
||||
}
|
||||
|
||||
/**
|
||||
@ -151,17 +145,4 @@ public final class CaptionStyleCompat {
|
||||
this.edgeColor = edgeColor;
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
@ -759,13 +759,8 @@ public class DefaultTimeBar extends View implements TimeBar {
|
||||
if (duration <= 0) {
|
||||
return;
|
||||
}
|
||||
if (Util.SDK_INT >= 21) {
|
||||
info.addAction(AccessibilityAction.ACTION_SCROLL_FORWARD);
|
||||
info.addAction(AccessibilityAction.ACTION_SCROLL_BACKWARD);
|
||||
} else {
|
||||
info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD);
|
||||
info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD);
|
||||
}
|
||||
info.addAction(AccessibilityAction.ACTION_SCROLL_FORWARD);
|
||||
info.addAction(AccessibilityAction.ACTION_SCROLL_BACKWARD);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -115,8 +115,7 @@ public class DefaultTrackNameProvider implements TrackNameProvider {
|
||||
if (TextUtils.isEmpty(language) || C.LANGUAGE_UNDETERMINED.equals(language)) {
|
||||
return "";
|
||||
}
|
||||
Locale languageLocale =
|
||||
Util.SDK_INT >= 21 ? Locale.forLanguageTag(language) : new Locale(language);
|
||||
Locale languageLocale = Locale.forLanguageTag(language);
|
||||
Locale displayLocale = Util.getDefaultDisplayLocale();
|
||||
String languageName = languageLocale.getDisplayName(displayLocale);
|
||||
if (TextUtils.isEmpty(languageName)) {
|
||||
|
@ -47,9 +47,7 @@ import android.view.accessibility.AccessibilityEvent;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
import androidx.annotation.DoNotInline;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.RequiresApi;
|
||||
import androidx.media3.common.C;
|
||||
import androidx.media3.common.MediaLibraryInfo;
|
||||
import androidx.media3.common.Player;
|
||||
@ -857,19 +855,15 @@ public class LegacyPlayerControlView extends FrameLayout {
|
||||
boolean requestPlayPauseAccessibilityFocus = false;
|
||||
boolean shouldShowPlayButton = Util.shouldShowPlayButton(player, showPlayButtonIfSuppressed);
|
||||
if (playButton != null) {
|
||||
requestPlayPauseFocus |= !shouldShowPlayButton && playButton.isFocused();
|
||||
requestPlayPauseAccessibilityFocus |=
|
||||
Util.SDK_INT < 21
|
||||
? requestPlayPauseFocus
|
||||
: (!shouldShowPlayButton && Api21.isAccessibilityFocused(playButton));
|
||||
requestPlayPauseFocus = !shouldShowPlayButton && playButton.isFocused();
|
||||
requestPlayPauseAccessibilityFocus =
|
||||
(!shouldShowPlayButton && playButton.isAccessibilityFocused());
|
||||
playButton.setVisibility(shouldShowPlayButton ? VISIBLE : GONE);
|
||||
}
|
||||
if (pauseButton != null) {
|
||||
requestPlayPauseFocus |= shouldShowPlayButton && pauseButton.isFocused();
|
||||
requestPlayPauseAccessibilityFocus |=
|
||||
Util.SDK_INT < 21
|
||||
? requestPlayPauseFocus
|
||||
: (shouldShowPlayButton && Api21.isAccessibilityFocused(pauseButton));
|
||||
shouldShowPlayButton && pauseButton.isAccessibilityFocused();
|
||||
pauseButton.setVisibility(shouldShowPlayButton ? GONE : VISIBLE);
|
||||
}
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -55,7 +55,6 @@ import androidx.annotation.DrawableRes;
|
||||
import androidx.annotation.IntDef;
|
||||
import androidx.annotation.IntRange;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.RequiresApi;
|
||||
import androidx.core.app.NotificationBuilderWithBuilderAccessor;
|
||||
import androidx.core.app.NotificationCompat;
|
||||
import androidx.core.app.NotificationManagerCompat;
|
||||
@ -1013,11 +1012,10 @@ public class PlayerNotificationManager {
|
||||
* @deprecated Use {@link #setMediaSessionToken(MediaSession.Token)} and pass in {@code
|
||||
* (MediaSession.Token) compatToken.getToken()}.
|
||||
*/
|
||||
// TODO: b/333355694 - Remove the dependency on androidx.media when this method is removed.
|
||||
@Deprecated
|
||||
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}.
|
||||
*/
|
||||
@RequiresApi(21)
|
||||
public final void setMediaSessionToken(MediaSession.Token token) {
|
||||
if (!Util.areEqual(this.mediaSessionToken, token)) {
|
||||
mediaSessionToken = token;
|
||||
@ -1247,8 +1244,7 @@ public class PlayerNotificationManager {
|
||||
* Creates the notification given the current player state.
|
||||
*
|
||||
* @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
|
||||
* builder when possible can prevent notification flicker when {@code Util#SDK_INT} < 21.
|
||||
* @param builder The builder used to build the last notification, or {@code null}.
|
||||
* @param ongoing Whether the notification should be ongoing.
|
||||
* @param largeIcon The large icon to be used.
|
||||
* @return The {@link NotificationCompat.Builder} on which to call {@link
|
||||
@ -1291,17 +1287,7 @@ public class PlayerNotificationManager {
|
||||
}
|
||||
|
||||
int[] actionIndicesForCompactView = getActionIndicesForCompactView(actionNames, player);
|
||||
if (Util.SDK_INT >= 21) {
|
||||
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);
|
||||
}
|
||||
builder.setStyle(new MediaStyle(mediaSessionToken, actionIndicesForCompactView));
|
||||
|
||||
// Set intent which is sent if the user selects 'clear all'
|
||||
builder.setDeleteIntent(dismissPendingIntent);
|
||||
@ -1317,9 +1303,7 @@ public class PlayerNotificationManager {
|
||||
.setPriority(priority)
|
||||
.setDefaults(defaults);
|
||||
|
||||
// Changing "showWhen" causes notification flicker if SDK_INT < 21.
|
||||
if (Util.SDK_INT >= 21
|
||||
&& useChronometer
|
||||
if (useChronometer
|
||||
&& player.isCommandAvailable(COMMAND_GET_CURRENT_MEDIA_ITEM)
|
||||
&& player.isPlaying()
|
||||
&& !player.isPlayingAd()
|
||||
@ -1628,7 +1612,6 @@ public class PlayerNotificationManager {
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresApi(21)
|
||||
private static final class MediaStyle extends androidx.core.app.NotificationCompat.Style {
|
||||
|
||||
private final int[] actionsToShowInCompact;
|
||||
|
Loading…
x
Reference in New Issue
Block a user