早期 J2ME 手機音效 API 混亂:Nokia 專有 com.nokia.mid.sound.Sound(S40)、com.nokia.mid.ui.DirectGraphics 系列、Moto V300 只支援 MMAPI javax.microedition.media.Player、部份機型僅支援 MIDI、部份支援 OTT(Nokia 鈴聲格式)。本文展示統一封裝策略,以單一介面支援多平台。
interface SoundPlayer {
void load(String resPath, int resId); // 載入資源
void play(int loopCount); // 播放
void stop(); // 停止
boolean isPlaying();
void setEnabled(boolean on); // 靜音開關
}
class NokiaS40Sound implements SoundPlayer {
private com.nokia.mid.sound.Sound sound;
private com.nokia.mid.sound.SoundListener listener = new SoundListener() {
public void soundStateChanged(Sound s, int event) {
playing = (event == Sound.SOUND_PLAYING);
}
};
private boolean playing = false, enabled = true;
public void load(String base, int id) {
try {
InputStream is = getClass().getResourceAsStream(base + "/" + id + ".ott");
byte[] data = readAll(is);
sound = new Sound(data, Sound.FORMAT_TONE);
sound.setSoundListener(listener);
} catch (Exception e) { sound = null; }
}
public void play(int loop) {
if (!enabled || sound == null) return;
try { if (playing) stop(); sound.play(loop); }
catch (Exception e) { playing = false; }
}
public void stop() { if (sound != null) sound.stop(); playing = false; }
public boolean isPlaying() { return playing; }
public void setEnabled(boolean on) { enabled = on; if (!on) stop(); }
}
class MmaSound implements SoundPlayer {
private javax.microedition.media.Player player;
private javax.microedition.media.PlayerListener listener = new PlayerListener() {
public void playerUpdate(Player p, String event, Object data) {
if (PlayerListener.STARTED.equals(event)) playing = true;
else if (PlayerListener.STOPPED.equals(event)) playing = false;
}
};
private boolean playing = false, enabled = true;
public void load(String base, int id) {
try {
InputStream is = getClass().getResourceAsStream(base + "/" + id + ".mid");
byte[] data = readAll(is);
player = Manager.createPlayer(new ByteArrayInputStream(data), "audio/midi");
player.addPlayerListener(listener);
player.realize();
player.prefetch();
} catch (Exception e) { player = null; }
}
public void play(int loop) {
if (!enabled || player == null) return;
try { if (playing) stop(); player.setLoopCount(loop); player.start(); }
catch (Exception e) { playing = false; }
}
public void stop() { try { if (player != null) player.stop(); } catch (Exception e) {} playing = false; }
public boolean isPlaying() { return playing; }
public void setEnabled(boolean on) { enabled = on; if (!on) stop(); }
}
V300 需指定 MIME audio/midi 或 audio/sp-midi,載入流程同 MMAPI,但 EMSound 類別中 type 欄位改為 String。
class SoundFactory {
public static SoundPlayer create() {
String platform = System.getProperty("microedition.platform");
if (platform != null && platform.toLowerCase().indexOf("nokia") >= 0) {
// 進一步判斷 S40/S60:嘗試載入 Sound 類別
try {
Class.forName("com.nokia.mid.sound.Sound");
return new NokiaS40Sound(); // S40
} catch (ClassNotFoundException e) {
return new MmaSound(); // S60 或其他支援 MMAPI 的 Nokia
}
} else if (platform != null && platform.toLowerCase().indexOf("motorola") >= 0) {
return new MmaSound(); // V300 等
}
// 預設回退
return new MmaSound();
}
}
private byte[] readAll(InputStream is) throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] buf = new byte[1024];
int len;
while ((len = is.read(buf)) != -1) baos.write(buf, 0, len);
is.close();
return baos.toByteArray();
}
SoundPlayer bgm = SoundFactory.create();
bgm.load("/res/snd", 1); // 載入 /res/snd/1.ott 或 1.mid
bgm.play(-1); // 循環播放
// 選單切換音效
SoundPlayer sfx = SoundFactory.create();
sfx.load("/res/snd", 2);
sfx.play(1);
.ott (S40)、.mid (S60/MMAPI) 兩版,打包時依平台剔除或全量打包由工廠選擇。stop() 後應 player.deallocate() 或 sound.release() 釋放原生資源。setEnabled()。以介面隔離 + 工廠模式 + 平台偵測封裝碎片化音效 API,使遊戲邏輯零感知地跨平台運行。此模式同樣適用於震動、全螢幕、鍵盤燈等差異化功能。