Mobile Media API (MMAPI, JSR 135) 是 MIDP 2.0 標配、MIDP 1.0 可選包,提供統一的 Manager.createPlayer() 工廠方法播放音頻/視頻。本文聚焦遊戲常見場景:從 JAR 內部讀取 WAV 文件並播放,覆蓋流獲取、Player 生命週期、循環控制、異常處理。
| 格式 | MIME 類型 | 典型支持 | 備註 |
|---|---|---|---|
| WAV | audio/x-wav | 幾乎所有設備 | 無壓縮、體積大、延遲極低,適合短音效 |
| MIDI / SP-MIDI | audio/midi | 廣泛支持 | 體積小、適合背景音樂 |
| AMR | audio/amr | 部分 3G 設備 | 語音編碼,適合錄音回放 |
| MP3 / AAC | audio/mpeg | 高端/智能機 | 需授權解碼器,MIDP 2.0 不強制 |
實戰建議:音效用 8kHz/16bit 單聲道 WAV(< 20KB/個),背景音樂用 SP-MIDI,發布前用 Manager.getSupportedContentTypes(null) 探測目標設備支持列表。
// 1. 獲取輸入流 (從 JAR 根目錄 /res 下讀取)
InputStream is = getClass().getResourceAsStream("/res/click.wav");
// 注意:路徑以 '/' 開頭表示 JAR 根,區分大小寫
// 2. 創建 Player
Player player = Manager.createPlayer(is, "audio/x-wav"); // MIME 必須準確
// 3. 實現並啟動
player.realize(); // 預加載解碼器、分配資源(可選,start() 會隱式調用)
player.prefetch(); // 緩衝數據(可選)
player.start(); // 開始播放
package sound;
import javax.microedition.midlet.*;
import javax.microedition.lcdui.*;
import javax.microedition.media.*;
import java.io.*;
public class SoundMIDlet extends MIDlet {
private Player player;
public SoundMIDlet() {
try {
InputStream is = getClass().getResourceAsStream("/Autorun.wav");
player = Manager.createPlayer(is, "audio/x-wav");
} catch (IOException e) { System.err.println("IO: " + e); }
catch (MediaException e) { System.err.println("Media: " + e); }
}
protected void startApp() {
if (player != null) {
try { player.start(); }
catch (MediaException e) { System.err.println("Start: " + e); }
}
}
protected void pauseApp() {
if (player != null) player.stop(); // 釋放音頻設備
}
protected void destroyApp(boolean unconditional) {
if (player != null) {
player.close(); // 徹底釋放資源
player = null;
}
}
}
| 需求 | API | 示例 |
|---|---|---|
| 循環播放 N 次 | player.setLoopCount(n) | player.setLoopCount(3); (0=無限) |
| 設置音量 0-100 | VolumeControl vc = (VolumeControl) player.getControl("VolumeControl"); vc.setLevel(50); | 部分設備不支持,需 if (vc!=null) 守衛 |
| 獲取/設置播放時間 | player.getMediaTime() (微秒)、player.setMediaTime(us) | 精確同步歌詞/動畫 |
| 監聽播放結束 | player.addPlayerListener(new PlayerListener() { public void playerUpdate(Player p, String event, Object o) { if (PlayerListener.END_OF_MEDIA.equals(event)) { /* 下一首 */ } } }); | 實現播放列表 |
res 目錄)、文件名含中文/空格導致 URL 編碼問題。audio/wav 錯,正確 audio/x-wav)、設備確實不支持該格式。player.realize() 拋異常被吞沒、設備靜音模式、音量控制為 0、並發播放超過設備最大聲道數(通常 1-2)。destroyApp() 必須 player.close(),否則本地解碼器資源不釋放,導致後續創建 Player 失敗。start() 可能阻塞(緩衝),建議在非 UI 線程調用或預先 prefetch()。public class SoundManager {
private static SoundManager instance = new SoundManager();
private Hashtable players = new Hashtable(); // key: name, value: Player
private SoundManager() {}
public static SoundManager getInstance() { return instance; }
public void load(String name, String mime) {
try {
InputStream is = getClass().getResourceAsStream("/res/" + name);
Player p = Manager.createPlayer(is, mime);
p.realize(); p.prefetch();
players.put(name, p);
} catch (Exception e) { System.err.println("Load " + name + ": " + e); }
}
public void play(String name, int loopCount) {
Player p = (Player) players.get(name);
if (p != null) {
try { p.setLoopCount(loopCount); p.setMediaTime(0); p.start(); }
catch (MediaException e) { System.err.println("Play " + name + ": " + e); }
}
}
public void stopAll() {
Enumeration e = players.elements();
while (e.hasMoreElements()) {
Player p = (Player) e.nextElement();
p.stop(); p.close();
}
players.clear();
}
}
// 使用:
// SoundManager.getInstance().load("jump.wav", "audio/x-wav");
// SoundManager.getInstance().play("jump.wav", 1);
MMAPI 以工廠模式屏蔽了平台差異,「流 → Player → start」三步走極簡。遊戲開發中,建議啟動時預載所有音效至 SoundManager,運行時零延遲播放;pauseApp() 停止、destroyApp() 關閉,做個內存友好的良民應用。