521 lines
22 KiB
Java
521 lines
22 KiB
Java
/*
|
|
* Decompiled with CFR 0.152.
|
|
*
|
|
* Could not load the following classes:
|
|
* com.google.common.collect.HashMultimap
|
|
* com.google.common.collect.Lists
|
|
* com.google.common.collect.Maps
|
|
* com.google.common.collect.Multimap
|
|
* com.google.common.collect.Sets
|
|
* com.mojang.logging.LogUtils
|
|
* it.unimi.dsi.fastutil.objects.Object2FloatMap
|
|
* it.unimi.dsi.fastutil.objects.Object2FloatOpenHashMap
|
|
* org.jspecify.annotations.Nullable
|
|
* org.slf4j.Logger
|
|
* org.slf4j.Marker
|
|
* org.slf4j.MarkerFactory
|
|
*/
|
|
package net.minecraft.client.sounds;
|
|
|
|
import com.google.common.collect.HashMultimap;
|
|
import com.google.common.collect.Lists;
|
|
import com.google.common.collect.Maps;
|
|
import com.google.common.collect.Multimap;
|
|
import com.google.common.collect.Sets;
|
|
import com.mojang.blaze3d.audio.Channel;
|
|
import com.mojang.blaze3d.audio.Library;
|
|
import com.mojang.blaze3d.audio.Listener;
|
|
import com.mojang.blaze3d.audio.ListenerTransform;
|
|
import com.mojang.blaze3d.audio.SoundBuffer;
|
|
import com.mojang.logging.LogUtils;
|
|
import it.unimi.dsi.fastutil.objects.Object2FloatMap;
|
|
import it.unimi.dsi.fastutil.objects.Object2FloatOpenHashMap;
|
|
import java.util.Iterator;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
import java.util.Set;
|
|
import java.util.concurrent.CompletableFuture;
|
|
import java.util.concurrent.atomic.AtomicReference;
|
|
import net.minecraft.SharedConstants;
|
|
import net.minecraft.client.Camera;
|
|
import net.minecraft.client.Options;
|
|
import net.minecraft.client.resources.sounds.Sound;
|
|
import net.minecraft.client.resources.sounds.SoundInstance;
|
|
import net.minecraft.client.resources.sounds.TickableSoundInstance;
|
|
import net.minecraft.client.sounds.AudioStream;
|
|
import net.minecraft.client.sounds.ChannelAccess;
|
|
import net.minecraft.client.sounds.SoundBufferLibrary;
|
|
import net.minecraft.client.sounds.SoundEngineExecutor;
|
|
import net.minecraft.client.sounds.SoundEventListener;
|
|
import net.minecraft.client.sounds.SoundManager;
|
|
import net.minecraft.client.sounds.WeighedSoundEvents;
|
|
import net.minecraft.core.registries.BuiltInRegistries;
|
|
import net.minecraft.resources.Identifier;
|
|
import net.minecraft.server.packs.resources.ResourceProvider;
|
|
import net.minecraft.sounds.SoundEvent;
|
|
import net.minecraft.sounds.SoundEvents;
|
|
import net.minecraft.sounds.SoundSource;
|
|
import net.minecraft.util.Mth;
|
|
import net.minecraft.util.Util;
|
|
import net.minecraft.world.phys.Vec3;
|
|
import org.jspecify.annotations.Nullable;
|
|
import org.slf4j.Logger;
|
|
import org.slf4j.Marker;
|
|
import org.slf4j.MarkerFactory;
|
|
|
|
public class SoundEngine {
|
|
private static final Marker MARKER = MarkerFactory.getMarker((String)"SOUNDS");
|
|
private static final Logger LOGGER = LogUtils.getLogger();
|
|
private static final float PITCH_MIN = 0.5f;
|
|
private static final float PITCH_MAX = 2.0f;
|
|
private static final float VOLUME_MIN = 0.0f;
|
|
private static final float VOLUME_MAX = 1.0f;
|
|
private static final int MIN_SOURCE_LIFETIME = 20;
|
|
private static final Set<Identifier> ONLY_WARN_ONCE = Sets.newHashSet();
|
|
private static final long DEFAULT_DEVICE_CHECK_INTERVAL_MS = 1000L;
|
|
public static final String MISSING_SOUND = "FOR THE DEBUG!";
|
|
public static final String OPEN_AL_SOFT_PREFIX = "OpenAL Soft on ";
|
|
public static final int OPEN_AL_SOFT_PREFIX_LENGTH = "OpenAL Soft on ".length();
|
|
private final SoundManager soundManager;
|
|
private final Options options;
|
|
private boolean loaded;
|
|
private final Library library = new Library();
|
|
private final Listener listener = this.library.getListener();
|
|
private final SoundBufferLibrary soundBuffers;
|
|
private final SoundEngineExecutor executor = new SoundEngineExecutor();
|
|
private final ChannelAccess channelAccess = new ChannelAccess(this.library, this.executor);
|
|
private int tickCount;
|
|
private long lastDeviceCheckTime;
|
|
private final AtomicReference<DeviceCheckState> devicePoolState = new AtomicReference<DeviceCheckState>(DeviceCheckState.NO_CHANGE);
|
|
private final Map<SoundInstance, ChannelAccess.ChannelHandle> instanceToChannel = Maps.newHashMap();
|
|
private final Multimap<SoundSource, SoundInstance> instanceBySource = HashMultimap.create();
|
|
private final Object2FloatMap<SoundSource> gainBySource = (Object2FloatMap)Util.make(new Object2FloatOpenHashMap(), map -> map.defaultReturnValue(1.0f));
|
|
private final List<TickableSoundInstance> tickingSounds = Lists.newArrayList();
|
|
private final Map<SoundInstance, Integer> queuedSounds = Maps.newHashMap();
|
|
private final Map<SoundInstance, Integer> soundDeleteTime = Maps.newHashMap();
|
|
private final List<SoundEventListener> listeners = Lists.newArrayList();
|
|
private final List<TickableSoundInstance> queuedTickableSounds = Lists.newArrayList();
|
|
private final List<Sound> preloadQueue = Lists.newArrayList();
|
|
|
|
public SoundEngine(SoundManager soundManager, Options options, ResourceProvider resourceProvider) {
|
|
this.soundManager = soundManager;
|
|
this.options = options;
|
|
this.soundBuffers = new SoundBufferLibrary(resourceProvider);
|
|
}
|
|
|
|
public void reload() {
|
|
ONLY_WARN_ONCE.clear();
|
|
for (SoundEvent sound : BuiltInRegistries.SOUND_EVENT) {
|
|
Identifier location;
|
|
if (sound == SoundEvents.EMPTY || this.soundManager.getSoundEvent(location = sound.location()) != null) continue;
|
|
LOGGER.warn("Missing sound for event: {}", (Object)BuiltInRegistries.SOUND_EVENT.getKey(sound));
|
|
ONLY_WARN_ONCE.add(location);
|
|
}
|
|
this.destroy();
|
|
this.loadLibrary();
|
|
}
|
|
|
|
private synchronized void loadLibrary() {
|
|
if (this.loaded) {
|
|
return;
|
|
}
|
|
try {
|
|
String soundDevice = this.options.soundDevice().get();
|
|
this.library.init("".equals(soundDevice) ? null : soundDevice, this.options.directionalAudio().get());
|
|
this.listener.reset();
|
|
this.soundBuffers.preload(this.preloadQueue).thenRun(this.preloadQueue::clear);
|
|
this.loaded = true;
|
|
LOGGER.info(MARKER, "Sound engine started");
|
|
}
|
|
catch (RuntimeException e) {
|
|
LOGGER.error(MARKER, "Error starting SoundSystem. Turning off sounds & music", (Throwable)e);
|
|
}
|
|
}
|
|
|
|
public void refreshCategoryVolume(SoundSource source) {
|
|
if (!this.loaded) {
|
|
return;
|
|
}
|
|
this.instanceToChannel.forEach((soundInstance, channelHandle) -> {
|
|
if (source == soundInstance.getSource() || source == SoundSource.MASTER) {
|
|
float newVolume = this.calculateVolume((SoundInstance)soundInstance);
|
|
channelHandle.execute(channel -> channel.setVolume(newVolume));
|
|
}
|
|
});
|
|
}
|
|
|
|
public void destroy() {
|
|
if (this.loaded) {
|
|
this.stopAll();
|
|
this.soundBuffers.clear();
|
|
this.library.cleanup();
|
|
this.loaded = false;
|
|
}
|
|
}
|
|
|
|
public void emergencyShutdown() {
|
|
if (this.loaded) {
|
|
this.library.cleanup();
|
|
}
|
|
}
|
|
|
|
public void stop(SoundInstance soundInstance) {
|
|
ChannelAccess.ChannelHandle handle;
|
|
if (this.loaded && (handle = this.instanceToChannel.get(soundInstance)) != null) {
|
|
handle.execute(Channel::stop);
|
|
}
|
|
}
|
|
|
|
public void updateCategoryVolume(SoundSource source, float gain) {
|
|
this.gainBySource.put((Object)source, Mth.clamp(gain, 0.0f, 1.0f));
|
|
this.refreshCategoryVolume(source);
|
|
}
|
|
|
|
public void stopAll() {
|
|
if (this.loaded) {
|
|
this.executor.shutDown();
|
|
this.instanceToChannel.clear();
|
|
this.channelAccess.clear();
|
|
this.queuedSounds.clear();
|
|
this.tickingSounds.clear();
|
|
this.instanceBySource.clear();
|
|
this.soundDeleteTime.clear();
|
|
this.queuedTickableSounds.clear();
|
|
this.gainBySource.clear();
|
|
this.executor.startUp();
|
|
}
|
|
}
|
|
|
|
public void addEventListener(SoundEventListener listener) {
|
|
this.listeners.add(listener);
|
|
}
|
|
|
|
public void removeEventListener(SoundEventListener listener) {
|
|
this.listeners.remove(listener);
|
|
}
|
|
|
|
private boolean shouldChangeDevice() {
|
|
boolean doExpensiveChecks;
|
|
if (this.library.isCurrentDeviceDisconnected()) {
|
|
LOGGER.info("Audio device was lost!");
|
|
return true;
|
|
}
|
|
long now = Util.getMillis();
|
|
boolean bl = doExpensiveChecks = now - this.lastDeviceCheckTime >= 1000L;
|
|
if (doExpensiveChecks) {
|
|
this.lastDeviceCheckTime = now;
|
|
if (this.devicePoolState.compareAndSet(DeviceCheckState.NO_CHANGE, DeviceCheckState.ONGOING)) {
|
|
String currentDevice = this.options.soundDevice().get();
|
|
Util.ioPool().execute(() -> {
|
|
if ("".equals(currentDevice)) {
|
|
if (this.library.hasDefaultDeviceChanged()) {
|
|
LOGGER.info("System default audio device has changed!");
|
|
this.devicePoolState.compareAndSet(DeviceCheckState.ONGOING, DeviceCheckState.CHANGE_DETECTED);
|
|
}
|
|
} else if (!this.library.getCurrentDeviceName().equals(currentDevice) && this.library.getAvailableSoundDevices().contains(currentDevice)) {
|
|
LOGGER.info("Preferred audio device has become available!");
|
|
this.devicePoolState.compareAndSet(DeviceCheckState.ONGOING, DeviceCheckState.CHANGE_DETECTED);
|
|
}
|
|
this.devicePoolState.compareAndSet(DeviceCheckState.ONGOING, DeviceCheckState.NO_CHANGE);
|
|
});
|
|
}
|
|
}
|
|
return this.devicePoolState.compareAndSet(DeviceCheckState.CHANGE_DETECTED, DeviceCheckState.NO_CHANGE);
|
|
}
|
|
|
|
public void tick(boolean paused) {
|
|
if (this.shouldChangeDevice()) {
|
|
this.reload();
|
|
}
|
|
if (!paused) {
|
|
this.tickInGameSound();
|
|
} else {
|
|
this.tickMusicWhenPaused();
|
|
}
|
|
this.channelAccess.scheduleTick();
|
|
}
|
|
|
|
private void tickInGameSound() {
|
|
++this.tickCount;
|
|
this.queuedTickableSounds.stream().filter(SoundInstance::canPlaySound).forEach(this::play);
|
|
this.queuedTickableSounds.clear();
|
|
for (TickableSoundInstance instance : this.tickingSounds) {
|
|
if (!instance.canPlaySound()) {
|
|
this.stop(instance);
|
|
}
|
|
instance.tick();
|
|
if (instance.isStopped()) {
|
|
this.stop(instance);
|
|
continue;
|
|
}
|
|
float volume = this.calculateVolume(instance);
|
|
float pitch = this.calculatePitch(instance);
|
|
Vec3 position = new Vec3(instance.getX(), instance.getY(), instance.getZ());
|
|
ChannelAccess.ChannelHandle handle = this.instanceToChannel.get(instance);
|
|
if (handle == null) continue;
|
|
handle.execute(channel -> {
|
|
channel.setVolume(volume);
|
|
channel.setPitch(pitch);
|
|
channel.setSelfPosition(position);
|
|
});
|
|
}
|
|
Iterator<Map.Entry<SoundInstance, ChannelAccess.ChannelHandle>> iterator = this.instanceToChannel.entrySet().iterator();
|
|
while (iterator.hasNext()) {
|
|
int minDeleteTime;
|
|
Map.Entry<SoundInstance, ChannelAccess.ChannelHandle> entry = iterator.next();
|
|
ChannelAccess.ChannelHandle handle = entry.getValue();
|
|
SoundInstance instance = entry.getKey();
|
|
if (!handle.isStopped() || (minDeleteTime = this.soundDeleteTime.get(instance).intValue()) > this.tickCount) continue;
|
|
if (SoundEngine.shouldLoopManually(instance)) {
|
|
this.queuedSounds.put(instance, this.tickCount + instance.getDelay());
|
|
}
|
|
iterator.remove();
|
|
LOGGER.debug(MARKER, "Removed channel {} because it's not playing anymore", (Object)handle);
|
|
this.soundDeleteTime.remove(instance);
|
|
try {
|
|
this.instanceBySource.remove((Object)instance.getSource(), (Object)instance);
|
|
}
|
|
catch (RuntimeException runtimeException) {
|
|
// empty catch block
|
|
}
|
|
if (!(instance instanceof TickableSoundInstance)) continue;
|
|
this.tickingSounds.remove(instance);
|
|
}
|
|
Iterator<Map.Entry<SoundInstance, Integer>> queueIterator = this.queuedSounds.entrySet().iterator();
|
|
while (queueIterator.hasNext()) {
|
|
Map.Entry<SoundInstance, Integer> next = queueIterator.next();
|
|
if (this.tickCount < next.getValue()) continue;
|
|
SoundInstance instance = next.getKey();
|
|
if (instance instanceof TickableSoundInstance) {
|
|
((TickableSoundInstance)instance).tick();
|
|
}
|
|
this.play(instance);
|
|
queueIterator.remove();
|
|
}
|
|
}
|
|
|
|
private void tickMusicWhenPaused() {
|
|
Iterator<Map.Entry<SoundInstance, ChannelAccess.ChannelHandle>> iterator = this.instanceToChannel.entrySet().iterator();
|
|
while (iterator.hasNext()) {
|
|
Map.Entry<SoundInstance, ChannelAccess.ChannelHandle> entry = iterator.next();
|
|
ChannelAccess.ChannelHandle handle = entry.getValue();
|
|
SoundInstance instance = entry.getKey();
|
|
if (instance.getSource() != SoundSource.MUSIC || !handle.isStopped()) continue;
|
|
iterator.remove();
|
|
LOGGER.debug(MARKER, "Removed channel {} because it's not playing anymore", (Object)handle);
|
|
this.soundDeleteTime.remove(instance);
|
|
this.instanceBySource.remove((Object)instance.getSource(), (Object)instance);
|
|
}
|
|
}
|
|
|
|
private static boolean requiresManualLooping(SoundInstance instance) {
|
|
return instance.getDelay() > 0;
|
|
}
|
|
|
|
private static boolean shouldLoopManually(SoundInstance instance) {
|
|
return instance.isLooping() && SoundEngine.requiresManualLooping(instance);
|
|
}
|
|
|
|
private static boolean shouldLoopAutomatically(SoundInstance instance) {
|
|
return instance.isLooping() && !SoundEngine.requiresManualLooping(instance);
|
|
}
|
|
|
|
public boolean isActive(SoundInstance instance) {
|
|
if (!this.loaded) {
|
|
return false;
|
|
}
|
|
if (this.soundDeleteTime.containsKey(instance) && this.soundDeleteTime.get(instance) <= this.tickCount) {
|
|
return true;
|
|
}
|
|
return this.instanceToChannel.containsKey(instance);
|
|
}
|
|
|
|
public PlayResult play(SoundInstance instance) {
|
|
Sound sound;
|
|
if (!this.loaded) {
|
|
return PlayResult.NOT_STARTED;
|
|
}
|
|
if (!instance.canPlaySound()) {
|
|
return PlayResult.NOT_STARTED;
|
|
}
|
|
WeighedSoundEvents soundEvent = instance.resolve(this.soundManager);
|
|
Identifier eventLocation = instance.getIdentifier();
|
|
if (soundEvent == null) {
|
|
if (ONLY_WARN_ONCE.add(eventLocation)) {
|
|
LOGGER.warn(MARKER, "Unable to play unknown soundEvent: {}", (Object)eventLocation);
|
|
}
|
|
if (!SharedConstants.DEBUG_SUBTITLES) {
|
|
return PlayResult.NOT_STARTED;
|
|
}
|
|
soundEvent = new WeighedSoundEvents(eventLocation, MISSING_SOUND);
|
|
}
|
|
if ((sound = instance.getSound()) == SoundManager.INTENTIONALLY_EMPTY_SOUND) {
|
|
return PlayResult.NOT_STARTED;
|
|
}
|
|
if (sound == SoundManager.EMPTY_SOUND) {
|
|
if (ONLY_WARN_ONCE.add(eventLocation)) {
|
|
LOGGER.warn(MARKER, "Unable to play empty soundEvent: {}", (Object)eventLocation);
|
|
}
|
|
return PlayResult.NOT_STARTED;
|
|
}
|
|
float instanceVolume = instance.getVolume();
|
|
float attenuationDistance = Math.max(instanceVolume, 1.0f) * (float)sound.getAttenuationDistance();
|
|
SoundSource soundSource = instance.getSource();
|
|
float volume = this.calculateVolume(instanceVolume, soundSource);
|
|
float pitch = this.calculatePitch(instance);
|
|
SoundInstance.Attenuation attenuation = instance.getAttenuation();
|
|
boolean isRelative = instance.isRelative();
|
|
if (!this.listeners.isEmpty()) {
|
|
float range = isRelative || attenuation == SoundInstance.Attenuation.NONE ? Float.POSITIVE_INFINITY : attenuationDistance;
|
|
for (SoundEventListener listener : this.listeners) {
|
|
listener.onPlaySound(instance, soundEvent, range);
|
|
}
|
|
}
|
|
boolean startedSilently = false;
|
|
if (volume == 0.0f) {
|
|
if (instance.canStartSilent() || soundSource == SoundSource.MUSIC) {
|
|
startedSilently = true;
|
|
} else {
|
|
LOGGER.debug(MARKER, "Skipped playing sound {}, volume was zero.", (Object)sound.getLocation());
|
|
return PlayResult.NOT_STARTED;
|
|
}
|
|
}
|
|
Vec3 position = new Vec3(instance.getX(), instance.getY(), instance.getZ());
|
|
boolean isLooping = SoundEngine.shouldLoopAutomatically(instance);
|
|
boolean isStreaming = sound.shouldStream();
|
|
CompletableFuture<@Nullable ChannelAccess.ChannelHandle> handleFuture = this.channelAccess.createHandle(sound.shouldStream() ? Library.Pool.STREAMING : Library.Pool.STATIC);
|
|
ChannelAccess.ChannelHandle handle = handleFuture.join();
|
|
if (handle == null) {
|
|
if (SharedConstants.IS_RUNNING_IN_IDE) {
|
|
LOGGER.warn("Failed to create new sound handle");
|
|
}
|
|
return PlayResult.NOT_STARTED;
|
|
}
|
|
LOGGER.debug(MARKER, "Playing sound {} for event {}", (Object)sound.getLocation(), (Object)eventLocation);
|
|
this.soundDeleteTime.put(instance, this.tickCount + 20);
|
|
this.instanceToChannel.put(instance, handle);
|
|
this.instanceBySource.put((Object)soundSource, (Object)instance);
|
|
handle.execute(channel -> {
|
|
channel.setPitch(pitch);
|
|
channel.setVolume(volume);
|
|
if (attenuation == SoundInstance.Attenuation.LINEAR) {
|
|
channel.linearAttenuation(attenuationDistance);
|
|
} else {
|
|
channel.disableAttenuation();
|
|
}
|
|
channel.setLooping(isLooping && !isStreaming);
|
|
channel.setSelfPosition(position);
|
|
channel.setRelative(isRelative);
|
|
});
|
|
if (!isStreaming) {
|
|
this.soundBuffers.getCompleteBuffer(sound.getPath()).thenAccept(soundBuffer -> handle.execute(channel -> {
|
|
channel.attachStaticBuffer((SoundBuffer)soundBuffer);
|
|
channel.play();
|
|
}));
|
|
} else {
|
|
this.soundBuffers.getStream(sound.getPath(), isLooping).thenAccept(stream -> handle.execute(channel -> {
|
|
channel.attachBufferStream((AudioStream)stream);
|
|
channel.play();
|
|
}));
|
|
}
|
|
if (instance instanceof TickableSoundInstance) {
|
|
this.tickingSounds.add((TickableSoundInstance)instance);
|
|
}
|
|
if (startedSilently) {
|
|
return PlayResult.STARTED_SILENTLY;
|
|
}
|
|
return PlayResult.STARTED;
|
|
}
|
|
|
|
public void queueTickingSound(TickableSoundInstance tickableSoundInstance) {
|
|
this.queuedTickableSounds.add(tickableSoundInstance);
|
|
}
|
|
|
|
public void requestPreload(Sound sound) {
|
|
this.preloadQueue.add(sound);
|
|
}
|
|
|
|
private float calculatePitch(SoundInstance instance) {
|
|
return Mth.clamp(instance.getPitch(), 0.5f, 2.0f);
|
|
}
|
|
|
|
private float calculateVolume(SoundInstance instance) {
|
|
return this.calculateVolume(instance.getVolume(), instance.getSource());
|
|
}
|
|
|
|
private float calculateVolume(float volume, SoundSource source) {
|
|
return Mth.clamp(volume, 0.0f, 1.0f) * Mth.clamp(this.options.getFinalSoundSourceVolume(source), 0.0f, 1.0f) * this.gainBySource.getFloat((Object)source);
|
|
}
|
|
|
|
public void pauseAllExcept(SoundSource ... ignoredSources) {
|
|
if (!this.loaded) {
|
|
return;
|
|
}
|
|
for (Map.Entry<SoundInstance, ChannelAccess.ChannelHandle> instance : this.instanceToChannel.entrySet()) {
|
|
if (List.of(ignoredSources).contains((Object)instance.getKey().getSource())) continue;
|
|
instance.getValue().execute(Channel::pause);
|
|
}
|
|
}
|
|
|
|
public void resume() {
|
|
if (this.loaded) {
|
|
this.channelAccess.executeOnChannels(channels -> channels.forEach(Channel::unpause));
|
|
}
|
|
}
|
|
|
|
public void playDelayed(SoundInstance instance, int delay) {
|
|
this.queuedSounds.put(instance, this.tickCount + delay);
|
|
}
|
|
|
|
public void updateSource(Camera camera) {
|
|
if (!this.loaded || !camera.isInitialized()) {
|
|
return;
|
|
}
|
|
ListenerTransform transform = new ListenerTransform(camera.position(), new Vec3(camera.forwardVector()), new Vec3(camera.upVector()));
|
|
this.executor.execute(() -> this.listener.setTransform(transform));
|
|
}
|
|
|
|
public void stop(@Nullable Identifier sound, @Nullable SoundSource source) {
|
|
if (source != null) {
|
|
for (SoundInstance instance : this.instanceBySource.get((Object)source)) {
|
|
if (sound != null && !instance.getIdentifier().equals(sound)) continue;
|
|
this.stop(instance);
|
|
}
|
|
} else if (sound == null) {
|
|
this.stopAll();
|
|
} else {
|
|
for (SoundInstance instance : this.instanceToChannel.keySet()) {
|
|
if (!instance.getIdentifier().equals(sound)) continue;
|
|
this.stop(instance);
|
|
}
|
|
}
|
|
}
|
|
|
|
public String getDebugString() {
|
|
return this.library.getDebugString();
|
|
}
|
|
|
|
public List<String> getAvailableSoundDevices() {
|
|
return this.library.getAvailableSoundDevices();
|
|
}
|
|
|
|
public ListenerTransform getListenerTransform() {
|
|
return this.listener.getTransform();
|
|
}
|
|
|
|
private static enum DeviceCheckState {
|
|
ONGOING,
|
|
CHANGE_DETECTED,
|
|
NO_CHANGE;
|
|
|
|
}
|
|
|
|
public static enum PlayResult {
|
|
STARTED,
|
|
STARTED_SILENTLY,
|
|
NOT_STARTED;
|
|
|
|
}
|
|
}
|
|
|