/*
* The MIT License (MIT)
*
* Copyright (c) 2014-2017 Sri Harsha Chilakapati
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package com.shc.silenceengine.backend.android;
import com.shc.androidopenal.AL;
import com.shc.androidopenal.ALC;
import com.shc.androidopenal.ALCcontext;
import com.shc.androidopenal.ALCdevice;
import com.shc.silenceengine.audio.AudioDevice;
import com.shc.silenceengine.audio.openal.ALBuffer;
import com.shc.silenceengine.backend.android.soundreaders.OggReader;
import com.shc.silenceengine.backend.android.soundreaders.WavReader;
import com.shc.silenceengine.core.SilenceException;
import com.shc.silenceengine.io.DirectBuffer;
import com.shc.silenceengine.io.PrimitiveSize;
import com.shc.silenceengine.utils.TaskManager;
import com.shc.silenceengine.utils.functional.UniCallback;
import java.nio.Buffer;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.IntBuffer;
import java.util.ArrayList;
import java.util.List;
import static com.shc.silenceengine.audio.AudioDevice.Constants.*;
/**
* @author Sri Harsha Chilakapati
*/
class AndroidAudioDevice extends AudioDevice
{
private static ALCdevice device;
private static ALCcontext context;
// List of all the sources, and the paused sources. The sources will be paused on out of focus,
// and will be resumed automatically on getting focus back.
private static final List<Integer> sources = new ArrayList<>();
private static final List<Integer> pausedSources = new ArrayList<>();
// The temp buffer is used to store the result from AndroidOpenAL.
private final IntBuffer temp = ByteBuffer.allocateDirect(PrimitiveSize.INT)
.order(ByteOrder.nativeOrder())
.asIntBuffer();
AndroidAudioDevice()
{
// Destroy old device and context if exist
if (device != null)
ALC.alcCloseDevice(device);
if (context != null)
ALC.alcDestroyContext(context);
sources.clear();
pausedSources.clear();
// Create a new device and context
device = ALC.alcOpenDevice();
context = ALC.alcCreateContext(device, null);
ALC.alcMakeContextCurrent(context);
}
@Override
public int alGenBuffers()
{
AL.alGenBuffers(1, temp);
return temp.get(0);
}
@Override
public void alBufferData(int id, int format, DirectBuffer data, int frequency)
{
AL.alBufferData(id, format, (Buffer) data.nativeBuffer(), data.sizeBytes(), frequency);
}
@Override
public void alDeleteBuffers(int... buffers)
{
DirectBuffer directBuffer = DirectBuffer.wrap(buffers);
AL.alDeleteBuffers(buffers.length, ((ByteBuffer) directBuffer.nativeBuffer()).asIntBuffer());
DirectBuffer.free(directBuffer);
}
@Override
public int alGetError()
{
return AL.alGetError();
}
@Override
public int alGenSources()
{
AL.alGenSources(1, temp);
int source = temp.get(0);
sources.add(source);
return source;
}
@Override
public void alSourcei(int id, int param, int value)
{
AL.alSourcei(id, param, value);
}
@Override
public void alSourcef(int id, int param, float value)
{
AL.alSourcef(id, param, value);
}
@Override
public void alSource3f(int id, int param, float v1, float v2, float v3)
{
AL.alSource3f(id, param, v1, v2, v3);
}
@Override
public void alSourcePlay(int id)
{
AL.alSourcePlay(id);
}
@Override
public void alSourcePause(int id)
{
AL.alSourcePause(id);
}
@Override
public void alSourceRewind(int id)
{
AL.alSourceRewind(id);
}
@Override
public void alSourceStop(int id)
{
AL.alSourceStop(id);
}
@Override
public int alGetSourcei(int id, int parameter)
{
IntBuffer intBuffer = ByteBuffer.allocateDirect(PrimitiveSize.INT).order(ByteOrder.nativeOrder()).asIntBuffer();
AL.alGetSourcei(id, parameter, intBuffer);
return intBuffer.get(0);
}
@Override
public void alDeleteSources(int... sources)
{
DirectBuffer directBuffer = DirectBuffer.wrap(sources);
AL.alDeleteSources(sources.length, ((ByteBuffer) directBuffer.nativeBuffer()).asIntBuffer());
DirectBuffer.free(directBuffer);
for (int i : sources)
AndroidAudioDevice.sources.remove((Integer) i);
}
@Override
public void readToALBuffer(AudioFormat format, DirectBuffer data, UniCallback<ALBuffer> onDecoded, UniCallback<Throwable> onError)
{
try
{
if (!isSupported(format))
throw new SilenceException("Cannot parse sound. The format is unsupported: " + format);
if (format == AudioFormat.WAV)
AsyncRunner.runAsync(() ->
{
WavReader reader = new WavReader(data);
ALBuffer alBuffer = new ALBuffer();
alBuffer.uploadData(new AndroidDirectBuffer(reader.data), reader.alFormat, reader.sampleRate);
return () -> TaskManager.runOnUpdate(() -> onDecoded.invoke(alBuffer));
});
else if (format == AudioFormat.OGG)
AsyncRunner.runAsync(() ->
{
OggReader reader = new OggReader(data);
ALBuffer alBuffer = new ALBuffer();
alBuffer.uploadData(new AndroidDirectBuffer(reader.getData()), reader.getFormat(), reader.getSampleRate());
return () -> TaskManager.runOnUpdate(() -> onDecoded.invoke(alBuffer));
});
}
catch (Throwable e)
{
onError.invoke(e);
}
}
@Override
public boolean isSupported(AudioFormat format)
{
switch (format)
{
case WAV:
return true;
case OGG:
return true;
}
return false;
}
void onFocusLost()
{
pausedSources.clear();
for (int source : sources)
{
AL.alGetSourcei(source, AL_SOURCE_STATE, temp);
// Pause the source explicitly if it is looping or playing
if (temp.get(0) == AL_PLAYING || temp.get(0) == AL_LOOPING)
{
pausedSources.add(source);
alSourcePause(source);
}
}
}
void onFocusGain()
{
for (int source : pausedSources)
alSourcePlay(source);
pausedSources.clear();
}
}