mirror of
https://github.com/androidx/media.git
synced 2025-04-30 06:46:50 +08:00
Load the Spatializer API with reflection
This change adds a delegate class that loads and forwards calls to a Spatializer with reflection, so that we can use the Spatializer API before we update the compile SDK target to 32. PiperOrigin-RevId: 416027289
This commit is contained in:
parent
a4f67ccf6a
commit
d90f5f37ef
@ -0,0 +1,217 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package androidx.media3.exoplayer.audio;
|
||||||
|
|
||||||
|
import static androidx.media3.common.util.Assertions.checkStateNotNull;
|
||||||
|
import static java.lang.annotation.ElementType.FIELD;
|
||||||
|
import static java.lang.annotation.ElementType.LOCAL_VARIABLE;
|
||||||
|
import static java.lang.annotation.ElementType.METHOD;
|
||||||
|
import static java.lang.annotation.ElementType.PARAMETER;
|
||||||
|
import static java.lang.annotation.ElementType.TYPE_USE;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.media.AudioAttributes;
|
||||||
|
import android.media.AudioFormat;
|
||||||
|
import android.media.AudioManager;
|
||||||
|
import androidx.annotation.IntDef;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.annotation.RequiresApi;
|
||||||
|
import androidx.media3.common.util.Assertions;
|
||||||
|
import androidx.media3.common.util.Util;
|
||||||
|
import java.lang.annotation.Documented;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
|
import java.lang.reflect.InvocationHandler;
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.lang.reflect.Proxy;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exposes the android.media.Spatializer API via reflection. This is so that we can use the
|
||||||
|
* Spatializer while the compile SDK target is set to 31.
|
||||||
|
*/
|
||||||
|
@RequiresApi(31)
|
||||||
|
/* package */ final class SpatializerDelegate {
|
||||||
|
/** Level of support for audio spatialization. */
|
||||||
|
@Documented
|
||||||
|
@Retention(RetentionPolicy.SOURCE)
|
||||||
|
@Target({FIELD, METHOD, PARAMETER, LOCAL_VARIABLE, TYPE_USE})
|
||||||
|
@IntDef({
|
||||||
|
SPATIALIZER_IMMERSIVE_LEVEL_MULTICHANNEL,
|
||||||
|
SPATIALIZER_IMMERSIVE_LEVEL_NONE,
|
||||||
|
SPATIALIZER_IMMERSIVE_LEVEL_OTHER
|
||||||
|
})
|
||||||
|
@interface ImmersiveAudioLevel {}
|
||||||
|
|
||||||
|
/** See Spatializer#SPATIALIZER_IMMERSIVE_LEVEL_MULTICHANNEL */
|
||||||
|
public static final int SPATIALIZER_IMMERSIVE_LEVEL_MULTICHANNEL = 1;
|
||||||
|
/** See Spatializer#SPATIALIZER_IMMERSIVE_LEVEL_NONE */
|
||||||
|
public static final int SPATIALIZER_IMMERSIVE_LEVEL_NONE = 0;
|
||||||
|
/** See Spatializer#SPATIALIZER_IMMERSIVE_LEVEL_OTHER */
|
||||||
|
public static final int SPATIALIZER_IMMERSIVE_LEVEL_OTHER = -1;
|
||||||
|
|
||||||
|
/** Wrapper for Spatializer.OnSpatializerStateChangedListener */
|
||||||
|
public interface Listener {
|
||||||
|
/** See Spatializer.OnSpatializerStateChangedListener.onSpatializerEnabledChanged */
|
||||||
|
void onSpatializerEnabledChanged(SpatializerDelegate spatializer, boolean enabled);
|
||||||
|
|
||||||
|
/** See Spatializer.OnSpatializerStateChangedListener.onSpatializerAvailableChanged */
|
||||||
|
void onSpatializerAvailableChanged(SpatializerDelegate spatializer, boolean available);
|
||||||
|
}
|
||||||
|
|
||||||
|
private final Object spatializer;
|
||||||
|
private final Class<?> spatializerClass;
|
||||||
|
private final Class<?> spatializerListenerClass;
|
||||||
|
private final Method isEnabled;
|
||||||
|
private final Method isAvailable;
|
||||||
|
private final Method getImmersiveAudioLevel;
|
||||||
|
private final Method canBeSpatialized;
|
||||||
|
private final Method addListener;
|
||||||
|
private final Method removeListener;
|
||||||
|
private final Map<Listener, Object> listeners;
|
||||||
|
|
||||||
|
/** Creates an instance. */
|
||||||
|
public SpatializerDelegate(Context context)
|
||||||
|
throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException,
|
||||||
|
IllegalAccessException {
|
||||||
|
Method getSpatializerMethod = AudioManager.class.getMethod("getSpatializer");
|
||||||
|
AudioManager manager =
|
||||||
|
Assertions.checkNotNull(
|
||||||
|
(AudioManager) context.getApplicationContext().getSystemService(Context.AUDIO_SERVICE));
|
||||||
|
spatializer = checkStateNotNull(getSpatializerMethod.invoke(manager));
|
||||||
|
spatializerClass = Class.forName("android.media.Spatializer");
|
||||||
|
spatializerListenerClass =
|
||||||
|
Class.forName("android.media.Spatializer$OnSpatializerStateChangedListener");
|
||||||
|
isEnabled = spatializerClass.getMethod("isEnabled");
|
||||||
|
isAvailable = spatializerClass.getMethod("isAvailable");
|
||||||
|
getImmersiveAudioLevel = spatializerClass.getMethod("getImmersiveAudioLevel");
|
||||||
|
canBeSpatialized =
|
||||||
|
spatializerClass.getMethod(
|
||||||
|
"canBeSpatialized", android.media.AudioAttributes.class, AudioFormat.class);
|
||||||
|
addListener =
|
||||||
|
spatializerClass.getMethod(
|
||||||
|
"addOnSpatializerStateChangedListener", Executor.class, spatializerListenerClass);
|
||||||
|
removeListener =
|
||||||
|
spatializerClass.getMethod(
|
||||||
|
"removeOnSpatializerStateChangedListener", spatializerListenerClass);
|
||||||
|
listeners = new HashMap<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Delegates to Spatializer.isEnabled() */
|
||||||
|
public boolean isEnabled() {
|
||||||
|
try {
|
||||||
|
return (boolean) Util.castNonNull(isEnabled.invoke(spatializer));
|
||||||
|
} catch (IllegalAccessException | InvocationTargetException e) {
|
||||||
|
throw new IllegalStateException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Delegates to Spatializer.isAvailable() */
|
||||||
|
public boolean isAvailable() {
|
||||||
|
try {
|
||||||
|
return (boolean) Util.castNonNull(isAvailable.invoke(spatializer));
|
||||||
|
} catch (IllegalAccessException | InvocationTargetException e) {
|
||||||
|
throw new IllegalStateException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Delegates to Spatializer.getImmersiveAudioLevel() */
|
||||||
|
@ImmersiveAudioLevel
|
||||||
|
public int getImmersiveAudioLevel() {
|
||||||
|
try {
|
||||||
|
return (int) Util.castNonNull(getImmersiveAudioLevel.invoke(spatializer));
|
||||||
|
} catch (IllegalAccessException | InvocationTargetException e) {
|
||||||
|
throw new IllegalStateException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Delegates to Spatializer.canBeSpatialized() */
|
||||||
|
public boolean canBeSpatialized(AudioAttributes attributes, AudioFormat format) {
|
||||||
|
try {
|
||||||
|
return (boolean) Util.castNonNull(canBeSpatialized.invoke(spatializer, attributes, format));
|
||||||
|
} catch (IllegalAccessException | InvocationTargetException e) {
|
||||||
|
throw new IllegalStateException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Delegates to Spatializer.addOnSpatializerStateChangedListener() */
|
||||||
|
public void addOnSpatializerStateChangedListener(Executor executor, Listener listener) {
|
||||||
|
if (listeners.containsKey(listener)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Object listenerProxy = createSpatializerListenerProxy(listener);
|
||||||
|
try {
|
||||||
|
addListener.invoke(spatializer, executor, listenerProxy);
|
||||||
|
listeners.put(listener, listenerProxy);
|
||||||
|
} catch (IllegalAccessException | InvocationTargetException e) {
|
||||||
|
throw new IllegalStateException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Delegates to Spatializer.removeOnSpatializerStateChangedListener() */
|
||||||
|
public void removeOnSpatializerStateChangedListener(Listener listener) {
|
||||||
|
@Nullable Object proxy = listeners.get(listener);
|
||||||
|
if (proxy == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
removeListener.invoke(spatializer, proxy);
|
||||||
|
listeners.remove(listener);
|
||||||
|
} catch (IllegalAccessException | InvocationTargetException e) {
|
||||||
|
throw new IllegalStateException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Object createSpatializerListenerProxy(Listener listener) {
|
||||||
|
return Proxy.newProxyInstance(
|
||||||
|
spatializerListenerClass.getClassLoader(),
|
||||||
|
new Class<?>[] {spatializerListenerClass},
|
||||||
|
new ProxySpatializerListener(this, listener));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Proxy-based implementation of Spatializer.OnSpatializerStateChangedListener. */
|
||||||
|
private static final class ProxySpatializerListener implements InvocationHandler {
|
||||||
|
private final SpatializerDelegate spatializerDelegate;
|
||||||
|
private final Listener listener;
|
||||||
|
|
||||||
|
private ProxySpatializerListener(SpatializerDelegate spatializerDelegate, Listener listener) {
|
||||||
|
this.spatializerDelegate = spatializerDelegate;
|
||||||
|
this.listener = listener;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object invoke(Object o, Method method, Object[] objects) {
|
||||||
|
String methodName = method.getName();
|
||||||
|
Class<?>[] parameterTypes = method.getParameterTypes();
|
||||||
|
if (methodName.equals("onSpatializerAvailableChanged")
|
||||||
|
&& parameterTypes.length == 2
|
||||||
|
&& spatializerDelegate.spatializerClass.isAssignableFrom(parameterTypes[0])
|
||||||
|
&& parameterTypes[1].equals(Boolean.TYPE)) {
|
||||||
|
listener.onSpatializerAvailableChanged(spatializerDelegate, (boolean) objects[1]);
|
||||||
|
} else if (methodName.equals("onSpatializerEnabledChanged")
|
||||||
|
&& parameterTypes.length == 2
|
||||||
|
&& spatializerDelegate.spatializerClass.isAssignableFrom(parameterTypes[0])
|
||||||
|
&& parameterTypes[1].equals(Boolean.TYPE)) {
|
||||||
|
listener.onSpatializerEnabledChanged(spatializerDelegate, (boolean) objects[1]);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user