/*
Реализация спецификаций CLDC версии 1.1 (JSR-139), MIDP версии 2.1 (JSR-118)
и других спецификаций для функционирования компактных приложений на языке
Java (мидлетов) в среде программного обеспечения Малик Эмулятор.
Copyright © 2016, 2019 Малик Разработчик
Это свободная программа: вы можете перераспространять ее и/или изменять
ее на условиях Меньшей Стандартной общественной лицензии GNU в том виде,
в каком она была опубликована Фондом свободного программного обеспечения;
либо версии 3 лицензии, либо (по вашему выбору) любой более поздней версии.
Эта программа распространяется в надежде, что она будет полезной,
но БЕЗО ВСЯКИХ ГАРАНТИЙ; даже без неявной гарантии ТОВАРНОГО ВИДА
или ПРИГОДНОСТИ ДЛЯ ОПРЕДЕЛЕННЫХ ЦЕЛЕЙ. Подробнее см. в Меньшей Стандартной
общественной лицензии GNU.
Вы должны были получить копию Меньшей Стандартной общественной лицензии GNU
вместе с этой программой. Если это не так, см.
<https://www.gnu.org/licenses/>.
*/
package malik.emulator.midp.player;
import java.io.*;
import javax.microedition.media.*;
import javax.microedition.media.control.*;
import malik.emulator.fileformats.*;
import malik.emulator.fileformats.sound.*;
import malik.emulator.fileformats.sound.sampled.*;
import malik.emulator.media.sound.*;
public final class SampledPlayer extends ContentPlayer
implements SoundPlayerListener, VolumeControl, SoundEncoderGetter
{
private boolean stopped;
private boolean loaded;
private boolean muted;
private double microsOnPackedSample;
private int volume;
private int startFromIndex;
private int loopCountSetted;
private int loopCountRemaining;
private int channelsCount;
private int samplesPerSecond;
private long[] packedSamples;
private SoundPlayerSampled player;
private SoundDecoderSampled decoder;
private String contentType;
public SampledPlayer(SoundDecoderSampled decoder, String contentType)
{
this.volume = 100;
this.loopCountSetted = 1;
this.decoder = decoder;
this.contentType = contentType;
}
public void getData(InputStream source)
throws IOException, InvalidDataFormatException, MediaException
{
decoder.loadFromInputStream(source);
}
public void close()
{
SoundPlayerSampled sp;
SoundDecoderSampled sd;
if(state == CLOSED)
{
return;
}
synchronized(lock)
{
if(state != CLOSED)
{
state = CLOSED;
packedSamples = null;
if((sp = player) != null)
{
sp.close();
player = sp = null;
}
if((sd = decoder) != null)
{
sd.clear();
decoder = sd = null;
}
deliverEvent(PlayerListener.CLOSED, null);
}
}
}
public void realize()
throws MediaException
{
int error = 0;
synchronized(lock)
{
switch(state)
{
default:
break;
case CLOSED:
error = 1;
break;
case UNREALIZED:
if(realizePlayer())
{
error = 2;
}
break;
}
}
switch(error)
{
case 1:
throw new IllegalStateException("Player.realize: " +
"проигрыватель закрыт.");
case 2:
deliverEvent(PlayerListener.ERROR, "Player.realize: " +
"не удалось получить корректный массив отсчётов звукового сигнала.");
throw new MediaException("Player.realize: " +
"не удалось получить корректный массив отсчётов звукового сигнала.");
}
super.realize();
}
public void prefetch()
throws MediaException
{
int error = 0;
synchronized(lock)
{
switch(state)
{
default:
break;
case CLOSED:
error = 1;
break;
case UNREALIZED:
if(realizePlayer())
{
error = 2;
break;
}
if(packedSamples == null)
{
break;
}
/* fall through */
case REALIZED:
if(prefetchPlayer())
{
error = 3;
}
break;
}
}
switch(error)
{
case 1:
throw new IllegalStateException("Player.prefetch: " +
"проигрыватель закрыт.");
case 2:
deliverEvent(PlayerListener.ERROR, "Player.prefetch: " +
"не удалось получить корректный массив отсчётов звукового сигнала.");
throw new MediaException("Player.prefetch: " +
"не удалось получить корректный массив отсчётов звукового сигнала.");
case 3:
deliverEvent(PlayerListener.ERROR, "Player.prefetch: " +
"не удалось получить доступ к проигрывателю ИКМ.");
throw new MediaException("Player.prefetch: " +
"не удалось получить доступ к проигрывателю ИКМ.");
}
super.prefetch();
}
public void start()
throws MediaException
{
int error;
int position;
long mediaTime;
long[] psamples;
SoundPlayerSampled sp;
error = 0;
synchronized(lock)
{
switch(state)
{
default:
break;
case CLOSED:
error = 1;
break;
case UNREALIZED:
if(realizePlayer())
{
error = 2;
break;
}
if(packedSamples == null)
{
break;
}
/* fall through */
case REALIZED:
if(prefetchPlayer())
{
error = 3;
break;
}
/* fall through */
case PREFETCHED:
if(loopCountRemaining == 0)
{
loopCountRemaining = loopCountSetted;
}
psamples = packedSamples;
sp = player;
if(!loaded)
{
loaded = true;
position = startFromIndex;
sp.loadBlock(psamples, position, Math.min(psamples.length - position,
SoundPlayerSampled.MAX_BLOCK_LENGTH));
} else
{
position = startFromIndex +
sp.getCurrentBlockIndex() * SoundPlayerSampled.MAX_BLOCK_LENGTH +
sp.getCurrentBlockPosition();
sp.start();
}
mediaTime = (long) (microsOnPackedSample * (double) position);
state = STARTED;
deliverEvent(PlayerListener.STARTED, new Long(mediaTime));
break;
}
}
switch(error)
{
case 1:
throw new IllegalStateException("Player.prefetch: " +
"проигрыватель закрыт.");
case 2:
deliverEvent(PlayerListener.ERROR, "Player.prefetch: " +
"не удалось получить корректный массив отсчётов звукового сигнала.");
throw new MediaException("Player.prefetch: " +
"не удалось получить корректный массив отсчётов звукового сигнала.");
case 3:
deliverEvent(PlayerListener.ERROR, "Player.prefetch: " +
"не удалось получить доступ к проигрывателю ИКМ.");
throw new MediaException("Player.prefetch: " +
"не удалось получить доступ к проигрывателю ИКМ.");
}
}
public void stop()
throws MediaException
{
int error;
int position;
long mediaTime;
SoundPlayerSampled sp;
error = 0;
synchronized(lock)
{
switch(state)
{
default:
break;
case CLOSED:
error = 1;
break;
case STARTED:
(sp = player).stop();
position = startFromIndex +
sp.getCurrentBlockIndex() * SoundPlayerSampled.MAX_BLOCK_LENGTH +
sp.getCurrentBlockPosition();
mediaTime = (long) (microsOnPackedSample * (double) position);
state = PREFETCHED;
deliverEvent(PlayerListener.STOPPED, new Long(mediaTime));
break;
}
}
if(error == 1)
{
throw new IllegalStateException("Player.stop: " +
"проигрыватель закрыт.");
}
}
public void setLoopCount(int loopCount)
{
int error;
if(loopCount == 0)
{
throw new IllegalArgumentException("Player.setLoopCount: " +
"количество повторов не может быть нулевым.");
}
error = 0;
synchronized(lock)
{
switch(state)
{
case CLOSED:
error = 1;
break;
case STARTED:
error = 2;
break;
default:
loopCountSetted = loopCount;
loopCountRemaining = loopCount;
break;
}
}
switch(error)
{
case 1:
throw new IllegalStateException("Player.setLoopCount: " +
"проигрыватель закрыт.");
case 2:
deliverEvent(PlayerListener.ERROR, "Player.setLoopCount: " +
"невозможно задать количество повторов во время воспроизведения.");
throw new IllegalStateException("Player.setLoopCount: " +
"невозможно задать количество повторов во время воспроизведения.");
}
}
public long setMediaTime(long time)
throws MediaException
{
double microsOPS;
int error;
int maxpos;
int position;
long maxTime;
long[] psamples;
SoundPlayerSampled sp;
error = 0;
synchronized(lock)
{
switch(state)
{
case CLOSED:
error = 1;
break;
case UNREALIZED:
error = 2;
break;
case REALIZED:
maxTime = (long) ((microsOPS = microsOnPackedSample) *
(double) (maxpos = packedSamples.length));
time = time < 0L ? 0L : (time > maxTime ? maxTime : time);
startFromIndex = position = (position = (int) (((double) time) / microsOPS)) < 0 ?
0 : (position > maxpos ? maxpos : position);
time = (long) (microsOPS * (double) position);
break;
case PREFETCHED:
maxTime = (long) ((microsOPS = microsOnPackedSample) *
(double) (maxpos = packedSamples.length));
time = time < 0L ? 0L : (time > maxTime ? maxTime : time);
startFromIndex = position = (position = (int) (((double) time) / microsOPS)) < 0 ?
0 : (position > maxpos ? maxpos : position);
time = (long) (microsOPS * (double) position);
if(loaded)
{
loaded = false;
player.reset();
}
break;
case STARTED:
maxTime = (long) ((microsOPS = microsOnPackedSample) *
(double) (maxpos = (psamples = packedSamples).length));
time = time < 0L ? 0L : (time > maxTime ? maxTime : time);
startFromIndex = position = (position = (int) (((double) time) / microsOPS)) < 0 ?
0 : (position > maxpos ? maxpos : position);
time = (long) (microsOPS * (double) position);
loaded = true;
(sp = player).reset();
sp.loadBlock(psamples, position, Math.min(psamples.length - position,
SoundPlayerSampled.MAX_BLOCK_LENGTH));
break;
}
}
switch(error)
{
case 1:
throw new IllegalStateException("Player.setMediaTime: " +
"проигрыватель закрыт.");
case 2:
deliverEvent(PlayerListener.ERROR, "Player.setMediaTime: " +
"продолжительность ещё не получена.");
throw new IllegalStateException("Player.setMediaTime: " +
"продолжительность ещё не получена.");
}
return time;
}
public long getMediaTime()
{
int error;
int position;
long result;
SoundPlayerSampled sp;
error = 0;
synchronized(lock)
{
switch(state)
{
case CLOSED:
error = 1;
result = 0L;
break;
case UNREALIZED:
result = TIME_UNKNOWN;
break;
default:
if(loaded)
{
sp = player;
position = startFromIndex +
sp.getCurrentBlockIndex() * SoundPlayerSampled.MAX_BLOCK_LENGTH +
sp.getCurrentBlockPosition();
} else
{
position = startFromIndex;
}
result = (long) (microsOnPackedSample * (double) position);
break;
}
}
if(error == 1)
{
throw new IllegalStateException("Player.getMediaTime: " +
"проигрыватель закрыт.");
}
return result;
}
public long getDuration()
{
int error;
int position;
long result;
error = 0;
synchronized(lock)
{
switch(state)
{
case CLOSED:
error = 1;
result = 0L;
break;
case UNREALIZED:
result = TIME_UNKNOWN;
break;
default:
position = packedSamples.length;
result = (long) (microsOnPackedSample * (double) position);
break;
}
}
if(error == 1)
{
throw new IllegalStateException("Player.getMediaTime: " +
"проигрыватель закрыт.");
}
return result;
}
public void loadBlock(SoundPlayer player, int blockIndex)
{
int len;
int position;
long duration;
long[] psamples;
synchronized(lock)
{
len = (psamples = packedSamples).length;
if(blockIndex < 0)
{
position = len;
duration = (long) (microsOnPackedSample * (double) position);
if(--loopCountRemaining == 0)
{
loaded = false;
state = PREFETCHED;
deliverEvent(PlayerListener.END_OF_MEDIA, new Long(duration));
} else
{
player.loadBlock(psamples, 0, Math.min(position,
SoundPlayerSampled.MAX_BLOCK_LENGTH));
deliverEvent(PlayerListener.END_OF_MEDIA, new Long(duration));
deliverEvent(PlayerListener.STARTED, new Long(0L));
}
}
else if((position = startFromIndex + blockIndex *
SoundPlayerSampled.MAX_BLOCK_LENGTH) >= 0 && position < len)
{
player.loadBlock(psamples, position, Math.min(len - position,
SoundPlayerSampled.MAX_BLOCK_LENGTH));
}
}
}
public void setMute(boolean mute)
{
SoundPlayer sp;
if(this.muted != (this.muted = mute))
{
if((sp = player) != null)
{
sp.setVolume(SupportedPlayerFormats.midpVolumeToDeviceVolume(mute ? 0 : volume));
}
deliverEvent(PlayerListener.VOLUME_CHANGED, (VolumeControl) this);
}
}
public boolean isMuted()
{
return muted;
}
public int setLevel(int volume)
{
SoundPlayer sp;
if(this.volume != (this.volume = volume = (sp = player) != null ?
SupportedPlayerFormats.deviceVolumeToMIDPVolume(sp.setVolume(
SupportedPlayerFormats.midpVolumeToDeviceVolume(muted ? 0 : volume))) :
SupportedPlayerFormats.midpVolumeToMIDPVolume(volume)))
{
deliverEvent(PlayerListener.VOLUME_CHANGED, (VolumeControl) this);
}
return volume;
}
public int getLevel()
{
return volume;
}
public SoundEncoder getSoundEncoder()
throws InvalidDataFormatException
{
int i;
int j;
int len;
int error;
long psample;
short[] samples;
long[] psamples;
SoundEncoderSampled result;
error = 0;
synchronized(lock)
{
if(state == CLOSED)
{
error = 1;
psamples = null;
}
else if((psamples = packedSamples) == null)
{
error = 2;
}
}
switch(error)
{
case 1:
throw new IllegalStateException("SoundEncoderGetter.getSoundEncoder: " +
"проигрыватель закрыт.");
case 2:
throw new IllegalStateException("SoundEncoderGetter.getSoundEncoder: " +
"данные ещё не получены.");
}
samples = new short[(len = psamples.length) << 2];
for(i = 0, j = 0; j < len; j++)
{
samples[i++] = (short) (psample = psamples[j]);
samples[i++] = (short) (psample >> 16);
samples[i++] = (short) (psample >> 32);
samples[i++] = (short) (psample >> 48);
}
(result = new malik.emulator.fileformats.sound.sampled.wavelib.WaveEncoder()).
setSamples(channelsCount, samplesPerSecond, samples);
return result;
}
protected void unrealize()
{
SoundDecoder sd;
if((sd = decoder) != null)
{
stopped = true;
sd.stopLoading();
}
super.unrealize();
}
protected void unprefetch()
{
SoundPlayer sp;
synchronized(lock)
{
if((sp = player) != null)
{
sp.close();
player = null;
loaded = false;
startFromIndex = 0;
}
super.unprefetch();
}
}
protected String getPlayerContentType()
{
return contentType;
}
private boolean realizePlayer()
{
int i;
int j;
int ch;
int ss;
int len;
long duration;
long samplesOC;
short[] samples;
long[] psamples;
SoundDecoderSampled sd;
stopped = false;
if((samples = (sd = decoder).getSamples()) == null)
{
return false;
}
if((len = samples.length) < 4)
{
return true;
}
psamples = new long[len >>= 2];
for(j = (len - 1) << 2, i = len; i-- > 0; j -= 4)
{
if(stopped)
{
return false;
}
psamples[i] = ((long) (samples[j] & 0xffff)) |
(((long) (samples[j + 1] & 0xffff)) << 16) |
(((long) (samples[j + 2] & 0xffff)) << 32) |
(((long) (samples[j + 3] & 0xffff)) << 48);
}
duration = ((long) len) * 4000000L /
(samplesOC = (long) ((ch = sd.getChannels()) * (ss = sd.getSamplesPerSecond())));
decoder = null;
microsOnPackedSample = 4.e+006d / ((double) samplesOC);
channelsCount = ch;
samplesPerSecond = ss;
packedSamples = psamples;
state = REALIZED;
deliverEvent(PlayerListener.DURATION_UPDATED, new Long(duration));
return false;
}
private boolean prefetchPlayer()
{
boolean result;
SoundPlayerSampled sp;
try
{
player = sp = SoundPlayerSampled.open(samplesPerSecond, 16, channelsCount,
Math.min(Math.max(SoundPlayerSampled.MIN_BLOCK_LENGTH, packedSamples.length),
SoundPlayerSampled.MAX_BLOCK_LENGTH));
sp.setSoundPlayerListener(this);
sp.setVolume(muted ? 0 : SupportedPlayerFormats.midpVolumeToDeviceVolume(volume));
state = PREFETCHED;
result = false;
}
catch(SoundPlayerException e)
{
e.printRealStackTrace();
result = true;
}
return result;
}
}