/*
Реализация спецификаций 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.synthetic.*;
import malik.emulator.media.sound.*;
public final class SyntheticPlayer extends ContentPlayer
implements SoundPlayerListener, VolumeControl, SoundEncoderGetter
{
private boolean loaded;
private boolean muted;
private int volume;
private int startFromIndex;
private int loopCountSetted;
private int loopCountRemaining;
private long[] synthesizerMessages;
private SoundPlayerSynthetic player;
private SoundDecoderSynthetic decoder;
private String contentType;
public SyntheticPlayer(SoundDecoderSynthetic 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()
{
SoundPlayerSynthetic sp;
SoundDecoderSynthetic sd;
if(state == CLOSED)
{
return;
}
synchronized(lock)
{
if(state != CLOSED)
{
state = CLOSED;
synthesizerMessages = 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: " +
"не удалось получить корректный массив сообщений для синтезатора MIDI.");
throw new MediaException("Player.realize: " +
"не удалось получить корректный массив сообщений для синтезатора MIDI.");
}
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(synthesizerMessages == 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: " +
"не удалось получить корректный массив сообщений для синтезатора MIDI.");
throw new MediaException("Player.prefetch: " +
"не удалось получить корректный массив сообщений для синтезатора MIDI.");
case 3:
deliverEvent(PlayerListener.ERROR, "Player.prefetch: " +
"не удалось получить доступ к синтезатору MIDI.");
throw new MediaException("Player.prefetch: " +
"не удалось получить доступ к синтезатору MIDI.");
}
super.prefetch();
}
public void start()
throws MediaException
{
int error;
int position;
long mediaTime;
long[] messages;
SoundPlayerSynthetic sp;
error = 0;
synchronized(lock)
{
switch(state)
{
default:
break;
case CLOSED:
error = 1;
break;
case UNREALIZED:
if(realizePlayer())
{
error = 2;
break;
}
if(synthesizerMessages == null)
{
break;
}
/* fall through */
case REALIZED:
if(prefetchPlayer())
{
error = 3;
break;
}
/* fall through */
case PREFETCHED:
if(loopCountRemaining == 0)
{
loopCountRemaining = loopCountSetted;
}
messages = synthesizerMessages;
sp = player;
if(!loaded)
{
loaded = true;
position = startFromIndex;
sp.loadBlock(messages, 0, messages.length);
if(position > 0)
{
sp.setCurrentBlockPosition(position);
}
} else
{
position = sp.getCurrentBlockPosition();
sp.start();
}
mediaTime = 1000L * (messages[position] >>> 24);
state = STARTED;
deliverEvent(PlayerListener.STARTED, new Long(mediaTime));
break;
}
}
switch(error)
{
case 1:
throw new IllegalStateException("Player.start: " +
"проигрыватель закрыт.");
case 2:
deliverEvent(PlayerListener.ERROR, "Player.start: " +
"не удалось получить корректный массив сообщений для синтезатора MIDI.");
throw new MediaException("Player.start: " +
"не удалось получить корректный массив сообщений для синтезатора MIDI.");
case 3:
deliverEvent(PlayerListener.ERROR, "Player.start: " +
"не удалось получить доступ к синтезатору MIDI.");
throw new MediaException("Player.start: " +
"не удалось получить доступ к синтезатору MIDI.");
}
}
public void stop()
throws MediaException
{
int error;
int position;
long mediaTime;
long[] messages;
SoundPlayerSynthetic sp;
error = 0;
synchronized(lock)
{
switch(state)
{
default:
break;
case CLOSED:
error = 1;
break;
case STARTED:
(sp = player).stop();
position = sp.getCurrentBlockPosition();
messages = synthesizerMessages;
mediaTime = 1000L * (messages[position] >>> 24);
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
{
int error;
int positionA;
int positionB;
int posApprox;
long timeApprox;
long[] messages;
error = 0;
synchronized(lock)
{
switch(state)
{
case CLOSED:
error = 1;
break;
case UNREALIZED:
error = 2;
break;
default:
messages = synthesizerMessages;
positionA = 0;
positionB = messages.length - 1;
do
{
posApprox = (positionA + positionB) >> 1;
timeApprox = 1000L * (messages[posApprox] >>> 24);
if(positionA == positionB)
{
break;
}
if(time < timeApprox)
{
if((positionB = posApprox - 1) < positionA)
{
positionB = positionA;
}
}
else if(time > timeApprox)
{
if((positionA = posApprox + 1) > positionB)
{
positionA = positionB;
}
}
else
{
break;
}
} while(true);
time = timeApprox;
if(!loaded)
{
startFromIndex = posApprox;
break;
}
player.setCurrentBlockPosition(posApprox);
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;
long[] messages;
error = 0;
synchronized(lock)
{
switch(state)
{
case CLOSED:
error = 1;
result = 0L;
break;
case UNREALIZED:
result = TIME_UNKNOWN;
break;
default:
position = loaded ? player.getCurrentBlockPosition() : startFromIndex;
messages = synthesizerMessages;
result = 1000L * (messages[position] >>> 24);
break;
}
}
if(error == 1)
{
throw new IllegalStateException("Player.getMediaTime: " +
"проигрыватель закрыт.");
}
return result;
}
public long getDuration()
{
int error;
int position;
long result;
long[] messages;
error = 0;
synchronized(lock)
{
switch(state)
{
case CLOSED:
error = 1;
result = 0L;
break;
case UNREALIZED:
result = TIME_UNKNOWN;
break;
default:
position = (messages = synthesizerMessages).length - 1;
result = 1000L * (messages[position] >>> 24);
break;
}
}
if(error == 1)
{
throw new IllegalStateException("Player.getDuration: " +
"проигрыватель закрыт.");
}
return result;
}
public void loadBlock(SoundPlayer player, int blockIndex)
{
int position;
long duration;
long[] messages;
if(blockIndex >= 0)
{
return;
}
synchronized(lock)
{
position = (messages = synthesizerMessages).length - 1;
duration = 1000L * (messages[position] >>> 24);
if(--loopCountRemaining == 0)
{
state = PREFETCHED;
deliverEvent(PlayerListener.END_OF_MEDIA, new Long(duration));
} else
{
player.start();
deliverEvent(PlayerListener.END_OF_MEDIA, new Long(duration));
deliverEvent(PlayerListener.STARTED, new Long(0L));
}
}
}
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 error;
long[] messages;
SoundEncoderSynthetic result;
error = 0;
synchronized(lock)
{
if(state == CLOSED)
{
error = 1;
messages = null;
}
else if((messages = synthesizerMessages) == null)
{
error = 2;
}
}
switch(error)
{
case 1:
throw new IllegalStateException("SoundEncoderGetter.getSoundEncoder: " +
"проигрыватель закрыт.");
case 2:
throw new IllegalStateException("SoundEncoderGetter.getSoundEncoder: " +
"данные ещё не получены.");
}
(result = new malik.emulator.fileformats.sound.synthetic.midilib.MIDIEncoder()).
setMessages(messages);
return result;
}
protected void unrealize()
{
SoundDecoder sd;
if((sd = decoder) != null)
{
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 position;
long duration;
long[] messages;
if((messages = decoder.getMessages()) == null)
{
return false;
}
if((position = messages.length) <= 0)
{
return true;
}
position--;
duration = 1000L * (messages[position] >>> 24);
decoder = null;
synthesizerMessages = messages;
state = REALIZED;
deliverEvent(PlayerListener.DURATION_UPDATED, new Long(duration));
return false;
}
private boolean prefetchPlayer()
{
boolean result;
SoundPlayerSynthetic sp;
try
{
player = sp = SoundPlayerSynthetic.open();
sp.setSoundPlayerListener(this);
sp.setVolume(muted ? 0 : SupportedPlayerFormats.midpVolumeToDeviceVolume(volume));
state = PREFETCHED;
result = false;
}
catch(SoundPlayerException e)
{
e.printRealStackTrace();
result = true;
}
return result;
}
}